|
|
|
|
@ -18,13 +18,24 @@ def get_data(stocks, start, end):
|
|
|
|
|
cache_name="cache", backend="sqlite", expire_after=expire_after
|
|
|
|
|
)
|
|
|
|
|
session.headers = DEFAULT_HEADERS
|
|
|
|
|
start = datetime.datetime.fromisoformat("1900-01-01")
|
|
|
|
|
end = datetime.datetime.now()
|
|
|
|
|
stockData = pdr.get_data_yahoo(stocks, start, end, session=session)
|
|
|
|
|
return stockData
|
|
|
|
|
stockData = pdr.get_data_yahoo(
|
|
|
|
|
stocks,
|
|
|
|
|
datetime.datetime.fromisoformat("1900-01-01"),
|
|
|
|
|
datetime.datetime.now(),
|
|
|
|
|
session=session,
|
|
|
|
|
)
|
|
|
|
|
return stockData.loc[start:end]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def human_readable_size(size, decimal_places=3):
|
|
|
|
|
for unit in ["$", "K$", "M$", "G$", "T$", "P$"]:
|
|
|
|
|
if size < 1000.0 or unit == "P$":
|
|
|
|
|
return f"{size:.{decimal_places}f} {unit}"
|
|
|
|
|
break
|
|
|
|
|
size /= 1000.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def run(strategy, data, fund_mode=False):
|
|
|
|
|
def prepare_simulation(strategy, params, data, fund_mode=False):
|
|
|
|
|
if fund_mode:
|
|
|
|
|
cerebro = bt.Cerebro()
|
|
|
|
|
cerebro.addobserver(bt.observers.FundShares)
|
|
|
|
|
@ -34,7 +45,7 @@ def run(strategy, data, fund_mode=False):
|
|
|
|
|
cerebro.addobserver(bt.observers.BuySell)
|
|
|
|
|
|
|
|
|
|
cerebro.adddata(data)
|
|
|
|
|
cerebro.addstrategy(strategy)
|
|
|
|
|
cerebro.addstrategy(strategy, params)
|
|
|
|
|
|
|
|
|
|
# Broker Information
|
|
|
|
|
broker_args = dict(coc=True)
|
|
|
|
|
@ -45,68 +56,100 @@ def run(strategy, data, fund_mode=False):
|
|
|
|
|
if fund_mode:
|
|
|
|
|
cerebro.broker.set_fundmode(True)
|
|
|
|
|
|
|
|
|
|
cerebro.broker.set_cash(st.month_sum)
|
|
|
|
|
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]
|
|
|
|
|
# cerebro.plot(iplot=False, style="candlestick")
|
|
|
|
|
return thestrat
|
|
|
|
|
return cerebro
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
def simulate(stockData, monthly_params):
|
|
|
|
|
pd.options.display.float_format = "{:,.2f}".format
|
|
|
|
|
df = pd.DataFrame(
|
|
|
|
|
columns=[
|
|
|
|
|
"froi",
|
|
|
|
|
"froi%",
|
|
|
|
|
"cost",
|
|
|
|
|
"total_value",
|
|
|
|
|
"times",
|
|
|
|
|
"units",
|
|
|
|
|
"#deals",
|
|
|
|
|
"#units",
|
|
|
|
|
"comms",
|
|
|
|
|
"monthly",
|
|
|
|
|
"annual%",
|
|
|
|
|
"max_dd_len",
|
|
|
|
|
"max_dd",
|
|
|
|
|
"max_dd_days",
|
|
|
|
|
"max_dd%",
|
|
|
|
|
"max_md",
|
|
|
|
|
"twr",
|
|
|
|
|
"vwr",
|
|
|
|
|
"tr",
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
actualStart: datetime.datetime = stockData.index[0]
|
|
|
|
|
actualEnd: datetime.datetime = stockData.index[-1]
|
|
|
|
|
data = bt.feeds.PandasData(dataname=stockData)
|
|
|
|
|
|
|
|
|
|
for i, strategy in enumerate((st.DCA, st.QDCA, st.VA, st.QVA)):
|
|
|
|
|
cerebro = prepare_simulation(strategy, monthly_params, data)
|
|
|
|
|
cerebro.broker.set_cash(monthly_params["sum"])
|
|
|
|
|
therun = cerebro.run()[0]
|
|
|
|
|
# cerebro.plot(iplot=False, style="candlestick")
|
|
|
|
|
dd = therun.analyzers.drawdown
|
|
|
|
|
ret = therun.analyzers.returns
|
|
|
|
|
tr = therun.analyzers.tr
|
|
|
|
|
# print(next(reversed(tr.get_analysis())))
|
|
|
|
|
params = therun.calc_params()
|
|
|
|
|
# omg IM so sorry for this, ironically this is here to get human readable size
|
|
|
|
|
for i in 1, 2, 5, 6:
|
|
|
|
|
params[i] = human_readable_size(params[i])
|
|
|
|
|
annual = 100 * (
|
|
|
|
|
(1 + params[0] / 100) ** (365 / (actualEnd - actualStart).days) - 1
|
|
|
|
|
)
|
|
|
|
|
df.loc[strategy.__name__] = params + [
|
|
|
|
|
annual,
|
|
|
|
|
dd.get_analysis().max.len,
|
|
|
|
|
dd.get_analysis().max.drawdown,
|
|
|
|
|
human_readable_size(dd.get_analysis().max.moneydown),
|
|
|
|
|
ret.get_analysis()["vwr"],
|
|
|
|
|
list(tr.get_analysis().items())[0][1],
|
|
|
|
|
]
|
|
|
|
|
print(
|
|
|
|
|
"Investing monthly, increasing {:.2f}%, starting from ${}, from {} to {}, {:.1f} years".format(
|
|
|
|
|
(monthly_params["coef"] * 100) - 100,
|
|
|
|
|
monthly_params["sum"],
|
|
|
|
|
actualStart.date(),
|
|
|
|
|
actualEnd.date(),
|
|
|
|
|
(actualEnd - actualStart).days / 365,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
# print(df[["annual%", "froi%", "cost", "total_value", "max_dd%", "max_md"]])
|
|
|
|
|
print(df)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
stockList = ["^GSPC"]
|
|
|
|
|
monthly_params = dict(sum=1000, coef=1.001)
|
|
|
|
|
# startDate = datetime.datetime.fromisoformat("2000-01-01")
|
|
|
|
|
# endDate = datetime.datetime.fromisoformat("2022-01-01")
|
|
|
|
|
print(stockList[0])
|
|
|
|
|
for period_years in (20, 50):
|
|
|
|
|
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(
|
|
|
|
|
(st.SmaCross, st.DCA, st.QDCA, st.VA, st.QVA, st.SmaVA)
|
|
|
|
|
):
|
|
|
|
|
therun = run(strategy, data)
|
|
|
|
|
dd = therun.analyzers.drawdown
|
|
|
|
|
ret = therun.analyzers.returns
|
|
|
|
|
tr = therun.analyzers.tr
|
|
|
|
|
# print(next(reversed(tr.get_analysis())))
|
|
|
|
|
params = therun.calc_params()
|
|
|
|
|
annual = 100 * (
|
|
|
|
|
(1 + params[0] / 100) ** (365 / (endDate - actualStart).days) - 1
|
|
|
|
|
)
|
|
|
|
|
df.loc[strategy.__name__] = therun.calc_params() + (
|
|
|
|
|
annual,
|
|
|
|
|
dd.get_analysis().max.len,
|
|
|
|
|
dd.get_analysis().max.drawdown,
|
|
|
|
|
dd.get_analysis().max.moneydown,
|
|
|
|
|
ret.get_analysis()["vwr"],
|
|
|
|
|
list(tr.get_analysis().items())[0][1],
|
|
|
|
|
)
|
|
|
|
|
print("Starting from:", actualStart)
|
|
|
|
|
print("Time in Market: {:.1f} years".format((endDate - actualStart).days / 365))
|
|
|
|
|
# print(df[["annual%", "froi", "cost", "total_value", "max_dd", "max_md"]])
|
|
|
|
|
print(df)
|
|
|
|
|
for period_years in (1, 2, 5, 10, 20, 50, 100):
|
|
|
|
|
end_date = datetime.datetime.now()
|
|
|
|
|
start_date = end_date - datetime.timedelta(days=period_years * 365)
|
|
|
|
|
stockData = get_data(stockList[0], start_date, end_date)
|
|
|
|
|
simulate(stockData, monthly_params)
|
|
|
|
|
"""
|
|
|
|
|
period_years = 10
|
|
|
|
|
stockData = get_data(
|
|
|
|
|
stockList[0],
|
|
|
|
|
datetime.datetime.fromisoformat("1900-01-01"),
|
|
|
|
|
datetime.datetime.now(),
|
|
|
|
|
)
|
|
|
|
|
start_date = stockData.index[0]
|
|
|
|
|
end_date = start_date + datetime.timedelta(days=period_years * 365)
|
|
|
|
|
while start_date < datetime.datetime.today():
|
|
|
|
|
stockData = get_data(stockList[0], start_date, end_date)
|
|
|
|
|
simulate(stockData)
|
|
|
|
|
start_date, end_date = (
|
|
|
|
|
end_date,
|
|
|
|
|
end_date + datetime.timedelta(days=period_years * 365),
|
|
|
|
|
)
|
|
|
|
|
"""
|
|
|
|
|
|