Backtesting Stochastic Oscillator, SMA, and WMA Trading Strategy with Bitcoin

I have seen a video on youtube by Trader Pro that claimed using Stochastic Oscillator, SMA and WMA together would be a very profitable strategy. The video that claimed using 5 minute and hourly data the strategy had a 47% win rate and the returns were 41%. The video is in the references. I wondered how it would fare with bitcoin. I mimicked that strategy and backtested it with Bitcoin historical data. In this article I will show my backtesting results and give the python code so you can do your own analyses if you wanted to. I used Backtesting.py module.

The Strategy

SMA(5) is simple moving average of 5 periods (it is 5 x 5 minutes or 5 x 1 hour), WMA(144) is the weighted moving average for 144 periods (it is 144 x 5 minutes or 144 x 1 hour). STOCHASTIC(14) is the Stochastic Oscillator for 14 periods, it returns Slow K and Slow D values.

Buy when:

  • One hour SMA(5) > WMA(144) and
  • Five minute SMA(5) > WMA(144) and
  • STOCHASTIC(14) < 20 and
  • Slow K is greater than Slow D.

Sell when: this part was not clear. It was rather subjective, you had to pick the prior low close and twice that amount would be your profit target. So I picked the following exit strategy:

  • Use a fixed profit and stop loss. (1% stop loss twice that, %2, profit target, we can also optimize these numbers.)

Results and Interpretation

I took the last two months bitcoin 5 minute Open, High, Low, Close (OHCL) data from March 1st to May 5th 2021. For fixed stop loss and profit I began with 1% stop loss and 2% profit. I used both long and short positions. Here are the results:

Start                     2021-03-01 00:00:00
End                       2021-05-05 23:55:00
Duration                     65 days 23:55:00
Exposure Time [%]                    33.21934
Equity Final [$]                 847023.91686
Equity Peak [$]                  1027969.5116
Return [%]                         -15.297608
Buy & Hold Return [%]               27.703443
Return (Ann.) [%]                   -60.07534
Volatility (Ann.) [%]               14.565408
Sharpe Ratio                              0.0
Sortino Ratio                             0.0
Calmar Ratio                              0.0
Max. Drawdown [%]                  -21.524675
Avg. Drawdown [%]                   -2.614039
Max. Drawdown Duration       57 days 07:00:00
Avg. Drawdown Duration        5 days 09:28:00
# Trades                                  102
Win Rate [%]                        32.352941
Best Trade [%]                       2.792126
Worst Trade [%]                     -1.355888
Avg. Trade [%]                      -0.167597
Max. Trade Duration           1 days 08:05:00
Avg. Trade Duration           0 days 05:05:00
Profit Factor                        0.808772
Expectancy [%]                      -0.156044
SQN                                 -1.150134

The win rate is 32% and we made negative returns. This is probably we set our profit target too short. But it is risk management, we could have set them even in fractional percentages. But thinking about commissions, I think at least 1% is required for profit. Then I optimized profit and stoploss values. Here are the best 10 values.

profit_short  profit  stop_loss  stop_loss_short
91            106     95         101                1.543821e+06
                      96         101                1.486828e+06
                      95         102                1.480832e+06
                                 103                1.472921e+06
90            106     96         107                1.471529e+06
                      95         101                1.471381e+06
91            106     96         102                1.467898e+06
                                 107                1.461706e+06
90            106     95         107                1.444866e+06
                      96         101                1.440215e+06

Stoploss for both and long positions of about 96% (if the price goes below 4% of the buy price close the position) and for short positions of about 101 (meaning if the price raises above 1% sell price close the position). Here is the heatmap of the entire combinations. We are interested in the yellow zones. 107% stoploss short seems interesting too.

Stochastic+WMA+SMA Trading Strategy Backtesting Optimization Results for Bitcoin 5 minute data

Let’s backtest another slice from the data using these optimized values. Let’s see how well the optimized strategy performed:

Start                     2021-03-01 00:00:00
End                       2021-05-05 23:55:00
Duration                     65 days 23:55:00
Exposure Time [%]                   76.824328
Equity Final [$]                1543820.74068
Equity Peak [$]                 1687013.50036
Return [%]                          54.382074
Buy & Hold Return [%]               27.703443
Return (Ann.) [%]                  1004.07315
Volatility (Ann.) [%]               649.99618
Sharpe Ratio                         1.544737
Sortino Ratio                       33.275677
Calmar Ratio                         64.78734
Max. Drawdown [%]                  -15.497984
Avg. Drawdown [%]                    -1.18527
Max. Drawdown Duration       29 days 23:00:00
Avg. Drawdown Duration        0 days 11:13:00
# Trades                                   31
Win Rate [%]                        38.709677
Best Trade [%]                      12.746972
Worst Trade [%]                     -5.235511
Avg. Trade [%]                       1.445104
Max. Trade Duration          11 days 10:30:00
Avg. Trade Duration           1 days 15:11:00
Profit Factor                        2.238145
Expectancy [%]                       1.562159
SQN                                  1.607989

It looks like we doubled the buy and hold return, despite having only a 38% winning rate. The number of trades is 31, one third of the original one. But we need to realize that the original idea was a scalping method. The optimized one is really using longer trends. Now we will test the two month period one year ago (01/March/2020 to 05/May/2020). Does this optimized strategy work in another time? Here are the results:

Start                     2020-03-01 00:00:00
End                       2020-05-05 23:55:00
Duration                     65 days 23:55:00
Exposure Time [%]                   60.925545
Equity Final [$]                 1400231.5826
Equity Peak [$]                 1441637.99534
Return [%]                          40.023158
Buy & Hold Return [%]                3.452817
Return (Ann.) [%]                  543.471889
Volatility (Ann.) [%]               563.50157
Sharpe Ratio                         0.964455
Sortino Ratio                       14.899343
Calmar Ratio                        21.705887
Max. Drawdown [%]                  -25.037995
Avg. Drawdown [%]                    -3.22443
Max. Drawdown Duration       44 days 23:50:00
Avg. Drawdown Duration        2 days 04:59:00
# Trades                                   44
Win Rate [%]                        29.545455
Best Trade [%]                      11.943699
Worst Trade [%]                     -5.349373
Avg. Trade [%]                       0.770228
Max. Trade Duration           6 days 12:00:00
Avg. Trade Duration           0 days 21:54:00
Profit Factor                        1.673854
Expectancy [%]                       0.870647
SQN                                  1.111204

Surprisingly the optimized strategy worked well within this other period. Although the percentage of winning trades are only about 30%, the returns look well. But I’m not satisfied. Let’s check a longer period. From 01/February/2018 to 05/May/2021. Here’s what the results look like:

Start                     2018-02-01 00:00:00
End                       2021-05-05 23:55:00
Duration                   1189 days 23:55:00
Exposure Time [%]                   79.531899
Equity Final [$]                3160070.43656
Equity Peak [$]                 3456734.54776
Return [%]                         216.007044
Buy & Hold Return [%]              473.427186
Return (Ann.) [%]                   42.320786
Volatility (Ann.) [%]               92.952654
Sharpe Ratio                         0.455294
Sortino Ratio                        1.165405
Calmar Ratio                         0.796827
Max. Drawdown [%]                  -53.111637
Avg. Drawdown [%]                   -2.476725
Max. Drawdown Duration      544 days 07:25:00
Avg. Drawdown Duration        4 days 17:55:00
# Trades                                  612
Win Rate [%]                        25.490196
Best Trade [%]                      12.746972
Worst Trade [%]                     -5.392173
Avg. Trade [%]                       0.188835
Max. Trade Duration          30 days 05:25:00
Avg. Trade Duration           1 days 13:03:00
Profit Factor                        1.181788
Expectancy [%]                       0.280834
SQN                                  1.344469

At least we are not loosing money. In 39 months we made 612 trades with a quarter winning trade rate. We still made about half of buy and hold and we were exposed to risk about 80% of the time. I think this optimized strategy looks promising. But remember backtesting results are not very reliable. Here’s how the chart looks like:

Optimized chart showing the trades for bitcoin backtest for a 39 month period.

As promised here’s the complete python code. I have the data in a SQLite database, you need to get your own data.

import sqlite3 as sql
import pandas as pd
import numpy as np
import talib as talib
from datetime import datetime
from datetime import timedelta
from backtesting import Strategy, Backtest
from backtesting.lib import resample_apply
from matplotlib import pyplot as plt
import seaborn as sns
from findiff import FinDiff

dbfile = 'bitcoin.db'
table = 'bitcoin_5minute_raw'
conn = sql.connect(dbfile)

start_date = '2021-05-05'
how_many_months = "-2"
SQL = "SELECT DISTINCT Date,Open,Close,Low,High,Volume_USD FROM " + table + " WHERE Date < date('"+ start_date +"') and Date > date('"+ start_date+"','start of month','"+how_many_months+" month') ORDER BY Date"

data = pd.read_sql(SQL, conn)
data['Date'] = pd.to_datetime(data['Date'])
data.set_index("Date", inplace = True)
data.columns = ['Open','Close','Low','High','Volume'];

## Three indicators two time frames
# WMA(144),  SMA(5)
## hourly chart to look for direction of the trend
# SMA(5) < WMA(144) downtrend
# SMA(5) > WMA(144) uptrend
## 5 minute chart to make entries
# SMA(5) < WMA(144) downtrend
# SMA(5) > WMA(144) uptrend

class System(Strategy):
    n1 = 5
    n2 = 144
    level = 20
    profit = 106
    profit_short = 91
    stop_loss = 95
    long_short = 1
    level_short = 80
    stop_loss_short = 101
    buy_price = 0
    sell_price = 0

    def init(self):
        # Compute moving averages the strategy demands
        self.sma5 = self.I(talib.SMA, self.data.Close, self.n1)
        self.wma144 = self.I(talib.WMA, self.data.Close, self.n2)
        self.slowk,self.slowd = self.I(talib.STOCH,self.data.High,self.data.Low,self.data.Close)
        self.hsma5 = resample_apply('H', talib.SMA, self.data.Close, self.n1)
        self.hwma144 = resample_apply('H', talib.WMA, self.data.Close, self.n2)
        # we will use this later with another stoploss strategy
        
    def next(self):
        price = self.data.Close[-1]
        # If we don't already have a position, and
        # if all conditions are satisfied, enter long.
        if (not self.position and
            self.slowk[-1] < self.level and
            self.slowd[-1] < self.level and
            self.slowk[-1] > self.slowd[-1] and
            self.sma5[-1] > self.wma144[-1] and
            self.hsma5[-1] > self.hwma144[-1]):
            self.buy(sl=self.stop_loss*price/100)
            self.long_short = 1
            self.buy_price = price
        
        # If the price closes at our profit target
        # close the position, if any.
        elif (price > self.buy_price*self.profit/100 and self.long_short == 1):
            self.position.close()
            self.long_short = 0
            self.buy_price = 0

        elif (not self.position and
            self.slowk[-1] > self.level_short and
            self.slowd[-1] > self.level_short and
            self.slowk[-1] > self.slowd[-1] and
            self.sma5[-1] < self.wma144[-1] and 
            self.hsma5[-1] < self.hwma144[-1]):
            self.sell(sl=self.stop_loss_short*price/100)
            self.long_short = 2
            self.sell_price = price

        # If the price closes at our profit target
        # close the position, if any.
        elif (price < self.sell_price*self.profit_short/100 and self.long_short == 2):
        #elif (price > self.profit_short/100 * self.sma5[-1] and self.long_short == 2):
        #elif (price < price + 1.5*self.n_atr*self.atr[-1] and self.long_short == 2):
            self.position.close()
            self.long_short = 0
            self.sell_price = 0


bt = Backtest(data, System, commission=.002, exclusive_orders=True, cash=1000000)
output = bt.run()
print(output)
bt.plot()
# Optimization code
'''
stats, heatmap = bt.optimize(profit_short = range(90,99,1), profit = range(101,110,1), stop_loss=range(90,99,1), 
    stop_loss_short=range(101,110,1),  maximize='Equity Final [$]',return_heatmap=True)
print(stats)
print(stats._strategy)
hm = heatmap.groupby(['profit', 'profit_short','stop_loss','stop_loss_short']).mean().unstack()
print(heatmap.sort_values(ascending=False).iloc[:10])
plt.figure(figsize=(12, 10))
sns.heatmap(hm[::-1], cmap='viridis')
plt.savefig('foo.png')
bt.plot()
'''

Concluding Thoughts

The entry part of this strategy is clear. However, the exit is more difficult. We saw that when optimized the SMA+WMA+Stochastic strategy appeared to be working OK. However, its efficiency changed depending on the time we tested it. Will it work in the coming days? It needs to be tested. Also we learned that win rate is not necessarily the best metric, other metrics such as return percentage and Sharpe ratio could be used. Again let me remind this is not trading or investment advice. You should always remember backtesting results may be unreliable. Overfitting is always present to a degree.

References

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.