zoukankan      html  css  js  c++  java
  • 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 Strategies BackTest With PyAlgoTrade

    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
    =================================" 
                      %(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:

    BitCoin Trading Strategies BackTest With PyAlgoTrade

    BitCoin Trading Strategies BackTest With PyAlgoTrade

    BitCoin Trading Strategies BackTest With PyAlgoTrade

    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.

    BitCoin Trading Strategies BackTest With PyAlgoTrade

    The summary result when running this strategy between 2013-2015

    BitCoin Trading Strategies BackTest With PyAlgoTrade

    CHARTS IN 2013

    BitCoin Trading Strategies BackTest With PyAlgoTrade

    CHARTS IN 2014

    BitCoin Trading Strategies BackTest With PyAlgoTrade

    CHARTS IN 2015

    BitCoin Trading Strategies BackTest With PyAlgoTrade

    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.

  • 相关阅读:
    解决安装vmware-tools出现的“The path "" is not a valid path to the 3.2.0-4-amd64 kernel headers”问题
    页面布局
    CSS属性/尺寸/边框/背景 超级链接
    前端
    索引
    Pymysql
    单表查询,多表查询,子查询
    表的完整性约束
    文件库,文件表,记录的增删改查
    IO多路复用,数据库mysql
  • 原文地址:https://www.cnblogs.com/bitquant/p/8434991.html
Copyright © 2011-2022 走看看