Building Trading Strategies on SmartTrade with Custom Functions

In the previous article, we illustrated the steps on how to build a trading strategy on SmartTrade from scratch. If you have not read it yet, please [click here](http://qiita.com/vinyip918/items/8623c5e902b113becd38). Below assumes you already know the basics on how to build and run a strategy on SmartTrade. 

Last time, we used the MACD and Stochastic technical indicators from TA-Lib to build a strategy. Today, we will build our own custom technical functions that are not available from TA-Lib for constructing the trade signals. Specifically, we will define two custom functions as follow:

```py
    #On-balance Volume  
    def OBV(c, v, n):  
      i = 0  
      
      OBV = [0]  
      while i < len(c)-1:  
          if c[i+1]- c[i] > 0:  
              OBV.append(v[i+1])  
          if c[i+1]- c[i] == 0:  
              OBV.append(0)  
          if c[i+1]- c[i] < 0:  
              OBV.append(-v[i+1])  
          i = i + 1  
      OBV = pd.Series(OBV)  
      OBV_ma = pd.Series(pd.rolling_mean(OBV, n), name = 'OBV_' + str(n))  
      
      return OBV_ma.values.astype(np.double)

    #Money Flow Index and Ratio  
    def MFI(c, h, l, v, n):  
      PP = (c + l +  h) / 3  
      i = 0  
      PosMF = [0]  
      while i < len(c)-1:  
          if PP[i + 1] > PP[i]:  
              PosMF.append(PP[i + 1] * v[i+1])  
          else:  
              PosMF.append(0)  
          i = i + 1  
      PosMF = pd.Series(PosMF)  
      TotMF = PP * v
      MFR = pd.Series(PosMF / TotMF)  
      MFI = pd.Series(pd.rolling_mean(MFR, n), name = 'MFI_' + str(n))  
      return MFI.values.astype(np.double)
```

Whereas MACD and Stochastic are only based on price data, On-Balance Volume and Money Flow Index are based on trading volume. The thesis is that we can detect inflows and outflows from a stock based on characteristics and trends from the trading volume. Below is more detailed definition of On-Balance Volume and Money Flow index courtesy of Investopedia and example via SmartTrade:

###On Balance Volume
>On-balance volume (OBV) is a momentum indicator that uses volume flow to predict changes in stock price. Joseph Granville developed the OBV metric in the 1960s. He believed that, when volume increases sharply without a significant change in the stock's price, the price will eventually jump upward, and vice versa.

>The theory behind OBV is based on the distinction between smart money – namely, institutional investors – and less sophisticated retail investors. As mutual funds and pension funds begin to buy into an issue that retail investors are selling, volume may increase even as the price remains relatively level. Eventually, volume drives the price upward. At that point, larger investors begin to sell, and smaller investors begin buying.

>http://www.investopedia.com/terms/o/onbalancevolume.asp#ixzz4m1A94zPN 

![obv.png](https://qiita-image-store.s3.amazonaws.com/0/189232/34079e4e-f4cf-ee0f-adad-ef76ae3c97f0.png)


###Money Flow Index
>The money flow index (MFI) is a momentum indicator that measures the inflow and outflow of money into a security over a specific period of time. The MFI uses a stock's price and volume to measure trading pressure. Because the MFI adds trading volume to the relative strength index (RSI), it's sometimes referred to as volume-weighted RSI.

>The value of the MFI is always between 0 and 100, and calculating it requires several steps. The developers of the MFI, Gene Quong and Avrum Soudack, suggest using a 14-day period for calculations. Step one is to calculate the typical price. Second, the raw money flow is calculated. The third step is to calculate the money flow ratio using the positive and negative money flows for the previous 14 days. Finally, using the money flow ratio, the MFI is calculated. Formulas for each of these items are as follows:
Typical price = (high price + low price + closing price) / 3

>Raw money flow = typical price x volume

>Money flow ratio = (14-day Positive Money Flow) / (14-day Negative Money Flow)

>(Positive money flow is calculated by summing up all of the money flow on the days in the period where the typical price is higher than the previous period typical price. This same logic applies for the negative money flow.)

>MFI = 100 - 100 / (1 + money flow ratio)

>Many traders watch for opportunities that arise when the MFI moves in the opposite direction as the price. This divergence can often be a leading indicator of a change in the current trend. An MFI of over 80 suggests the security is overbought, while a value lower than 20 suggest the security is oversold.

>http://www.investopedia.com/terms/m/mfi.asp#ixzz4m1AeQD8A 

![mfi.png](https://qiita-image-store.s3.amazonaws.com/0/189232/3b776625-91c1-a033-8946-60faeccb3142.png)


Using the above 2 indicators on SmartTrade, we prepare the data and construct the signal as follow:

```py
        high = data["high_price_adj"].fillna(method='pad')
        low = data["low_price_adj"].fillna(method='pad')
        close = data["close_price_adj"].fillna(method='pad')
        volume = data["volume_adj"].fillna(method='pad')
        obv = pd.DataFrame(data=0,columns=close.columns, index=close.index)
        mfi = pd.DataFrame(data=0,columns=close.columns, index=close.index)
        result = pd.DataFrame(data=0,columns=close.columns, index=close.index)
        result2 = pd.DataFrame(data=0,columns=close.columns, index=close.index)
      
      
        for (sym,val) in close.items():
          obv[sym] = OBV( val.values.astype(np.double), volume[sym].values.astype(np.double), 50)
          mfi[sym] = MFI( val.values.astype(np.double), high[sym].values.astype(np.double), low[sym].values.astype(np.double), volume[sym].values.astype(np.double), 50)
        
          test = np.sign( obv[sym].values[1:] - obv[sym].values[:-1] )
          n = len(test)
  
          for i, row in enumerate(test):
            result[sym][i+1] = 0 if isNaN(test[i]) else (test[i] + ( 0 if test[i] != test[i-1] else result[sym][i] ) )
        
          test = np.sign( mfi[sym].values[1:] - mfi[sym].values[:-1] )
          n = len(test)
  
          for i, row in enumerate(test):
            result2[sym][i+1] = 0 if isNaN(test[i]) else (test[i] + ( 0 if test[i] != test[i-1] else result2[sym][i] ) )
        
        
        buy_sig = result[ ( (result>0) & (result<=5) ) & ( (result2>0) & (result2<=5) )     ]
        sell_sig = result[  ( (result<0) & (result>=-1) ) | ( (result2<0) & (result2>=-1) ) ]
        
```

In brief, there are 2 conditions for the signals to go long. 1) if OBV starts trending higher for less than 5 days, and 2) if MFI starts trending higher for less than 5 days. The reason for the time period restriction is because we only want to trigger a buy signal in the initial trend change. On the other hand, we exit the buy signal if the trend of OBV or MFI begins to turn down.

Running the strategy on SmartTrade on 50 random stocks yields the following results since 2013 with a 91% gain:

![result.png](https://qiita-image-store.s3.amazonaws.com/0/189232/4a0be6aa-8e87-9b93-edda-cd5e9465fb72.png)

For the full code, please see below:

https://beta.smarttrade.co.jp/demo/6e3ca9838a5a4f9698b343de4eb5deeb