In the first article of this series, I described my plans for creating a Poloniex Trading Bot and as promised, I'm back with some updates.
I've made some progress and created a Python class that will allow for backtesting of strategies.
My aim here is just to explain what I've done and give a couple of examples of how to use the class.
The code has already been uploaded to GitHub and should be reasonably straightforward to get running on any system with Python 3 installed.
BackTest
class SMACrossoverBackTest(BackTest):
def addindicators(self, **kwargs):
self.fastma = kwargs["fastma"]
self.slowma = kwargs["slowma"]
self.data["fastma"] = self.data["close"].rolling(self.fastma).mean()
self.data["slowma"] = self.data["close"].rolling(self.slowma).mean()
def dostep(self):
if self.step > self.slowma:
prevfastma = self.data.ix[self.step - 1, "fastma"]
prevslowma = self.data.ix[self.step - 1, "slowma"]
price = self.data.ix[self.step, "close"]
fastma = self.data.ix[self.step, "fastma"]
slowma = self.data.ix[self.step, "slowma"]
if fastma > slowma and prevfastma < prevslowma:
self.buy(price)
elif fastma < slowma and prevfastma > prevslowma:
self.sell(price)
The BackTest class should be used by creating your own instance of the class and overriding the addindicators and dostep methods.
The __init__ method of the class requires a pandas dataframe with your chart data and accepts several keyword values. Default values are set so these keywords are optional.
def __init__(self, data, tradepct=10, btcbalance=0.01, coinbalance=0.0,
buyfee=0.25, sellfee=0.15, candlewidth=5, **kwargs):
- tradepct: percentage of btcbalance on each trade
- btcbalance: starting BTC balance for backtest
- coinbalance: starting balance for the cryptocurrency we're testing
- buyfee: Poloniex fee for a buy trade in percent
- sellfee: Poloniex fee for sell trade in percent
- candlewidth: optionally resample chartdata, value in minutes, ie to use 1 hour charts use 60
Any additional keyword arguments are then passed to the addindicators method. In the above example we might pass
fastma=6, slowma=29
In the addindicators method we store our values then create a new column in our dataframe. In this example we're taking a rolling mean of the "close" data with the length of the rolling windows being the values we passed.
(Open, High, Low, Close, Volume, Quote Volume and Weighted Average are already in the dataframe.)
In the dostep method we define what we want to do on each step of the backtest.
The first thing we check for is that enough time steps have passed so that we actually have a values for the both moving averages.
Next, we read our MA values from the previous step and this step, along with the current price.
If the fast MA is above the slow MA and it wasn't on the step prior, then it's just crossed over so we call the buy method.
Similarly, if the fast MA is below the slow MA and it wasn't before, sell.
Testing Testing
Here's how to run a backtest on "XMR" with a fast moving average of 5 and a slow moving average of 25.
polo = Poloniex()
portfolio = Portfolio(["XMR"])
test = SMACrossoverBackTest(portfolio.chartdata["XMR"], fastma=5, slowma=25)
initialvalue, finalvalue, profit = test.runtest()
print("Start Value: {0:.8f}BTC, Final Value: {1:.8f}BTC, Profit {2:.2f}%".\
format(initialvalue, finalvalue, profit))
This runs for a few seconds then prints out
Downloading: XMR
Start Value: 0.01000000BTC, Final Value: 0.00852960BTC, Profit -14.70%
Not great, let's try some higher values for the moving averages, say 25 and 100.
Loading: XMR OK.
Start Value: 0.01000000BTC, Final Value: 0.00956564BTC, Profit -4.34%
A bit better. Since we now have the ability, we can try all sorts of different values for the moving averages or try Exponential MA instead of simple (EMA Crossover code is included in last update). What about adding another moving average and looking for multiple crossovers?
The options are pretty much unlimited as to what can be added to the strategy. A lot of the indicators might be quite complicated to code however, so to make things a bit easier, I'll take @veleje's advice and add TA-Lib to the mix. (soon)
What's Next?
I think it's about time we got some visualisation added to the code. It'll be much easier to see how each strategy is acting on a pretty graph. Likely going down the TKinter + Matplotlib route for the GUI.
Once I've got a GUI running, I'll add TA-Lib and it will be well on it's way to being an actual application rather than just a bunch of code. hehehe.
As always, don't be shy with any suggestions, advice, criticism or questions, and please feel free to download the code and try it out yourself.
Have another day. :-)
Moving averages crossovers only happen after the trend as already begin to change, so you'll lose most part of the movement. Thats why also they give false signals. The price breaking a slower moving average like 50 or 100 is much more interesting. And still not ideal, you need to combine 2 studies. The ability of backtesting all this ideas with a programme is just so goooood. Studies don't work the same for each instrument, backtesting is ideal and usually alot of work. I might open a polionex account to try this out in the future :)
Nice and easy to test. Minimal change to existing code.
class PriceCrossSMABackTest(BackTest): def addindicators(self, **kwargs): self.ma = kwargs["ma"] self.data["ma"] = self.data["close"].rolling(self.ma).mean() def dostep(self): if self.step > self.ma: prevprice = self.data.ix[self.step-1, "close"] price = self.data.ix[self.step, "close"] prevma = self.data.ix[self.step-1, "ma"] ma = self.data.ix[self.step, "ma"] if price > ma and prevprice < prevma: self.buy(price) elif price < ma and prevprice > prevma: self.sell(price)
Really need to get a GUI going so I can visualise what the strategy is actually doing.
Later bud. :-)
Oh, need to change the call as well.
test = PriceCrossSMABackTest(portfolio.chartdata["ETH"], ma=50)
Geez, do you think in code?
I wish. :-)