SmartTradeisanalgorithmictradingplatformwhichallowsprogrammerstoeasilyimplementandback-testtradingstrategiesusingPython.AsSmartTradeprovidesaccesstomultipleessentiallibrariessuchasstatsmodelandta-lib,youcanusetechnicalanalysisorstatisticalmethodsforyourstrategies.Belowwewillillustrateanexampleusing2commontechnicalindicators:MACD(MovingAverageConvergenceDivergence) and Stochastics.
First, we login to SmartTrade’s coding interface, and we will be presented with 2 panels: 1) Python coding interface and 2) Backtesting results
We will begin the development process. There are 4 key steps in building a strategy on SmartTrade
import pandas as pd
import talib as ta
import numpy as np
It is recommended to import pandas library at a minimum in order to operate on the timeseries variables which come as DataFrame objects on SmartTrade. Next specify the list of stocks you would like to backtest in the initialize method. Currently, SmartTrade supports backtesting up to 50 stocks at a time.
def initialize(ctx):
#Setting
ctx.logger.debug("initialize() called")
ctx.configure(
channels={ #Channel used
"jp.stock": {
"symbols": [
"jp.stock.9984",
"jp.stock.9983",
"jp.stock.7201",
"jp.stock.8377",
"jp.stock.5715",
"jp.stock.7014",
"jp.stock.9810",
"jp.stock.6861",
"jp.stock.2782",
],
"columns": [
"open_price_adj", #Open price(After stock split adjustment)
"high_price_adj", #High price(After stock split adjustment)
"low_price_adj", #Low price(After stock split adjustment)
"volume_adj", #Volume
"txn_volume", #Trading price
"close_price", #closing price
"close_price_adj", #closing price(After stock split adjustment)
]
}
}
)
open2 = data["open_price_adj"].fillna(method='pad')
high = data["high_price_adj"].fillna(method='pad')
low = data["low_price_adj"].fillna(method='pad')
close = data["close_price_adj"].fillna(method='pad')
Next, we will apply ta-lib to the above timeseries. Before we do that, we define additional objects to build the signals based on the results from ta-lib. We define the below objects with same DataFrame structures as data:
s1 = pd.DataFrame(data=0, columns=close.columns, index=close.index)
s2 = pd.DataFrame(data=0, columns=close.columns, index=close.index)
sto = pd.DataFrame(data=0, columns=close.columns, index=close.index)
sto2 = pd.DataFrame(data=0, columns=close.columns, index=close.index)
s3 = pd.DataFrame(data=0, columns=close.columns, index=close.index)
result = pd.DataFrame(data=0, columns=close.columns, index=close.index)
After that, we will begin applying ta-lib to the timeseries. To do that, we need to loop through the close objects by symbol as follow. We will use MACD and Stochastics functions as described above:
for (sym, val) in close.items():
macd_talib, signal, s1[sym] = ta.MACD(val.values.astype(np.double),
fastperiod=12,
slowperiod=26,
signalperiod=9)
stok, stod = ta.STOCH(high[sym].values.astype(np.double),
low[sym].values.astype(np.double),
val.values.astype(np.double),
slowk_period=14,
slowd_period=3)
s2[sym] = stok - stod
sto[sym] = stok
sto2[sym] = stod
It is important to note that he close, open, high and low timeseries must be converted to array as double first before they can be used in ta-lib.
MACD The MACD keeps track of 2 exponential moving averages (usually 12 and 26 period), and detects when the shorter moving average “crosses-over” the longer one. When a cross-over occurs, the MACD histogram crosses the zero line. The idea is that we locate the time when the histogram crosses from negative to positive, which indicates positive momentum, and vice-versa on the negative side. Below is an example illustrating buy signal using the MACD:
We use the below code to keep track on when the cross-over occurs:
test = np.sign(s1[sym].values[1:]) - np.sign(s1[sym].values[:-1])
n = len(test)
for i, row in enumerate(test):
result[sym][i+1] = 0 if isNaN(test[i]) else test[i]
Stochastic There are 2 sub-indicators within Stochastic – the K line and D line. The trend is said to shift to positive when the K line “crosses-over” the D line. In other words, when K Line > D Line. Here is an example:
Therefore, in our Python code, we keep track of stok – stod timeseries in the s2 object and detect when the s2 changes from negative to positive in order to find the time when K line crosses over the D line positively using the following code:
test = np.sign(s2[sym].values[1:]) - np.sign(s2[sym].values[:-1])
n = len(test)
for i, row in enumerate(test):
s3[sym][i+1] = 0 if isNaN(test[i]) else test[i]
In brief, when s3 equals +1, the crossover is positive, and -1 when the crossover is negative otherwise.
Finally once we constructed the above 2 signals, we provide SmartTrade with when to buy and signal using the below code:
buy_sig = result[(result>=2) & (s2>0) & ((sto>=40) | (sto2>=40))]
sell_sig = result[(result<=-2) | ((s3<0) & ((sto<=40) | (sto2<=40)))]
return {
"buy:sig": buy_sig,
"sell:sig": sell_sig
}
This is simply done by looping through the buy:sig and sell:sig objects returned from the above.
buy = current["buy:sig"].dropna()
for (sym, val) in buy.items():
if sym in done_syms:
continue
sec = ctx.getSecurity(sym)
sec.order(sec.unit() * 1, comment="SIGNAL BUY")
sell = current["sell:sig"].dropna()
for (sym, val) in sell.items():
if sym in done_syms:
continue
sec = ctx.getSecurity(sym)
sec.order(sec.unit() * -1, comment="SIGNAL SELL")
This is a custom logic to cut losses or take profit. SmartTrade provides a position object for the program to check the current return and maximum return to date. Based on that, we create a “trailing-stop” logic as follow:
for (sym, val) in ctx.portfolio.positions.items():
returns = val["returns"]
trailing = val["max_returns"] - returns
if (trailing > 0.02 and returns < 0) or (returns > 0 and trailing > 0.04) or (returns > 0.05 and trailing > 0.02) or (returns > 0.1 and trailing > 0.01):
sec = ctx.getSecurity(sym)
sec.order(-val["amount"], comment="Trailing Stop(%f)" % returns)
done_syms.add(sym)
In brief, the logic cuts losses if greater than 2%, and takes profit depending on how much the trailing return is. The idea is to maximize profit and ride the up trend as long as the trailing stop is not hit.
And we are done with the coding! Next we execute the strategy and below are the backtesting results since 2007:
As shown above, the strategy is able to navigate the bear market with relatively mild losses while generating a positive return bigger than the Nikkei 225. When applied on more stocks, the result would be even more profound.
Since the strategy is public on SmartTrade, feel free to check out the full code and run the strategy yourself below:
https://beta.smarttrade.co.jp/demo/2fdd1b2f08e04d15a45f244bcd970ef1
Recommended Posts