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.
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.
- 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.
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:
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() '''
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.