What is consensus?

The blockchain is a decentralized, distributed, peer-to-peer ledger system. All nodes or peers on the network have to maintain the same ledger, thus any new transactions that are added to the ledger only can be done so once they are verified via the “consensus mechanism”.

The consensus mechanism is a dynamic way of achieving agreement across all network nodes to ensure the validity and security of the distributed ledger. The larger the size of the network, the more challenging it becomes to achieve consensus and verify which data or transactions being added to the ledger are valid. This problem is called the “Byzantine Generals’ Problem”.

6 minute read…

What is the scalability trilemma?

The scalability trilemma is a phrase coined by Vitalik Buterin (founder of Ethereum) to describe that it is not possible to equally maximize the three desirable attributes that are decentralization, scalability, and security. The trilemma claims that blockchain systems can maximize two at the expense of the third attribute. Therefore, all blockchain systems require trade-offs depending on the specific application and use case of the blockchain system.

3 minute read…

What are smart contracts?

Smart contracts are similar to the standard offline contracts except that they are implemented digitally as a software program on the distributed, decentralized blockchain. They allow agreements and transactions to be executed between anonymous parties without the need for 3rd parties such as a central authority or external enforcement mechanism. As the smart contract is computer code, once the parties within the contract have fulfilled the stipulations of the contract, all actions pertaining to the agreement or transaction are immediately executed. All actions associated with the smart contract are trackalable and irreversible on the blockchain.

4 minute read…

What's the difference between coins and tokens?

Coins are digital currency that can be used to pay for goods and services. Tokens are programmable assets that are managed through a smart contract and a distributed ledger. Tokens are classified as either Security Tokens or Utility Tokens and are sold (issued) to raise capital for companies. Security tokens are created as investments and are analogous to owning shares in a company as token holders receive dividends and have voting rights with the issuing company. Utility tokens are not created as investments and are analogous to digital coupons that only provide you with access to the product or service being developed by the issuing company.

4 minute read…

Informed trading and market microstructure

Informed trading is an interesting research topic in the world of market micro-structure. My first foray into this area was when Prof. Maureen O'hara who was at the time chairman of Information Technology Group (ITG) and a Professor at Cornell University's Samuel Curtis Johnson Graduate School of Management. Maureen was also the first president of the American Finance Association (AFA). Pretty pretigious stuff.

Maureen was presenting her research at University of Queensland and was talking about her interactions with another famous industry quant, that was Marcos Le Prado about her paper on the VPIN Flow Toxicity metric, which basically is an indicator for informed trading. Apparently, Marcos out-of-the-blue contacted Maureen thanking her for her research paper and the VPIN model as it allowed him to get out of the market before the May 2010 Flash Crash. Initially, Maureen thought that Marcos was a bit of a nutter, but after delving a little deeper into his background she realized he was a bonafide quant and actually did use her model as a predictive indicator to avoid the Flash Crash. At that point, they published a paper and patented the VPIN metric. However, controversially, there came a series of papers that refuted their findings about whether the model was able to predict the flash crash.

Exposure to this work led me to initiate my own investigations on BV-VPIN at lower frequencies (i.e., daily) across multiple countries. This work can be found in the Publication and the paper is entitled BV–VPIN: Measuring the impact of order flow toxicity and liquidity on international equity markets. So I thought I'd write a short article on order flows, and market microstructure.

5 minute read…

Dai Stablecoin

In one of the articles I wrote in late 2017 was on Bitcoin and comparing it to Tulip-mania or digital currency for the new generation.

This article was initiated by UQBS marketing on requesting for me to write about the Top x3 trends for 2018. One of those trends I wrote on was in Bitcoin; however, by the time the Tulip-Mania article was written and accepted in the Studies in Economics & Finance it was already the end of 2018, in which some of the comments that I made in late 2017 about crypto-assets and Bitcoin were recognized and addressed especially around the difficulty in using Bitcoin as a form of exchange/currency when it was so highly volatile. One of the cryptoassets that have arisen to address this issue is Stable Coin.

Thus, I will discuss the Dai Stable coin.

Dai promises that is is a completely decentralized Stablecoin. Dai is linked to Ethereum. Before we delve into Dai, let's have a quick summary of Ethereum.

Note

Ethereum is most commonly known as Bitcoin's competitor but it is different in several respects. Both Ethereum and Bitcoin are based on blockchain technology, but applied differently as follows:

  • Decentralized platform running smart contracts. Whereas Bitcoin is more about being a digital currency, Ethereum is really about being a decentralized platform. Using Ethereum, one can create Distributed Applications (dApps) and Smart Contracts. As Ethereum is a platform, it can host multiple applications so therefore the use-casesendless in that it can have its own digital currency (i.e., Ether), games (i.e., Cryptokitties) and so on. It is said there are as many as 200-300 dApps sitting on Ethereum. Essentially Ethereum is a decentralized platform that runs smart contracts that are applications that run precisely as programmed without any possibility of fraud, censorship, downtime, or 3rd-party interference.

  • Proof of Stake. Whereas Bitcoin mining is based on Proof of Work (PoW), Ethereum uses Proof of Stake (PoS). Validating a new block in the consensus algorithm is based on the stake that the miner holds (i.e., number of Ether coins). Block validators do not receive a block reward, but receive network fees called Gas in Ethereum. As Ethereum uses PoS, the time to validate a block is much faster (i.e., 25s) compared to Bitcoin (i.e., 8 mins).

So Dai is Stablecoin that is written as part of Maker that is a Smart Contract on the Ethereum platform. Maker consists of Dai, collateral loans, and a system of community governance.

Overview of backtrader (Intermediate)

The previous posts went through a quick overview of the backtrader platform so that one could see the speed and simplicity of using backtrader to execute your trading strategies.

This post goes through the structure of the backtrader platform

  1. Data Feeds. The data for the trading strategy needs to be loaded into the backtester needs to be loaded in.
  2. Trading strategy. The trading strategy is the most complex and involved part of the programming process since this is where decisions are made to buy/sell the asset.
  3. Backtester settings. Cerebro is the name of the backtester. Cerebro requires several settings such as (i) Trading capital (ii) Broker Comissions (iii) Datafeed (iv) Trading strategy (v) Size of each trading position.
  4. Running backtest. Run the Cerebro backtester and print out all trades executed.
  5. Evaluating performance. Graphically evaluate the performance of the backtested trading strategy.

Import modules

In [1]:
import datetime
import os.path
import sys
import backtrader as bt
import matplotlib.pyplot as plt

Data

In [2]:
homepath = os.getenv('HOME')
datapath = os.path.join(homepath, 'github/backtrader/datas/orcl-1995-2014.txt')

data = bt.feeds.YahooFinanceCSVData(
    dataname=datapath,
    fromdate=datetime.datetime(2000,1,1),
    todate = datetime.datetime(2000,12,31),
    reverse=False)

Strategy Class

The __init__ function that initialies the strategy class will contain all the attributes (e.g., indicators - SMA, RSI, SMA, price data - volume, open price, close price) and the variabl

The next function is where the trading strategy is written based on the attributes

In [3]:
class Strat2_BGTMA_SLSMA(bt.Strategy):
    
    params = (
        ('maperiod',15), # Tuple of tuples containing any variable settings required by the strategy.
        ('printlog',False), # Stop printing the log of the trading strategy
        
    )
    
    def __init__(self):
        self.dataclose= self.datas[0].close    # Keep a reference to the "close" line in the data[0] dataseries
        self.order = None # Property to keep track of pending orders.  There are no orders when the strategy is initialized.
        self.buyprice = None
        self.buycomm = None
        
        # Add SimpleMovingAverage indicator for use in the trading strategy
        self.sma = bt.indicators.SimpleMovingAverage( 
            self.datas[0], period=self.params.maperiod)
    
    def log(self, txt, dt=None, doprint=False):
        if self.params.printlog or doprint: # Add if statement to only log of printlog or doprint is True
            dt = dt or self.datas[0].datetime.date(0)
            print('{0},{1}'.format(dt.isoformat(),txt))
    
    def notify_order(self, order):
        # 1. If order is submitted/accepted, do nothing 
        if order.status in [order.Submitted, order.Accepted]:
            return
        # 2. If order is buy/sell executed, report price executed
        if order.status in [order.Completed]: 
            if order.isbuy():
                self.log('BUY EXECUTED, Price: {0:8.2f}, Size: {1:8.2f} Cost: {2:8.2f}, Comm: {3:8.2f}'.format(
                    order.executed.price,
                    order.executed.size,
                    order.executed.value,
                    order.executed.comm))
                
                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
            else:
                self.log('SELL EXECUTED, {0:8.2f}, Size: {1:8.2f} Cost: {2:8.2f}, Comm{3:8.2f}'.format(
                    order.executed.price, 
                    order.executed.size, 
                    order.executed.value,
                    order.executed.comm))
            
            self.bar_executed = len(self) #when was trade executed
        # 3. If order is canceled/margin/rejected, report order canceled
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('Order Canceled/Margin/Rejected')
            
        self.order = None
    
    def notify_trade(self,trade):
        if not trade.isclosed:
            return
        
        self.log('OPERATION PROFIT, GROSS {0:8.2f}, NET {1:8.2f}'.format(
            trade.pnl, trade.pnlcomm))
    
    def next(self):
        # Log the closing prices of the series from the reference
        self.log('Close, {0:8.2f}'.format(self.dataclose[0]))

        if self.order: # check if order is pending, if so, then break out
            return
                
        # since there is no order pending, are we in the market?    
        if not self.position: # not in the market
            if self.dataclose[0] > self.sma[0]:
                self.log('BUY CREATE {0:8.2f}'.format(self.dataclose[0]))
                self.order = self.buy()           
        else: # in the market
            if self.dataclose[0] < self.sma[0]:
                self.log('SELL CREATE, {0:8.2f}'.format(self.dataclose[0]))
                self.order = self.sell()
                
    def stop(self):
        self.log('MA Period: {0:8.2f} Ending Value: {1:8.2f}'.format(
            self.params.maperiod, 
            self.broker.getvalue()),
                 doprint=True)

Backtest settings

Rather than using cerebro.addstrategy(), we now use cerebro.optstrategy() as we are optimizing the strategy over a variety of parameters in our SMA indicator. In this case we are testing all the maperiod from 10, 11, 12, ..., 30, 31 bars.

In [4]:
cerebro = bt.Cerebro()  
cerebro.adddata(data) 
strats = cerebro.optstrategy(
    Strat2_BGTMA_SLSMA,
    maperiod=range(10,31),
    printlog=False)
cerebro.addsizer(bt.sizers.FixedSize,stake=10)
cerebro.broker.setcash(1000.0) 
cerebro.broker.setcommission(commission=0.0) 

Run backtest

The maxcpus allows us to trigger how many cpus to run in parallel when performing the optimization. We find that the SMA strategy is profitable when we use values between 18 to 25 days.

In [5]:
cerebro.run(maxcpus=1)
2000-12-29,MA Period:    10.00 Ending Value:   877.50
2000-12-29,MA Period:    11.00 Ending Value:   878.70
2000-12-29,MA Period:    12.00 Ending Value:   839.80
2000-12-29,MA Period:    13.00 Ending Value:   899.90
2000-12-29,MA Period:    14.00 Ending Value:   902.50
2000-12-29,MA Period:    15.00 Ending Value:   975.60
2000-12-29,MA Period:    16.00 Ending Value:   961.90
2000-12-29,MA Period:    17.00 Ending Value:   952.60
2000-12-29,MA Period:    18.00 Ending Value:  1011.00
2000-12-29,MA Period:    19.00 Ending Value:  1039.40
2000-12-29,MA Period:    20.00 Ending Value:  1073.20
2000-12-29,MA Period:    21.00 Ending Value:  1055.10
2000-12-29,MA Period:    22.00 Ending Value:  1057.60
2000-12-29,MA Period:    23.00 Ending Value:  1021.50
2000-12-29,MA Period:    24.00 Ending Value:  1018.80
2000-12-29,MA Period:    25.00 Ending Value:  1012.40
2000-12-29,MA Period:    26.00 Ending Value:   998.30
2000-12-29,MA Period:    27.00 Ending Value:   983.10
2000-12-29,MA Period:    28.00 Ending Value:   976.90
2000-12-29,MA Period:    29.00 Ending Value:   984.20
2000-12-29,MA Period:    30.00 Ending Value:   980.80
Out[5]:
[[<backtrader.cerebro.OptReturn at 0x7a4ee2c14b38>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2bd70f0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2bd19b0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2c142b0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b79a58>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2ba94a8>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b52518>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2af5c88>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b19710>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b011d0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b691d0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b832b0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b79e10>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2c5d780>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2c00898>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2bbb860>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2ad08d0>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2adff60>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b5f198>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2ae7cf8>],
 [<backtrader.cerebro.OptReturn at 0x7a4ee2b444e0>]]

Conclusion

In this post, we learnt how to optimize parameters in our trading strategy.

  1. We learnt how to use cerebro.optstrategy where we indicate which parameter we want to optimize and the range of parameters to evaluate the strategy's performance.
  2. We altered the log function of the Strategy class to only print the values of the parameter being optimized and its corresponding portfolio value.
In [ ]:
 

Optimizing parameters of the trading strategy

In this post, we show how to optimize variables in your strategy. In our previous post on introducing indicators in to the backtester, we found that moving average over 15 days led to the a losing trading strategy. Thus, we might want to evalute which is the best period (i.e., 5, 10, 15, 20) to use to ensure that our trading strategy is profitable. As a rule, it is best not to over optimize your trading strategy as it will not generalize well in an out-of-sample test.

  1. We set printlog to False in the Strategy as we do not want a print of trade in each backtest that is applied to each parameter that is being optimized.
  2. We add an if statement to the log function in the Strategy class that only logs data if the settings are set to True.
  3. We use cerebro.optstrategy() instead of cerebro.addstrategy(). We optimize the strategy over a range of MA periods from 10 to 31.

The code additions here are in the following cells:

  1. Strategy Class
  2. Backtest Settings

Import modules

In [7]:
import datetime
import os.path
import sys
import backtrader as bt
import matplotlib.pyplot as plt

Data

In [8]:
homepath = os.getenv('HOME')
datapath = os.path.join(homepath, 'github/backtrader/datas/orcl-1995-2014.txt')

data = bt.feeds.YahooFinanceCSVData(
    dataname=datapath,
    fromdate=datetime.datetime(2000,1,1),
    todate = datetime.datetime(2000,12,31),
    reverse=False)

Strategy Class

In params, set the printlog to False. This is because when we are optimizing over different parameters, we don't want to see all the trades that are executed each time a different backtest is applied to each parameter. We just want to see what the Final value of the portfolio is when each parameter is applied in the backtest.

In the log function, we add an additional if statement to only log data if self.params.printlog or doprint are True.

We add a stop function, that prints out the MA period that is being backtested and the corresponding portfolio value.

In [9]:
class Strat2_BGTMA_SLSMA(bt.Strategy):
    
    params = (
        ('maperiod',15), # Tuple of tuples containing any variable settings required by the strategy.
        ('printlog',False), # Stop printing the log of the trading strategy
        
    )
    
    def __init__(self):
        self.dataclose= self.datas[0].close    # Keep a reference to the "close" line in the data[0] dataseries
        self.order = None # Property to keep track of pending orders.  There are no orders when the strategy is initialized.
        self.buyprice = None
        self.buycomm = None
        
        # Add SimpleMovingAverage indicator for use in the trading strategy
        self.sma = bt.indicators.SimpleMovingAverage( 
            self.datas[0], period=self.params.maperiod)
    
    def log(self, txt, dt=None, doprint=False):
        if self.params.printlog or doprint: # Add if statement to only log of printlog or doprint is True
            dt = dt or self.datas[0].datetime.date(0)
            print('{0},{1}'.format(dt.isoformat(),txt))
    
    def notify_order(self, order):
        # 1. If order is submitted/accepted, do nothing 
        if order.status in [order.Submitted, order.Accepted]:
            return
        # 2. If order is buy/sell executed, report price executed
        if order.status in [order.Completed]: 
            if order.isbuy():
                self.log('BUY EXECUTED, Price: {0:8.2f}, Size: {1:8.2f} Cost: {2:8.2f}, Comm: {3:8.2f}'.format(
                    order.executed.price,
                    order.executed.size,
                    order.executed.value,
                    order.executed.comm))
                
                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
            else:
                self.log('SELL EXECUTED, {0:8.2f}, Size: {1:8.2f} Cost: {2:8.2f}, Comm{3:8.2f}'.format(
                    order.executed.price, 
                    order.executed.size, 
                    order.executed.value,
                    order.executed.comm))
            
            self.bar_executed = len(self) #when was trade executed
        # 3. If order is canceled/margin/rejected, report order canceled
        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('Order Canceled/Margin/Rejected')
            
        self.order = None
    
    def notify_trade(self,trade):
        if not trade.isclosed:
            return
        
        self.log('OPERATION PROFIT, GROSS {0:8.2f}, NET {1:8.2f}'.format(
            trade.pnl, trade.pnlcomm))
    
    def next(self):
        # Log the closing prices of the series from the reference
        self.log('Close, {0:8.2f}'.format(self.dataclose[0]))

        if self.order: # check if order is pending, if so, then break out
            return
                
        # since there is no order pending, are we in the market?    
        if not self.position: # not in the market
            if self.dataclose[0] > self.sma[0]:
                self.log('BUY CREATE {0:8.2f}'.format(self.dataclose[0]))
                self.order = self.buy()           
        else: # in the market
            if self.dataclose[0] < self.sma[0]:
                self.log('SELL CREATE, {0:8.2f}'.format(self.dataclose[0]))
                self.order = self.sell()
                
    def stop(self):
        self.log('MA Period: {0:8.2f} Ending Value: {1:8.2f}'.format(
            self.params.maperiod, 
            self.broker.getvalue()),
                 doprint=True)

Backtest settings

Rather than using cerebro.addstrategy(), we now use cerebro.optstrategy() as we are optimizing the strategy over a variety of parameters in our SMA indicator. In this case we are testing all the maperiod from 10, 11, 12, ..., 30, 31 bars.

In [10]:
cerebro = bt.Cerebro()  
cerebro.adddata(data) 
strats = cerebro.optstrategy(
    Strat2_BGTMA_SLSMA,
    maperiod=range(10,31),
    printlog=False)
cerebro.addsizer(bt.sizers.FixedSize,stake=10)
cerebro.broker.setcash(1000.0) 
cerebro.broker.setcommission(commission=0.0) 

Run backtest

The maxcpus allows us to trigger how many cpus to run in parallel when performing the optimization. We find that the SMA strategy is profitable when we use values between 18 to 25 days.

In [11]:
cerebro.run(maxcpus=1)
2000-12-29,MA Period:    10.00 Ending Value:   877.50
2000-12-29,MA Period:    11.00 Ending Value:   878.70
2000-12-29,MA Period:    12.00 Ending Value:   839.80
2000-12-29,MA Period:    13.00 Ending Value:   899.90
2000-12-29,MA Period:    14.00 Ending Value:   902.50
2000-12-29,MA Period:    15.00 Ending Value:   975.60
2000-12-29,MA Period:    16.00 Ending Value:   961.90
2000-12-29,MA Period:    17.00 Ending Value:   952.60
2000-12-29,MA Period:    18.00 Ending Value:  1011.00
2000-12-29,MA Period:    19.00 Ending Value:  1039.40
2000-12-29,MA Period:    20.00 Ending Value:  1073.20
2000-12-29,MA Period:    21.00 Ending Value:  1055.10
2000-12-29,MA Period:    22.00 Ending Value:  1057.60
2000-12-29,MA Period:    23.00 Ending Value:  1021.50
2000-12-29,MA Period:    24.00 Ending Value:  1018.80
2000-12-29,MA Period:    25.00 Ending Value:  1012.40
2000-12-29,MA Period:    26.00 Ending Value:   998.30
2000-12-29,MA Period:    27.00 Ending Value:   983.10
2000-12-29,MA Period:    28.00 Ending Value:   976.90
2000-12-29,MA Period:    29.00 Ending Value:   984.20
2000-12-29,MA Period:    30.00 Ending Value:   980.80
Out[11]:
[[<backtrader.cerebro.OptReturn at 0x7d54a5bb05c0>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5be16d8>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5af7390>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a9f198>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a49940>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a773c8>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a21470>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a10d68>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a70a90>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5ade470>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a9f160>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5b20438>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5b22ef0>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5b60208>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5b59a20>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5be1e80>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a4a6a0>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5ab9d68>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a9df60>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a5f278>],
 [<backtrader.cerebro.OptReturn at 0x7d54a5a65c18>]]

Conclusion

In this post, we learnt how to optimize parameters in our trading strategy.

  1. We learnt how to use cerebro.optstrategy where we indicate which parameter we want to optimize and the range of parameters to evaluate the strategy's performance.
  2. We altered the log function of the Strategy class to only print the values of the parameter being optimized and its corresponding portfolio value.
In [ ]: