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
先扯点闲篇儿,直取干货者,可以点击这里. 我曾误打误撞的搞过一年多的量化交易,期间尝试过做价格和涨跌的预测,当时全凭一腔热血,拿到行情数据就迫不及待地开始测试各种算法. 最基本的算法是技术指标类型的, ...
随机推荐
- 04、数据绑定控件 ListBox 的一个 Bug
同事这两天在做 universal 项目的时候,遇到一个诡异的问题,即使设置 Page 为 缓存状态, 在页面跳转后, ListBox 的位置不会被缓存,怀疑是页面的缓存状态出了问题: this.Na ...
- MySql Trace
MySql Trace 2015-09-25 目录 1 版本简要2 在线设置 2.1 打开命令列界面 2.2 设置参数3 查看结果 在MsSQL Server中我们要追踪SQL,只需要开启MS S ...
- [boostrap]debian下为arm创建debian和emdebian文件系统
转自:http://www.cnblogs.com/qiaoqiao2003/p/3738552.html Debian系统本身包含对arm的支持,其包含的软件包最多,但是最终的文件系统要大一些. e ...
- linux 链接的使用 创建和删除符号连接(软、硬链接)
1 . 使用方式 :ln [option] source_file dist_file (source_file是待建立链接文件的文件,dist_file是新创建的链接文件) ...
- Cocos2d-x 3.1.1 学习日志7--7分钟让你了解cocos2d-x3.1.1 Sprite精灵类
精灵(Sprite)是游戏里面的角色,比方敌人.游戏里面运动的物体等等,所以精灵是游戏里面一个很常见的概念.差点儿无处不在. 在Cocos2D-x里面精灵是用Sprite类来进行表示的,它能够用一张图 ...
- jQuery中ajax的使用与缓存问题的解决方法
http://www.jb51.net/article/44620.htm —————————————————————————————————————————————————————————————— ...
- 009Maven_建立私服——报错问题
前一篇文章的建立私服一直出问题,这里的问题是: jdk6.0只支持nuxus2.5及以下的版本,要支持nexus2.6以上,必须要jdk7.0以上.不然报错,把nexus-2.6.2war包放在Tom ...
- linux文件目录连接
linux系统下提供ln指令来进行文件链接.文件链接主要分为硬链接和软链接. 硬链接:由于linux下的文件是通过索引节点(Inode)来识别文件,硬链接可以认为是一个指针,指向文件索引节点的指针,系 ...
- 【ML】有偏样本解决方案
占个位置,得空写文章. From:learning-from-imbalanced-data
- 查看linux系统外网ip命令
终端中输入 curl ipinfo.io 或者 curl ifconfig.me 即可通过IP地址检测网站提供的api获得取本机的外网IP,或者以 JSON 格式返回全部结果.