import os
os.chdir('../src')
import math
import numpy as np
import pandas as pd
import matplotlib.pyplot as matplt
from matplotlib import colors
import seaborn as sns
from datetime import datetime
from pyalgotrade import strategy
from pyalgotrade import plotter
from pyalgotrade.stratanalyzer import sharpe, drawdown, trades, returns
from pandas_barfeed import DataFrameBarFeed
from pyalgotrade.technical import ma
from pyalgotrade import barfeed
from strats import rsi2
instrument = 'BTC_ETH'
original = pd.read_json('../data/raw/BTC_ETH.json')
original = original[2000:].reset_index().drop('index', axis=1)
Let's look at a baseline of just holding ethereum.
import math
log_price = pd.Series(np.log(original.close))
yearly_returns = (log_price - log_price.shift(365*24*2)).dropna()
sns.kdeplot(yearly_returns)
matplt.axvline(np.mean(yearly_returns), c='red')
matplt.axvline(yearly_returns.quantile(0.25), c='green')
monthly_returns = (log_price - log_price.shift(30*24*2)).dropna()
sns.kdeplot(monthly_returns)
matplt.axvline(np.mean(monthly_returns), c='red')
matplt.axvline(monthly_returns.quantile(0.25), c='green')
At first glance, this distribution seems to have a desireble shape, since if you avoid the downside, you can have potentially unlimited returns. On second thought, that just is because of the hype train that ocurred in the past. I don't think that insane growth is replicable, so the distribution probably doesn't reflect the true distribution. This is the problem with this higher timeframe return rate distributions.
monthly_returns = (log_price - log_price.shift(7*24*2)).dropna()
sns.kdeplot(monthly_returns)
matplt.axvline(np.mean(monthly_returns), c='red')
matplt.axvline(monthly_returns.quantile(0.25), c='green')
dayly_returns = (log_price - log_price.shift(24*2)).dropna()
sns.kdeplot(dayly_returns)
matplt.axvline(np.mean(dayly_returns), c='red')
matplt.axvline(dayly_returns.quantile(0.25), c='green')
print('mean yearly returns in non-logarithmic:')
math.exp(np.mean(yearly_returns))
class HODL(strategy.BacktestingStrategy):
def __init__(self, feed, instrument):
super(HODL, self).__init__(feed)
self.getBroker().getFillStrategy().setVolumeLimit(None)
self.__instrument = instrument
self.__position = None
# self.__ma1 = ma.SMA(feed[instrument].getPriceDataSeries(), 56)
# self.__ma2 = ma.SMA(feed[instrument].getPriceDataSeries(), 28)
# self.iter = 0
def onEnterCanceled(self, position):
self.__position = None
def onExitOk(self, position):
self.__position = None
def onExitCanceled(self, position):
# If the exit was canceled, re-submit it.
self.__position.exitMarket()
def onBars(self, bars):
if self.__position is None:
shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice())
self.__position = self.enterLong(self.__instrument, shares, True)
def run_strat(strat, timeframe=(len(original) * barfeed.Frequency.HOUR / 2)):
retAnalyzer = returns.Returns()
strat.attachAnalyzer(retAnalyzer)
sharpeRatioAnalyzer = sharpe.SharpeRatio()
strat.attachAnalyzer(sharpeRatioAnalyzer)
drawDownAnalyzer = drawdown.DrawDown()
strat.attachAnalyzer(drawDownAnalyzer)
tradesAnalyzer = trades.Trades()
strat.attachAnalyzer(tradesAnalyzer)
plt = plotter.StrategyPlotter(strat, True, False, True)
strat.run()
plt.plot()
print("Final portfolio value: $%.2f" % strat.getResult())
print("Cumulative returns: %.2f %%" % (retAnalyzer.getCumulativeReturns()[-1] * 100))
print("Expected monthly returns: %.2f %%" % (math.pow(1 + retAnalyzer.getCumulativeReturns()[-1],
barfeed.Frequency.MONTH / timeframe) * 100 - 100))
print("Expected yearly returns: %.2f %%" % (math.pow(1 + retAnalyzer.getCumulativeReturns()[-1],
barfeed.Frequency.DAY * 365 / timeframe) * 100 - 100))
print("Sharpe ratio: %.2f" % (sharpeRatioAnalyzer.getSharpeRatio(0.05)))
print("Max. drawdown: %.2f %%" % (drawDownAnalyzer.getMaxDrawDown() * 100))
print("Longest drawdown duration: %s" % (drawDownAnalyzer.getLongestDrawDownDuration()))
print("")
print("Total trades: %d" % (tradesAnalyzer.getCount()))
if tradesAnalyzer.getCount() > 0:
profits = tradesAnalyzer.getAll()
print("Avg. profit: $%2.f" % (profits.mean()))
print("Profits std. dev.: $%2.f" % (profits.std()))
print("Max. profit: $%2.f" % (profits.max()))
print("Min. profit: $%2.f" % (profits.min()))
ret = tradesAnalyzer.getAllReturns()
print("Avg. return: %2.f %%" % (ret.mean() * 100))
print("Returns std. dev.: %2.f %%" % (ret.std() * 100))
print("Max. return: %2.f %%" % (ret.max() * 100))
print("Min. return: %2.f %%" % (ret.min() * 100))
print("")
print("Profitable trades: %d" % (tradesAnalyzer.getProfitableCount()))
if tradesAnalyzer.getProfitableCount() > 0:
profits = tradesAnalyzer.getProfits()
print("Avg. profit: $%2.f" % (profits.mean()))
print("Profits std. dev.: $%2.f" % (profits.std()))
print("Max. profit: $%2.f" % (profits.max()))
print("Min. profit: $%2.f" % (profits.min()))
ret = tradesAnalyzer.getPositiveReturns()
print("Avg. return: %2.f %%" % (ret.mean() * 100))
print("Returns std. dev.: %2.f %%" % (ret.std() * 100))
print("Max. return: %2.f %%" % (ret.max() * 100))
print("Min. return: %2.f %%" % (ret.min() * 100))
print("")
print("Unprofitable trades: %d" % (tradesAnalyzer.getUnprofitableCount()))
if tradesAnalyzer.getUnprofitableCount() > 0:
losses = tradesAnalyzer.getLosses()
print("Avg. loss: $%2.f" % (losses.mean()))
print("Losses std. dev.: $%2.f" % (losses.std()))
print("Max. loss: $%2.f" % (losses.min()))
print("Min. loss: $%2.f" % (losses.max()))
ret = tradesAnalyzer.getNegativeReturns()
print("Avg. return: %2.f %%" % (ret.mean() * 100))
print("Returns std. dev.: %2.f %%" % (ret.std() * 100))
print("Max. return: %2.f %%" % (ret.max() * 100))
print("Min. return: %2.f %%" % (ret.min() * 100))
instrument = 'BTC_ETH'
feed = DataFrameBarFeed(original, instrument, barfeed.Frequency.HOUR / 2)
hodl = HODL(feed, instrument)
run_strat(hodl)
Oh, a sample strategy, I hope it is not too bad. HOLY SHIT CUMULATIVE RETURNS OF 2500% AND A MUCH BETTER RISK PROFILE THAN HODLING!!!!
instrument = 'BTC_ETH'
feed = DataFrameBarFeed(original, instrument, barfeed.Frequency.HOUR / 2)
entrySMA = 200
exitSMA = 5
rsiPeriod = 2
overBoughtThreshold = 90
overSoldThreshold = 10
strat = rsi2.RSI2(feed, instrument, entrySMA, exitSMA, rsiPeriod, overBoughtThreshold, overSoldThreshold)
run_strat(strat)
barfeed.Frequency.MONTH / barfeed.Frequency.HOUR * 2
(len(original) * barfeed.Frequency.HOUR / 2) / barfeed.Frequency.MONTH
original.tail()
for i in [3 * 12, 8]:
index = int(i * (barfeed.Frequency.MONTH / barfeed.Frequency.HOUR) * 2)
print("running on last {} months".format(i))
instrument = 'BTC_ETH'
feed = DataFrameBarFeed(original[-index:].reset_index(), instrument, barfeed.Frequency.HOUR / 2)
entrySMA = 200
exitSMA = 5
rsiPeriod = 2
overBoughtThreshold = 90
overSoldThreshold = 10
strat = rsi2.RSI2NoShort(feed, instrument, entrySMA, exitSMA, rsiPeriod, overBoughtThreshold, overSoldThreshold)
run_strat(strat, len(original[-index:]) * barfeed.Frequency.HOUR / 2)
df = pd.read_json('../data/raw/BTC_ETH.json')
df['preds'] = pd.read_hdf('../data/predictions/technical_indicators1.h5', key='predictions')
df = df.dropna().reset_index()
class Predictor(strategy.BacktestingStrategy):
def __init__(self, feed, instrument, enter_threashold=0.5, exit_threashold=0.5):
super(Predictor, self).__init__(feed)
self.getBroker().getFillStrategy().setVolumeLimit(None)
self.__instrument = instrument
self.__position = None
self.enter_threashold = enter_threashold
self.exit_threashold = exit_threashold
self.iter = 0
def onEnterCanceled(self, position):
self.__position = None
def onExitOk(self, position):
self.__position = None
def onExitCanceled(self, position):
# If the exit was canceled, re-submit it.
self.__position.exitMarket()
def onBars(self, bars):
bar = bars[self.__instrument]
if self.__position is not None:
if df.preds[self.iter] < self.exit_threashold:
self.__position.exitMarket()
else:
if df.preds[self.iter] > self.enter_threashold:
shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice())
self.__position = self.enterLong(self.__instrument, shares, True)
self.iter += 1
df['log_diff'] = pd.Series(np.log(df.close)).diff().shift(-1)
metrics.accuracy_score(df.log_diff > 0, df.preds > 0.5)
Funnyly enough, you can predict most of the models predictions by predicting the opposite of what just happened.
metrics.accuracy_score(df.log_diff.shift(1) < 0, df.preds > 0.5)
instrument = 'BTC_ETH'
feed = DataFrameBarFeed(original, instrument, barfeed.Frequency.HOUR / 2)
strat = Predictor(feed, instrument)
run_strat(strat)
instrument = 'BTC_ETH'
feed = DataFrameBarFeed(original, instrument, barfeed.Frequency.HOUR / 2)
strat = Predictor(feed, instrument, enter_threashold=0.6, exit_threashold=0.55)
run_strat(strat)
class Quasitrivial(strategy.BacktestingStrategy):
def __init__(self, feed, instrument):
super(Quasitrivial, self).__init__(feed)
self.getBroker().getFillStrategy().setVolumeLimit(None)
self.__instrument = instrument
self.__position = None
self.__priceDS = feed[instrument].getPriceDataSeries()
def onEnterCanceled(self, position):
self.__position = None
def onExitOk(self, position):
self.__position = None
def onExitCanceled(self, position):
# If the exit was canceled, re-submit it.
self.__position.exitMarket()
def onBars(self, bars):
bar = bars[self.__instrument]
if len(self.__priceDS) > 2:
if self.__position is not None:
if self.__priceDS[-1] > self.__priceDS[-2]:
self.__position.exitMarket()
else:
if self.__priceDS[-1] < self.__priceDS[-2]:
shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice())
self.__position = self.enterLong(self.__instrument, shares, True)
instrument = 'BTC_ETH'
feed = DataFrameBarFeed(original, instrument, barfeed.Frequency.HOUR / 2)
strat = Quasitrivial(feed, instrument)
run_strat(strat)
df = pd.read_json('../data/raw/BTC_ETH.json')
df['preds'] = pd.read_hdf('../data/predictions/regression_indicators1.h5', key='predictions')
df = df.dropna().reset_index()
feed = DataFrameBarFeed(df, instrument, barfeed.Frequency.HOUR / 2)
strat = Predictor(feed, instrument, enter_threashold=0, exit_threashold=0)
run_strat(strat)