Short Term Reversal
In my first post, I’ve talked about momentum in South Korea. Following the methodology of Jegadeesh and Titman (1993), I’ve shown that decile portfolios based on 9 month past return and 1 month gap exihibts momentum trend with the exception of the highest decile. I’ve mentioned that despite the robustness of momentum phenomenon, it is not practical to trade on this due to high transactions cost and shorting constraint. One potential way to circumvent this issue is to trade in the futures market, which allows shorting positions and has very low transactions costs. Yet, Korean futures market has a limitation in that stock futures are not very liquid, especially for contracts that have later expiration dates. Given that stock futures contract maturities are available at monthly frequency, implementing long term momentum strategy (monthly rebalancing) with futures will either necessarily require rollover or trading in less liquid contract market, which can be risky. To ameliate this concern, I will look at a closely related strategy - short term reversal.
Among 2,000+ stocks listed in KOSPI and KOSDAQ, there are about 140 or so stock or ETF related futures and even fewer of them have enough liquidity. The first step in analyzing short-term reversal strategy is to isolate out candidate futures. Here is transaction volume, closing price, and opening price for all stock futures with 2020 March maturity in 30 minute brackets, obtained from Daishin Securities’ Home Trading System (HTS). The data was extracted on March 9th and has starting time of January 2nd, 2020. To isolate out potential stocks, I will first look at average daily volume. Since each contract matures on the second Thursday of its maturity month, I will look at February 15th, 2020 (first day March 2020 contract becomes most liquid contract) onward.
#KOSPI Trend prediction library(data.table) library(Hmisc) dt = fread("intra_fut.csv") head(dt,2) #shows 2 rows of dt
## futcode stockcode date time open_price close_price volume ## 1: 111Q3 A005930 20200309 1545 541 538 49919 ## 2: 111Q3 A005930 20200309 1530 543 541 142702
futcode is future contract id and
stockcode is stockcode of
time refers to the closing time of the 30 minute
bracket. Korean stock market hours are 9:00 AM - 3:30PM where as Korean
futures market hours are 9:00AM - 3:45PM. As a result, time bracket
1545 has exception that it is 15 minutes. Now let’s look at
which stocks have highest average volume.
daily_volume = dt[,.(volume = sum(volume)),by = .(date, stockcode)] avg_volume = daily_volume[,.(volume=mean(volume)), .(stockcode)][order(-volume)] head(avg_volume[1:10],2)
## stockcode volume ## 1: A005930 616087.28 ## 2: A000660 94400.38
Most traded future is related to A005930: Samsung Eletronics and the second most traded future is related to A000660: SK Hynix.
hist(avg_volume[! stockcode %in% c('A005930','A000660')]$volume/1000, main = "Avg volume exc. Samsung Electronics/SK Hynix", xlab = "avg volume (000)")
Looking at the histogram of average daily volume for stock futures (Samsung electronics and SK Hynix is excluded to make the plot prettier), majority of stock futures have on average less than 5000 transactions in a day. In this post, I will be looking at 35 stocks with average volume over 10,000 contracts. The data set used in the remainder of the post is here
The trading strategy is rather simple. I will be looking at past 2 day’s cumulative return and go long on stock with lowest return and go short on stock with the highest return. Futures contract is a contract that will have value in direct proportion to the underlying asset. In Korean stock futures’ case, each contract at maturity is worth 10 times the stock price. As a result, futures price moves in unison with the underlying stock price. Thus, to test the robustness of short-term reverasal, I will look at profitability of short term reversal strategy assuming stock prices is futures prices (this is due to the difficulty of obtaining past returns of expired futures contracts).
stock_dt = fread("daily_stocks.csv") hold_window = 1 #Number of trading days to hold security past_window = 2 #Number of trading days to compute past return numports = 7 #Stocks will be divided into 7 bins #Compute Returns from holding stock_dt[, ret:=shift(adj_cls, -hold_window)/adj_cls, by = stockid] #Compute Past Return stock_dt[, past_ret:=adj_cls/shift(adj_cls,past_window), by = stockid] #Keep only observations with past returns and future return stock_dt = stock_dt[!is.na(past_ret) & !is.na(ret)] #Based on past return place into bins stock_dt[,momentum_bin:=as.integer(cut2(past_ret,g=numports)),by=tradedate] #Average daily returns for each decile portfolio bin_daily_return = stock_dt[,.(port_ret=mean(ret), past_ret=mean(past_ret)),by=.(momentum_bin,tradedate)] bin_return = bin_daily_return[,.(bin_ret=mean(port_ret)),by=momentum_bin][order(momentum_bin)] bin_return
## momentum_bin bin_ret ## 1: 1 1.0003701 ## 2: 2 0.9991952 ## 3: 3 0.9995643 ## 4: 4 0.9985996 ## 5: 5 0.9985105 ## 6: 6 0.9979376 ## 7: 7 0.9987271
It appears that lowest momentum bin (stocks with lowest return in the past 2 days) have the highest subsequant return compared to the other 4 buckets. While the ideal long-short portfolio would long bin 1 and short bin 5, as with momentum strategies, the highest bin appears erratic. If one were to go long on bin 1 and short on bin 6, the resulting long-short portfolio would result in approximately 0.22% daily return after trnascation costs and 2.26 annualized sharpe ratio, which is quite good.
#Long Short Portfolio Performance longshort = merge(bin_daily_return[momentum_bin==1,.(tradedate, long = port_ret)], bin_daily_return[momentum_bin==numports-1,.(tradedate, short = port_ret)], by = "tradedate") longshort[,diff:=long-short] print(paste("daily mean return is ", round((mean(longshort$diff)-0.00024)*100,2),"%"))
##  "daily mean return is 0.22 %"
print(paste("annualized sharpe ratio is ", round(sqrt(252)*(mean(longshort$diff)-0.00024)/sd(longshort$diff),2)))
##  "annualized sharpe ratio is 2.26"
Looking at distribution of returns
hist(longshort$diff*100, main = "Histogram of Reversal Strategy", xlab = "Daily Return(%)")
While definitely executeable, there are few additional things to consider in this strategy. First is market impact.
#Compute opening price and total traded volume daily daily_dt = dt[order(stockcode, date,time)][,.(volume = sum(volume), open_prc = head(open_price,1)),by = .(date, stockcode)] #compute daily average cash volume (opening price * volume) cash_volume = daily_dt[date>='2020-02-14',.(cash_volume=mean(volume*open_prc)), .(stockcode)] #Isolate only stock futures with over 10,000 trades cash_volume = cash_volume[stockcode %in% avg_volume[volume>=10000]$stockcode][order(cash_volume)] summary(cash_volume$cash_volume) #Amount in 000 won
## Min. 1st Qu. Median Mean 3rd Qu. Max. ## 413891 1723416 5462307 22866004 17563064 352508532
As can be seen in the summary stat above, among the 35 stocks with average daily volume over 10,000 contracts, the smallest stock futures (A008560: MERITZ SECURITIES) trades only about 413,891,000 KRW daily (approximately 413,891$). This is problematic since the largest stock future (A051900: LG HOUSEHOLD & HEALTH CARE) is 14,200,000 KRW per contract and if one were to implement equal-weight long-short, long 1 contract on LG Household & Health Care and equal value short on Meritz Securities would necessarily create market impact.
Second issue is bid-ask spread. This analysis assumes that futures contract can be bought and sold at closing price of the underlying stock. Given that futures market closes 15 minutes later than stock market, knowing the closing price is not an issue. However, for some of these futures with large bid ask spread, the bid-ask spread may eat into the return. Nevertheless, further refining of this strategy into more liquid futures may prove useful.Tags: