BitCoin Trading Strategies BackTest With PyAlgoTrade
Written by Khang Nguyen Vo, khangvo88@gmail.com, for the RobustTechHouse blog. Khang is a graduate from the Masters of Quantitative and Computational Finance Program, John Von Neumann Institute 2014. He is passionate about research in machine learning, predictive modeling and backtesting of trading strategies.
INTRODUCTION
Bitcoin (or BTC) was invented by Japanese Satoshi Nakamoto and considered the first decentralized digital currency or crypto-currency. In this article, we experiment with a simple momentum based trading strategy for Bitcoin using PyAlgoTrade which is a Python Backtesting library. The Moving Average Crossover trading strategy we start with is defined as:
- Enter position:
- Long when MA10 > MA20
- Short when MA10 < MA20
- Exit position:
- reverse trend
- Take profit when we gain $20
- Cut loss when we lose $10
MA10 refers to 10 day moving average price and MA20 refers 20 day moving average price.
DATA
The bitcoin data can be obtained from Bitcoin charts. The raw data of this source is at minute based sampling frequency and we group the data to 15-minutes prices as follows:

BITCOIN TRADING STRATEGY BACKTEST WITH PYALGOTRADE
PyAlgoTrade, as mentioned in previous blog, is an event-driven library. So we must override the basic events onEnterOk and onExitOk, which are raised when orders submitted before are successfully filled.
import Momentum.MyBaseStrategy as bstr #extend from pyalgotrade.BacktestStrategy
from pyalgotrade.technical import ma
from pyalgotrade.barfeed import csvfeed
from pyalgotrade.bar import Frequency
from pyalgotrade import plotter
import numpy as np
import datetime
class MyStrategy(bstr.MyBTStrategy):
def __init__(self, feed, cash,sma):
self.__instrument = 'Bitcoin'
bstr.MyBTStrategy.__init__(self,feed,cash, instrument=self.__instrument)
self.MAXPROFIT = 20; self.STOPLOSS = 10
self.getBroker().setAllowNegativeCash(True)
# using for trading signal
self.__position = None
self.__price = feed[self.__instrument].getCloseDataSeries()
self.__sma10 = ma.SMA(self.__price,sma[0],maxLen=100000)
self.__sma20 = ma.SMA(self.__price,sma[1],maxLen=100000)
self.__lastPrice = 0 #last price. Use for take profit and cutloss
self.__signal = 0 #1: buying, -1: selling, 0: no change
self.__last_exit_time = None
def onEnterOk(self, position):
execInfo = position.getEntryOrder().getExecutionInfo()
self.info("%s %d at VND %s" %(self.alert_message,execInfo.getQuantity(),
ut.accountingFormat(execInfo.getPrice())))
self.__lastPrice = execInfo.getPrice()
self.record_detail_transaction(position)
def onEnterCanceled(self, position):
self.__position = None
def onExitOk(self, position):
execInfo = position.getExitOrder().getExecutionInfo()
self.info("%s %d at %s\n================================="
%(self.alert_message,
execInfo.getQuantity(),'{:11,.2f}'.format(execInfo.getPrice())))
self.__position = None
self.record_detail_transaction(position, False) # log detail for later analysis
# run before onEnterOk and onExitOk
def onOrderUpdated(self,order):
pass
The main process of trading algorithm is in onBars, which is raised every time there is new record of time series. PyAlgoTrade feed the data series and put it in bars, on each time given. This mandatory method is implemented as follows:
. . .
# main event to update trading strategy
def onBars(self, bars):
self.portfolio_values.append(self.getBroker().getEquity())
if self.__sma20[-1] is None:
return
bar = bars[self.__instrument]
if self.__sma10[-1] > self.__sma20[-1]: self.__signal = 1 # buying signal
elif self.__sma10[-1] < self.__sma20[-1]: self.__signal =-1 # selling signal
shares = 1
if(self.__position) is None and self.__sma20[-2] is not None:
# go into long position
if self.__sma10[-1] > self.__sma20[-1] and self.__sma10[-2] <= self.__sma20[-2]:
self.info("short SMA > long SMA. RAISE BUY SIGNAL")
#shares = int(self.getBroker().getCash() * 0.9 / bar.getClose())
self.__position = self.enterLong(self.__instrument,shares,False)
self.alert_message='Long position'
self.buy_signals.append(self.getCurrentDateTime())
#short position
elif self.__sma10[-1] < self.__sma20[-1] and self.__sma10[-2] >= self.__sma20[-2]:
self.info("short SMA < long SMA. RAISE SELL SIGNAL")
self.__position = self.enterShort(self.__instrument,shares,False)
self.alert_message='Short position'
self.sell_signals.append(self.getCurrentDateTime())
elif self.__lastPrice is not None and self.getBroker().getPositions() != {}:
pos = self.getBroker().getPositions()[self.__instrument]
# take profit when we obtain >= $20
if( np.sign(pos)*(bar.getClose() - self.__lastPrice) >= self.MAXPROFIT):
self.alert_message = 'TAKE PROFIT'
self.__position.exitMarket()
self.__lastPrice = None
# cut loss when we lose more than $10
elif (np.sign(pos)*(self.__lastPrice - bar.getClose())) >= self.STOPLOSS:
self.alert_message = 'STOP LOSS'
self.__position.exitMarket()
self.__lastPrice = None
elif pos*self.__signal < 0:
self.alert_message = "Reverse signal. TAKE PROFIT"
self.__position.exitMarket()
self.__lastPrice = None
if self.__signal < 0:
self.sell_signals.append(self.getCurrentDateTime())
else:
self.buy_signals.append(self.getCurrentDateTime())
self.__last_exit_time = self.getCurrentDateTime()
Then the main script as follows:
filename = '../btcUSD15m_2.csv'
# TODO: change the date range
firstDate = datetime.datetime(2014,1,1,0,0,0,0,pytz.utc)
endDate = datetime.datetime(2014,3,31,0,0,0,0,pytz.utc)
feed = csvfeed.GenericBarFeed(15*Frequency.MINUTE,pytz.utc,maxLen=100000)
feed.setBarFilter(csvfeed.DateRangeFilter(firstDate,endDate))
feed.addBarsFromCSV('Bitcoin', filename)
cash = 10 ** 3 # 1,000 USD
myStrategy = MyStrategy(feed,cash,[12,30]) #short and long moving average
plt = plotter.StrategyPlotter(myStrategy, True, False, True)
myStrategy.run()
myStrategy.printTradingPerformance()
The trading transaction detail of this strategy from Jan 2014 to Mar 2014 are as follows:



In this short time window, the Sharpe Ratio is indeed poor and only -1.9. Moreover, there are a total of 200 trades executed in 3 months, and most are unprofitable trades (132/200 trades = 66%). Therefore, we need to reduce the number of unprofitable trades.
TWEAKING BITCOIN TRADING STRATEGY BACKTEST
The problem might be that we are using a very short-length moving average window to calculate the change of trends, so the strategy is very sensitive to changes. Now we try a longer moving average window with MA(80,200) crossover
myStrategy = MyStrategy(feed,cash,[80,200]) #short and long moving average
plt = plotter.StrategyPlotter(myStrategy, True, False, True)
myStrategy.run()
The result of this trading strategy as follows for the same period.

The summary result when running this strategy between 2013-2015

CHARTS IN 2013

CHARTS IN 2014

CHARTS IN 2015

We see that the trading performance is better now. The Sharpe ratio is larger than 0.5, and in 2014, the cumulative returns is as big as 33%. The length of Moving Average could be further optimized (data-mined!).
NOTE ON TRANSACTION COSTS
In real trading, it is mandatory to add commission rates or transaction costs. Usually, the transaction cost can be computed as the difference between ASK price and BID price (BID-ASK SPREAD) if market orders are used to buy or sell. In our data set, the average “bid ask spread” is about 0.11, so we set the cost of each transaction to BTC 0.11.
from pyalgotrade.broker import backtesting feed = createFeed(firstDate, endDate) strat3 = MyStrategy(feed,cash,[80,200]) #short and long moving average strat3.getBroker().setCommission(backtesting.FixedPerTrade(0.11)) #t-cost per trade = $0.11 strat3.run() strat3.printTradingPerformance()
Overall, the strategy is still profitable, though we have to be mindful that because BitCoin history is very short, so the statistical significance of the strategy is inconclusive. Note that we assume there are no broker transaction fees. In reality, usually this fee cost 0.7$ per trade.
BitCoin Trading Strategies BackTest With PyAlgoTrade的更多相关文章
- Basics of Algorithmic Trading: Concepts and Examples
https://www.investopedia.com/articles/active-trading/101014/basics-algorithmic-trading-concepts-and- ...
- Algorithmic Trading[z]
Algorithmic Trading has been a hot topic for equity/derivative trading over a decade. Many ibanks an ...
- Python金融行业必备工具
有些国外的平台.社区.博客如果连接无法打开,那说明可能需要"科学"上网 量化交易平台 国内在线量化平台: BigQuant - 你的人工智能量化平台 - 可以无门槛地使用机器学习. ...
- [转]Introduction to Learning to Trade with Reinforcement Learning
Introduction to Learning to Trade with Reinforcement Learning http://www.wildml.com/2018/02/introduc ...
- Introduction to Learning to Trade with Reinforcement Learning
http://www.wildml.com/2015/12/implementing-a-cnn-for-text-classification-in-tensorflow/ The academic ...
- OnePy--构建属于自己的量化回测框架
本文主要记录我构建量化回测系统的学习历程. 被遗弃的项目:Chandlercjy/OnePy_Old 新更新中的项目:Chandlercjy/OnePy 目录 1. 那究竟应该学习哪种编程语言比较好呢 ...
- Should You Build Your Own Backtester?
By Michael Halls-Moore on August 2nd, 2016 This post relates to a talk I gave in April at QuantCon 2 ...
- An Introduction to Stock Market Data Analysis with R (Part 1)
Around September of 2016 I wrote two articles on using Python for accessing, visualizing, and evalua ...
- 数据集划分——train set, validate set and test set
先扯点闲篇儿,直取干货者,可以点击这里. 我曾误打误撞的搞过一年多的量化交易,期间尝试过做价格和涨跌的预测,当时全凭一腔热血,拿到行情数据就迫不及待地开始测试各种算法. 最基本的算法是技术指标类型的, ...
随机推荐
- Java的==与equals之辨,简单解释,很清楚
"=="和equals方法究竟有什么区别? (单独把一个东西说清楚,然后再说清楚另一个,这样,它们的区别自然就出来了,混在一起说,则很难说清楚) ==操作符专门用来比较两个变量的值 ...
- (转载)C++STL中vector容器的用法
vector是C++标准模板库中的部分内容,它是一个多功能的,能够操作多种数据结构和算法的模板类和函数库.vector之所以被认为是一个容器,是因为它能够像容器一样存放各种类型的对象,简单地说vec ...
- Windows Phone 提升开发效率(一)使用d:DataContext添加设计时Binding
[问题的提出] 在开发过程中我们经常会遇到将UI同学提供的效果图转化成实际的页面,而在这过程中,多数时候Blend等设计工具默认情况下并不能提供很好的可视化支持. 举个简单的例子来说下吧: ...
- 纯css3实现的鼠标悬停动画按钮
今天给大家带来一款纯css3实现的鼠标悬停动画按钮.这款按钮鼠标经过前以正方形的形式,当鼠标经过的时候以动画的形式变成圆形.效果图如下: 在线预览 源码下载 实现的代码. html代码: < ...
- linux 域和xenomai 实时域之间的交互
/* * XDDP-based RT/NRT threads communication demo. * * Real-time Xenomai threads and regular Linux t ...
- spingboot集成jpa(一)
springboot + jpa 练习 spingboot集成jpa(一):最基本的环境搭建 spingboot集成jpa(二):使用单元测试 1. pom.xml中添加依赖 <!-- jdbc ...
- Scala中List(Map1,Map2,Map3 ....) 转成一个Map
这个问题研究好久...头大,不记得有fold用法了. fold函数:折叠,提供一个输入参数作为初始值,然后大括号中应用自定义fun函数并返回值. list.fold(Map()){(x,y)=> ...
- 2018-11-17 js的this引起的血案
js的this. 昨天测试,删除商品会报错,马上去测了一把,的确会报错.为毛线呢? SubOrderItem: function (orderitem) { if (orderitem.ordernu ...
- svn:ignore 的用处
用svn管理代码,一直以来都受到一件不爽事情的困扰: 1)有些文件或文件夹不想在commit的时候看到,虽然他们是non-versioned,比如*.bak.*.class,*.scc(vss文件), ...
- 23SpringMvc_各种参数绑定方式-就是<input那种
本篇博文转载自http://www.cnblogs.com/HD/p/4107674.html: SpringMVC的各种参数绑定方式 1. 基本数据类型(以int为例,其他类似):Controlle ...