|
|
|
@ -6,27 +6,11 @@ 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"]
|
|
|
|
stockList = ["SWPPX"]
|
|
|
|
total_days_in_market = 365 * 10
|
|
|
|
|
|
|
|
month_sum = 500 # usd
|
|
|
|
month_sum = 500 # usd
|
|
|
|
reserve = 5 # usd for comms etc
|
|
|
|
reserve = 5 # usd for comms etc
|
|
|
|
startDate = datetime.datetime.fromisoformat("2000-01-01")
|
|
|
|
# startDate = datetime.datetime.fromisoformat("2000-01-01")
|
|
|
|
endDate = datetime.datetime.fromisoformat("2022-01-30")
|
|
|
|
# endDate = datetime.datetime.fromisoformat("2022-01-01")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# import data
|
|
|
|
|
|
|
|
def get_data(stocks, start, end):
|
|
|
|
|
|
|
|
stockData = pdr.get_data_yahoo(stocks, start, end)
|
|
|
|
|
|
|
|
return stockData
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 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]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
data = bt.feeds.PandasData(dataname=stockData)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LSI(bt.Strategy):
|
|
|
|
class LSI(bt.Strategy):
|
|
|
|
@ -53,12 +37,15 @@ class LSI(bt.Strategy):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class PercentageCommisionScheme(bt.CommInfoBase):
|
|
|
|
class PercentageCommisionScheme(bt.CommInfoBase):
|
|
|
|
paras = (
|
|
|
|
params = (
|
|
|
|
("commission", 0.004),
|
|
|
|
("commission", 0.004),
|
|
|
|
("stocklike", True),
|
|
|
|
("stocklike", True),
|
|
|
|
("commtype", bt.CommInfoBase.COMM_PERC),
|
|
|
|
("commtype", bt.CommInfoBase.COMM_PERC),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _getcommission(self, size, price, pseudoexec):
|
|
|
|
|
|
|
|
return size * price * self.p.commission + 4 # 290rub/month
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class FormulaInvesting(bt.Strategy):
|
|
|
|
class FormulaInvesting(bt.Strategy):
|
|
|
|
def __init__(self):
|
|
|
|
def __init__(self):
|
|
|
|
@ -71,10 +58,10 @@ class FormulaInvesting(bt.Strategy):
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
self.cash_start = self.broker.get_cash()
|
|
|
|
self.cash_start = self.broker.get_cash()
|
|
|
|
self.val_start = 100.0
|
|
|
|
self.val_start = 100.0
|
|
|
|
@ -93,31 +80,22 @@ class FormulaInvesting(bt.Strategy):
|
|
|
|
|
|
|
|
|
|
|
|
if order.status in [order.Completed]:
|
|
|
|
if order.status in [order.Completed]:
|
|
|
|
if order.isbuy():
|
|
|
|
if order.isbuy():
|
|
|
|
self.log(
|
|
|
|
|
|
|
|
"BUY EXECUTED, Price %.2f, Cost %.2f, Comm %.2f, Size %.0f"
|
|
|
|
|
|
|
|
% (
|
|
|
|
|
|
|
|
order.executed.price,
|
|
|
|
|
|
|
|
order.executed.value,
|
|
|
|
|
|
|
|
order.executed.comm,
|
|
|
|
|
|
|
|
order.executed.size,
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.units += order.executed.size
|
|
|
|
self.units += order.executed.size
|
|
|
|
self.cost += order.executed.value
|
|
|
|
self.cost += order.executed.value
|
|
|
|
self.comms += order.executed.comm
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
elif order.issell():
|
|
|
|
elif order.issell():
|
|
|
|
|
|
|
|
self.units -= order.executed.size
|
|
|
|
|
|
|
|
|
|
|
|
self.log(
|
|
|
|
self.log(
|
|
|
|
"SELL EXECUTED, Price %.2f, Cost %.2f, Comm %.2f, Size %.0f"
|
|
|
|
"%s Price %.2f, Units %.0f, Value %.2f, Comm %.2f, "
|
|
|
|
% (
|
|
|
|
% (
|
|
|
|
|
|
|
|
"BUY" if order.isbuy else "SELL",
|
|
|
|
order.executed.price,
|
|
|
|
order.executed.price,
|
|
|
|
|
|
|
|
order.executed.size,
|
|
|
|
order.executed.value,
|
|
|
|
order.executed.value,
|
|
|
|
order.executed.comm,
|
|
|
|
order.executed.comm,
|
|
|
|
order.executed.size,
|
|
|
|
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
self.units -= order.executed.size
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
self.comms += order.executed.comm
|
|
|
|
self.comms += order.executed.comm
|
|
|
|
self.times += 1
|
|
|
|
self.times += 1
|
|
|
|
@ -128,19 +106,15 @@ class FormulaInvesting(bt.Strategy):
|
|
|
|
|
|
|
|
|
|
|
|
self.order = None
|
|
|
|
self.order = None
|
|
|
|
|
|
|
|
|
|
|
|
def stop(self):
|
|
|
|
|
|
|
|
self.calc_params()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def calc_params(self):
|
|
|
|
def calc_params(self):
|
|
|
|
# calculate actual returns
|
|
|
|
# calculate actual returns
|
|
|
|
self.froi = self.broker.get_fundvalue() - self.val_start
|
|
|
|
self.froi = self.broker.get_fundvalue() - self.val_start
|
|
|
|
value = self.datas[0].close * self.units + self.broker.get_cash()
|
|
|
|
|
|
|
|
return (
|
|
|
|
return (
|
|
|
|
# Annual
|
|
|
|
# Annual
|
|
|
|
100 * ((1 + self.froi / 100) ** (365 / (endDate - actualStart).days) - 1),
|
|
|
|
100 * ((1 + self.froi / 100) ** (365 / (endDate - actualStart).days) - 1),
|
|
|
|
self.froi,
|
|
|
|
self.froi,
|
|
|
|
self.cost,
|
|
|
|
self.cost,
|
|
|
|
value,
|
|
|
|
self.broker.get_value() + self.broker.get_cash(),
|
|
|
|
self.times,
|
|
|
|
self.times,
|
|
|
|
self.units,
|
|
|
|
self.units,
|
|
|
|
self.comms,
|
|
|
|
self.comms,
|
|
|
|
@ -179,6 +153,7 @@ class QDCA(DCA):
|
|
|
|
def run(strategy, data, fund_mode=False):
|
|
|
|
def run(strategy, data, fund_mode=False):
|
|
|
|
if fund_mode:
|
|
|
|
if fund_mode:
|
|
|
|
cerebro = bt.Cerebro()
|
|
|
|
cerebro = bt.Cerebro()
|
|
|
|
|
|
|
|
cerebro.addobserver(bt.observers.FundShares)
|
|
|
|
else:
|
|
|
|
else:
|
|
|
|
cerebro = bt.Cerebro(stdstats=False)
|
|
|
|
cerebro = bt.Cerebro(stdstats=False)
|
|
|
|
cerebro.addobserver(bt.observers.Broker)
|
|
|
|
cerebro.addobserver(bt.observers.Broker)
|
|
|
|
@ -186,8 +161,6 @@ def run(strategy, data, fund_mode=False):
|
|
|
|
|
|
|
|
|
|
|
|
cerebro.adddata(data)
|
|
|
|
cerebro.adddata(data)
|
|
|
|
cerebro.addstrategy(strategy)
|
|
|
|
cerebro.addstrategy(strategy)
|
|
|
|
print("-" * 50)
|
|
|
|
|
|
|
|
print(strategy.__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Broker Information
|
|
|
|
# Broker Information
|
|
|
|
broker_args = dict(coc=True)
|
|
|
|
broker_args = dict(coc=True)
|
|
|
|
@ -206,7 +179,7 @@ def run(strategy, data, fund_mode=False):
|
|
|
|
)
|
|
|
|
)
|
|
|
|
thestrats = cerebro.run()
|
|
|
|
thestrats = cerebro.run()
|
|
|
|
thestrat = thestrats[0]
|
|
|
|
thestrat = thestrats[0]
|
|
|
|
cerebro.plot(iplot=False, style="candlestick")
|
|
|
|
# cerebro.plot(iplot=False, style="candlestick")
|
|
|
|
return thestrat
|
|
|
|
return thestrat
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -226,7 +199,19 @@ if __name__ == "__main__":
|
|
|
|
"twr",
|
|
|
|
"twr",
|
|
|
|
]
|
|
|
|
]
|
|
|
|
)
|
|
|
|
)
|
|
|
|
for i, strategy in enumerate((DCA, QDCA, VA, QVA)):
|
|
|
|
# import data
|
|
|
|
|
|
|
|
def get_data(stocks, start, end):
|
|
|
|
|
|
|
|
stockData = pdr.get_data_yahoo(stocks, start, end)
|
|
|
|
|
|
|
|
return stockData
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for period_years in (1, 2, 5, 10, 15, 20, 25):
|
|
|
|
|
|
|
|
endDate = datetime.datetime.now()
|
|
|
|
|
|
|
|
startDate = endDate - datetime.timedelta(days=period_years * 365)
|
|
|
|
|
|
|
|
stockData = get_data(stockList[0], startDate, endDate)
|
|
|
|
|
|
|
|
actualStart: datetime.datetime = stockData.index[0]
|
|
|
|
|
|
|
|
data = bt.feeds.PandasData(dataname=stockData)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for i, strategy in enumerate((DCA, QDCA)): # , VA, QVA)):
|
|
|
|
therun = run(strategy, data)
|
|
|
|
therun = run(strategy, data)
|
|
|
|
dd = therun.analyzers.drawdown
|
|
|
|
dd = therun.analyzers.drawdown
|
|
|
|
ret = therun.analyzers.returns
|
|
|
|
ret = therun.analyzers.returns
|
|
|
|
@ -241,4 +226,5 @@ if __name__ == "__main__":
|
|
|
|
)
|
|
|
|
)
|
|
|
|
print("Starting from:", actualStart)
|
|
|
|
print("Starting from:", actualStart)
|
|
|
|
print("Time in Market: {:.1f} years".format((endDate - actualStart).days / 365))
|
|
|
|
print("Time in Market: {:.1f} years".format((endDate - actualStart).days / 365))
|
|
|
|
print(df)
|
|
|
|
print(df[["annual%", "froi", "cost", "total_value"]])
|
|
|
|
|
|
|
|
# print(df)
|
|
|
|
|