Oct 11 2022

We use yoy and mom of monthly sales of Taiwan listed companies to establish strategies and observe the win rate and return.

Table of Contents

Difficulties: ★★☆☆☆

We use yoy and mom as our buy&sell point for trading strategy.

Advice: The backtesting framework in this paper can refer to 【Quant】 Market strength indicator trade. For readers who are not familiar with the backtesting, they can read the 【Quant】Technical analysis to realize the process of backtesting in more detail.

According to the Securities and Exchange Act 36, the listed companies should announce and report its operation of the previous month before the 10th day of each month. Monthly sales are a particular information in the market. Foreign market rarely releases information about monthly sales. Therefore, it may have a good effect to help trading decision. We use yoy and mom to measure the return and win rate under this strategy. Due to the volatility of mom is high, the parameter of rolling window set 10, while the yoy set 5.

1. Entry condition: mom more than 10 months average of mom and yoy more than 10 months average of yoy.

2. Exit condition: mom less than 10 months average of mom and yoy less than 10 months average of yoy.

This article uses Mac OS as system and jupyter as editor.

import pandas as pd

import numpy as np

import tejapi

import matplotlib.pyplot as plt

import matplotlib.transforms as transforms

from matplotlib.font_manager import FontProperties

plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # 解決MAC電腦 plot中文問題

plt.rcParams['axes.unicode_minus'] = False

tejapi.ApiConfig.api_key = "Your Key"

tejapi.ApiConfig.ignoretz = True

Security properties Data Table (TWN/ANPRCSTD)

Listed company adjusted stock price(daily)-ex-right and dividends adjustment (TWN/APRCD1)

Monthly sales (TWN/ASALE)

Since 2013, the disclosure method of monthly sales data has been replaced by consolidated data. Therefore, we select data from April 2013 to the end of 2021. In order to make the later loop code more concise, the part of data import is written as a function.

data=tejapi.get('TWN/ANPRCSTD' ,chinese_column_name=True )

select=data["上市別"].unique()

select=select[1:3]

condition =(data["上市別"].isin(select)) & ( data["證券種類名稱"]=="普通股" )

data=data[condition]

twid=data["證券碼"].to_list() #取得上市櫃股票證券碼def get_data(code:str, id_):

df = tejapi.get(code, #從TEJ api撈取所需要的資料

chinese_column_name = True,

paginate = True,

mdate = {'gt':'2013-04-01', 'lt':'2021-12-31'},

coid=id_,

opts={'columns':['coid','mdate','close_adj']})

return dfdef get_data1(code:str, id_):

df = tejapi.get(code, #從TEJ api撈取所需要的資料

chinese_column_name = True,

paginate = True,

mdate = {'gt':'2013-04-01', 'lt':'2021-12-31'},

coid=id_,

opts={'columns':['coid','annd_s', 'd0003', 'd0004']})

return df

We calculate 5 months average of yoy and 10 months average of mom, then merge the indicator and close price. Due to the monthly sales release are often after closing quotation, it will be a bias if we set buy signal in the day of monthly sales release. Therefore, we move the indicator into next day, to set the buy signal in the next day of monthly sales release.

df_1 = get_data('TWN/APRCD1', i)

df = get_data1('TWN/ASALE', i)

df.rename(columns={'營收發布日':'年月日','單月營收成長率%':'yoy', '單月營收與上月比%':'mom', '公司':'證券代碼'}, inplace=True)

df['yoy3'] = df['yoy'].rolling(5).mean()

df['mom3'] = df['mom'].rolling(10).mean()df2 = df_1.merge(df, on=['證券代碼', '年月日'], how='outer')

df2 = df2.sort_values(by='年月日')

df2[['yoy', 'mom','yoy3','mom3']] = df2[['yoy', 'mom','yoy3',"mom3"]].shift(1)

df3 = df2.dropna()

df3.set_index(df3['年月日'], inplace=True)

df3.drop(columns={'年月日'}, inplace=True)

For the part of backtest system, you can refer to 【Quant】Market strength indicator trade. The following only shows the changes. When yoy is more than 5 months average of yoy and mom more than 10 months average of mom, then generate the buy signal; otherwise, it will be sold. The complete code will be placed at the bottom.

for i in range(len(data)):

if (data["yoy"][i] > data["yoy3"][i]) & (data["mom"][i] > data["mom3"][i]):

sell.append(np.nan)

if hold !=1:

buy.append(data["收盤價(元)"][i])

hold = 1

else:

buy.append(np.nan)

elif (data["yoy"][i] < data["yoy3"][i]) & (data["mom"][i] < data["mom3"][i]):

buy.append(np.nan)

if hold !=0:

sell.append(data["收盤價(元)"][i])

hold = 0

else:

sell.append(np.nan)

else:

buy.append(np.nan)

sell.append(np.nan)

a=(buy,sell)

Write the previous function and data processing into a for loop, and run out the results of all the listed companies.

qq = pd.DataFrame()

for i in twid[:]:

df_1 = get_data("TWN/APRCD1",i)

df = get_data1("TWN/ASALE",i)

df.rename(columns={'營收發布日':'年月日','單月營收成長率%':'yoy', '單月營收與上月比%':'mom', '公司':'證券代碼'}, inplace=True)

df['yoy3'] = df['yoy'].rolling(5).mean()

df['mom3'] = df['mom'].rolling(10).mean()

df2 = df_1.merge(df, on=['證券代碼', '年月日'], how='outer')

df2 = df2.sort_values(by='年月日')

df2[['yoy', 'mom','yoy3','mom3']] = df2[['yoy', 'mom','yoy3',"mom3"]].shift(1)

df3 = df2.dropna()

df3.set_index(df3['年月日'], inplace=True)

df3.drop(columns={'年月日'}, inplace=True)

qq = qq.append(buysell(df3, i))

print(i)

qq.index.name = '證券碼'

We can see the win rate and return by all listed companies using this strategy.

Verify whether the missing value and remove them.

print(qq.isna().sum())

qq.dropna(inplace=True)

The average return of all target is 66%, and the win rate is 52%.

print('勝率：',qq['勝率'].mean())

print('報酬率：',qq['報酬'].mean())

We calculate the number of companies with positive return and the win rate is more than 50%, accounting for about 60% of all companies. There are 60% of listed companies have positive return and 50% of win rate.

qq['count'] = np.where( (qq['報酬']>0) &(qq['勝率']>= 50),1,0)

(qq['count'].sum()/qq['count'].count())*100

Cut the win rate into five equal parts, and calculate the number of companies in each part, which will be used to draw a pie chart later.

qq['20%'] = np.where((qq['勝率']>= 80),1,0)

qq['40%'] = np.where((qq['勝率']>= 60)& (qq['勝率']< 80),1,0)

qq['60%'] = np.where((qq['勝率']>= 40)& (qq['勝率']< 60),1,0)

qq['80%'] = np.where((qq['勝率']>= 20)& (qq['勝率']< 40),1,0)

qq['100%'] = np.where((qq['勝率']<20),1,0)

z5 = [qq['20%'].sum(),qq['40%'].sum(), qq['60%'].sum(), qq['80%'].sum(),qq['100%'].sum()]

The results are presented in a pie chart, and the win rate which is above than 60% account for 1/3, indicating this strategy with a high win rate.

We can see that the 60% of listed companies may gain the positive return in these strategies, and the win rate above 60% are account for 1/3, the subsequent can also through parameter optimization method to gain higher return and win rate. The important part to note is that the backtesting here has not considered the commission fee, and the actual transaction results still have to consider the commission.

Last but not least, please note that “**Stocks this article mentions are just for the discussion, please do not consider it to be any recommendations or suggestions for investment or products.”** Hence, if you are interested in issues like Creating Trading Strategy , Performance Backtesting , Evidence-based research , welcome to purchase the plans offered in TEJ E Shop and use the well-complete database to find the potential event.

Category