Trading Strategy by Keeping an Eye on Big Players

Chip analysis and backtesting using TEJ API trial database

Photo Creds: Usplash


Generally speaking, big players such as institutional investors, insiders or other investors holding large amounts of shares have information advantages over retail investors. Thus, they are more likely to select stocks with potential growth and distance themselves from risky stocks. To decrease this kind of information asymmetry, The Financial Supervisory Committee in Taiwan requires firms and securities firms to release their daily trading data, allowing investors to analyze the trend of stock price by observing big players’ investment. And this is so-called chip analysis.

Common indicators in chip analysis include the net buy-and-sell of three primary institutional investors, the days of consecutive buying or selling, trading amounts by institutional investors, stock holding of directors and supervisors, the ratio of short sale and margin purchase. Since the TEJ API database contains a variety of chip indicators, there’s no need to calculate ourselves and we can immediately carry out stock selection and backtesting based on it. This article follows the content of 【Introduction(5)】Starting Using TEJ Trial Database, so we will still obtain data from the trial database for backtesting. And for those who already applied for access to the trial database, it’s recommended to do the backtesting yourself while reading this article !

The Editing Environment and Modules Required

We use Windows OS and Jupyter Notebook in this article

import tejapi
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
tejapi.ApiConfig.api_key = “Your Key”
tejapi.ApiConfig.ignoretz = True

Note: Remember to replace ‘Your Key’ with the one you applied for. The last line indicates the time zone is not shown.

The Highlights of the Article

  • Understand the indicators of chip analysis
  • Demonstrate the basic backtesting

Trial Database Used

The Application of Chip Analysis Indicators

  • Shareholding ratio of institutional investors: The sum of shareholding ratio of foreign investors, securities investment trust companies and dealers. It can be used to observe the distribution of each stock.
  • Net Buy-and-sell of institutional investors: The sum of net buy-and-sell of foreign investors, securities investment trust companies and dealers. The positive figure indicates net buy, while negative one means net sell. It can be employed to trace the movement of big players.

The Design and Practice of Trading Strategy

Buy signal: if the institutional investors are net-buying and their shareholding ratio is lower than the 5-day moving average, the stock price may begin to rise.

Sell signal: if the institutional investors are net-selling and their shareholding ratio exceeds the 5-day moving average, it may indicate the institutional investors may start to dump shares.

Assumption: When the buy signal pops up, we buy the stock at the next day’s opening price since the net buy-and-sell information is disclosed after the market closes. During the holding period, we hold no more than one share and sell it when the sell signal is met. Transaction fee (0.1425%) and transfer tax on stock (0.3%) are considered.

Step 1. Obtain data after importing modules

evergreen_chip = tejapi.get('TRAIL/TATINST1',
                            coid = '2603',
                            opts = {'columns':['coid','mdate',
                            chinese_column_name = True)

For starters, we obtain the chip data of Evergreen Marine Corporation (2603). The column codes we select are shareholding share and net buy-and-sell of institutional investors according to column description of the database.

evergreen_price = tejapi.get('TRAIL/TAPRCD',
                            coid = '2603',
                            opts = {'columns':['mdate','open_d']},
                            chinese_column_name = True)

To calculate the return, we also need stock price data. Here we apply shift(-1) to move the opening price up one row and form a new column called next day’s opening price, in order to match our buying and selling price.

market = tejapi.get('TRAIL/TAPRCD',
                    coid = 'Y9997',
                    opts = {'columns':['mdate', 'roi']},
                    chinese_column_name = True)

We need the return of TAIEX Total Return Index as our benchmark to measure the performance of this strategy. To not confuse with strategy return, we use rename() to change the name of the return column into market return.

market = market.rename(columns = {'報酬率%':'市場報酬率%'})

Step 2. Merge the data

evergreen = evergreen_chip.merge(evergreen_price, on = '年月日')
evergreen = evergreen.merge(market, on = '年月日')

In this step, we combine chip, stock price and market return data into one table with the usage of merge() and on based on their mutual column.

Step 3. Create signal identification column

evergreen['合計買賣超'] = np.where(evergreen['合計買賣超(千股)'] >= 0, 1, 0)

Here we take advantage of np.where() to create a new column and fill it with1 if the institutional investors are net-buying, and 0, otherwise.

evergreen['持股率_5日MA'] = evergreen['合計持股率%'].rolling(5).mean()
evergreen['合計持股變化'] = np.where(evergreen['合計持股率%'] - evergreen['持股率_5日MA'] > 0, 1, 0)

To evaluate the change of shareholding ratio, we use the difference between shareholding ratio and its 5-day moving average to decide whether its value is relatively high or low.

evergreen = evergreen.dropna().reset_index(drop=True)

Finally, we utilize dropna() to remove NaN values and reset_index(drop=True) to reset the index and prevent the original index from forming a new column.

Step 4. Add signal column

  • Buy
evergreen['訊號'] = np.where((evergreen['合計買賣超'] == 1)&(evergreen['合計持股變化'] == 0), 'Buy', '')
  • Sell
evergreen['訊號'] = np.where((evergreen['合計買賣超'] == 0)&(evergreen['合計持股變化'] == 1), 'Sell', evergreen['訊號'])

Here we newly add the signal column and fill it with ‘Buy’, ‘Sell’ or empty string according to the value of the signal identification column. It’s worth noting that when deciding whether or not to assign a ‘Sell’ value, we have to put the signal column in the third parameter to not substitute the outcome of the previous assignment of ‘Buy’ signal. In other words, we keep the original judgement if the selling condition is not satisfied.

evergreen['訊號'][len(evergreen)-1] = 'Sell'

len(evergreen) represents the number of data and the index of the last data (12/30) equals that number minus one. What we are doing is to manually assign a ‘Sell’ signal to the last day, indicating we’ll close position at the end of 2020.

Step 5. Calculate strategy return

First of all, we create the variable hold and assign 0 by default, meaning we have no stock on hand. Once there’s a buy signal and we don’t have a stock, we assign 1 to the variable. However, if we have a stock and meet a sell signal, we reset the value of the variable back to 0.

hold = 0

The value of the variable cost is also set 0 by default. If there’s a buy signal when we hold no stock, we will assign the next day’s opening price as the cost of buying the stock.

cost = 0

Then we create the empty list Return. When we meet a sell signal while holding a stock, we use np.log() to calculate the holding period return. After taking friction cost into consideration, we adopt append() to add the continuous return into the list and add 0 under other circumstances to make sure the length of the list matches the number of the data.

Return = []

Now we use for to loop as many times as the number of the data. Among each loop, we apply if to examine whether buy or sell signal occurs at the i day. If certain conditions are met, we calculate return and alter the value of some variables.

for i in range(len(evergreen)):
    if evergreen['訊號'][i] == '':
    elif evergreen['訊號'][i] == 'Buy':
        if hold == 0:
            cost = evergreen['次日開盤價'][i]
            hold = 1
    elif evergreen['訊號'][i] == 'Sell':
        if hold == 1:
            Return.append(100*(np.log(evergreen['次日開盤價'][i]/cost)- 0.001425*2 - 0.003))
            hold = 0

Finally we take the list Return as our new column called chip analysis return.

evergreen['籌碼面報酬率(%)'] = Return

Step 6. Calculate the cumulative return and visualize the performance

evergreen['籌碼累積報酬率'] = evergreen['籌碼面報酬率(%)'].apply(lambda x: 0.01*x+1).cumprod()
evergreen['市場累積報酬率'] = evergreen['市場報酬率%'].apply(lambda x: 0.01*x+1).cumprod()

We firstly use apply() to transform the form of return into the decimal form with principal, then utilize cumprod() to calculate the cumulative return.

plt.plot(evergreen['年月日'], evergreen['籌碼累積報酬率'], label = 'strategy')
plt.plot(evergreen['年月日'], evergreen['市場累積報酬率'], label = 'market')

Lastly, we present the plot with plt.plot() and and show the label by using label and plt.legend().

Obviously the cumulative return of this strategy is much better than that of the market during 2020.

Step 7. Performance table

cagr = [100*(evergreen['籌碼累積報酬率'].values[-1]**(252/len(evergreen)) - 1), 100*(evergreen['市場累積報酬率'].values[-1]**(252/len(evergreen)) - 1)]

We create the list cagr to store the compound annual growth rate of the strategy and market. values[-1] means we select the latest cumulative return and annualize it based on 252 trading days a year. Finally we subtract the principal from the annualized return.

std = [evergreen['籌碼面報酬率(%)'].std()*(252**0.5),evergreen['市場報酬率%'].std()*(252**0.5)]

The list std is used to store the annualized standard deviation, which is the result of daily standard deviation adjusted by trading days a year.

sharpe_ratio = [(cagr[0] - 1)/std[0],(cagr[1] - 1)/std[1]]

Then we create the list sharpe_ratio to store chip analysis strategy and market’s sharpe ratio. Here we assume the riskless interest rate is 1%. Finally we use pd.DataFrame() to form a table

result = pd.DataFrame([cagr,std,sharpe_ratio], columns = ['籌碼面','市場'], index = ['年化報酬(%)','年化標準差(%)','夏普比率'])


You must have grasped the overall idea of chip analysis indicators and backtesting ! However, there’s no general strategy that guarantees profit all the time, so you must take this backtesting outcome seriously. If you want to have a longer backtesting period or more indicators, we highly recommend you to consider our master plan to optimize your own strategy !

Related Link