Analyzing Market Breadth with the Advance/Decline Ratio Using Python

Apr 10, 2023

8 min read


programmer-with-chrome

In this article, we will delve into the concept of using an Advanced Decline Ratio chart to analyze financial data. We will employ Python and some widely-used libraries to read and process the data, followed by creating a band chart to visualize the information. The provided code imports the required libraries and scrapes data from a website. It then uses a list of stocks from the website to download data and store it in a pandas DataFrame.


Importing Libraries:

Begin by importing the necessary libraries:


import pandas as pd
import numpy as np
import yfinance as yf

Loading the Data:


Next, use selenium to scrape data from the website:


from selenium import webdriver
from selenium.webdriver.common.keys import Keys


from bs4 import BeautifulSoup
import time
import requests
from selenium.webdriver.common.action_chains import ActionChains
from webdriver_manager.chrome import ChromeDriverManager


from selenium.webdriver.support import expected_conditions as EC

# Create a new instance of the Firefox driver
driver = webdriver.Chrome(ChromeDriverManager().install())

# Navigate to the webpage URL
url = "https://www.set.or.th/th/market/index/set50/overview"
driver.get(url)

# Wait for the page to load
time.sleep(5)

# Find the second table on the page
table = None

while table is None:
    soup = BeautifulSoup(driver.page_source, "html.parser")
    tables = soup.find_all("table", {"class": "table b-table table-custom-field table-custom-field--cnc table-hover-underline b-table-no-border-collapse b-table-selectable b-table-select-multi"})
    # Scroll down the page to load more data
    actions = ActionChains(driver)
    actions.send_keys(Keys.PAGE_DOWN).perform()
    table = tables[0]



# Extract the table headers
headers = [th.text.strip() for th in table.select("thead th")]

# Extract the table rows
rows = []
for tr in table.select("tbody tr"):
    rows.append([td.text.strip() for td in tr.select("td")])

# Print the headers and rows of the table
print(headers)
for row in rows:
    print(row)

# Close the web driver
driver.quit()
set50_list=rows

result



  ['หลักทรัพย์\n        (Click to sort Ascending)', 'เปิด\n        (Click to sort Ascending)', 'สูงสุด\n        (Click to sort Ascending)', 'ต่ำสุด\n        (Click to sort Ascending)', 'ล่าสุด\n        (Click to sort Ascending)', 'เปลี่ยนแปลง\n        (Click to sort Ascending)', '% เปลี่ยนแปลง\n        (Click to sort Ascending)', 'เสนอซื้อ\n        (Click to sort Ascending)', 'เสนอขาย\n        (Click to sort Ascending)', 'ปริมาณ (หุ้น)\n        (Click to sort Ascending)', "มูลค่า ('000 บาท)\n        (Click to sort Ascending)"]
    ['ADVANC', '211.00', '212.00', '209.00', '210.00', '-2.00', '-0.94', '210.00', '211.00', '4,691,365', '986,973.28']
    ['AOT', '72.25', '72.75', '72.00', '72.25', '-0.25', '-0.34', '72.00', '72.25', '17,551,455', '1,269,367.13']
    ['AWC', '5.35', '5.45', '5.30', '5.35', '0.00', '0.00', '5.35', '5.40', '34,728,779', '186,514.97']
    ['BANPU', '9.65', '9.65', '9.50', '9.50', '-0.10', '-1.04', '9.50', '9.55', '54,118,634', '517,509.20']
    ['BBL', '158.50', '159.50', '156.50', '158.50', '+0.50', '+0.32', '158.00', '158.50', '9,138,471', '1,443,339.52']
    ['BDMS', '29.75', '29.75', '29.00', '29.50', '0.00', '0.00', '29.25', '29.50', '57,222,628', '1,675,890.88']
    ['BEM', '9.00', '9.05', '8.90', '8.90', '-0.15', '-1.66', '8.90', '8.95', '16,640,178', '148,715.88']
    ['BGRIM', '39.25', '39.25', '38.00', '38.50', '-0.75', '-1.91', '38.25', '38.50', '7,556,798', '289,970.67']
    ['BH', '241.00', '242.00', '239.00', '242.00', '-1.00', '-0.41', '241.00', '242.00', '1,952,385', '469,645.61']
    ['BTS', '7.70', '7.80', '7.55', '7.60', '-0.10', '-1.30', '7.55', '7.60', '32,545,775', '248,895.92']
    ['CBG', '80.25', '81.00', '76.75', '77.25', '-4.00', '-4.92', '77.00', '77.25', '14,004,203', '1,097,867.26']
    ['CENTEL', '54.25', '55.50', '53.75', '55.00', '+0.75', '+1.38', '54.75', '55.00', '3,049,516', '167,007.28']
    ['COM7', '28.50', '29.00', '27.75', '28.00', '-0.25', '-0.88', '28.00', '28.25', '10,717,883', '302,077.08']
    ['CPALL', '64.00', '64.25', '63.50', '63.75', '-0.25', '-0.39', '63.75', '64.00', '11,075,918', '706,749.57']
    ['CPF', '20.90', '20.90', '20.70', '20.70', '-0.20', '-0.96', '20.70', '20.80', '6,615,716', '137,399.75']
    ['CPN', '69.00', '70.00', '68.75', '69.00', '-0.25', '-0.36', '69.00', '69.25', '6,480,281', '449,116.05']
    ['CRC', '43.75', '44.25', '43.50', '43.50', '-0.25', '-0.57', '43.50', '43.75', '5,739,627', '251,292.06']
    ['DELTA', '966.00', '970.00', '956.00', '960.00', '-12.00', '-1.23', '960.00', '962.00', '470,997', '452,708.58']
    ['EA', '73.75', '74.00', '72.50', '72.75', '-1.00', '-1.36', '72.50', '72.75', '11,800,814', '861,601.68']
    ['EGCO', '161.50', '161.50', '158.00', '158.00', '-4.00', '-2.47', '158.00', '158.50', '587,378', '93,394.65']
    ['GLOBAL', '17.40', '17.40', '16.90', '17.10', '-0.40', '-2.29', '17.00', '17.10', '15,313,248', '262,140.87']
    ['GPSC', '65.00', '65.00', '63.50', '63.75', '-1.50', '-2.30', '63.75', '64.00', '4,184,165', '267,989.58']
    ['GULF', '52.75', '53.00', '51.50', '51.50', '-1.50', '-2.83', '51.50', '51.75', '21,681,046', '1,126,171.71']
    ['HMPRO\n       \n          XD', '13.90', '14.00', '13.50', '13.50', '-0.50', '-3.57', '13.50', '13.60', '33,904,124', '462,121.80']
    ['INTUCH', '73.25', '74.25', '73.25', '73.50', '0.00', '0.00', '73.50', '73.75', '2,667,470', '196,586.17']
    ['IVL', '33.75', '34.00', '32.50', '32.50', '-1.25', '-3.70', '32.50', '32.75', '19,821,754', '653,723.54']
    ['JMART', '19.80', '20.20', '19.10', '19.20', '-0.40', '-2.04', '19.10', '19.20', '30,470,676', '596,668.01']
    ['JMT', '39.75', '40.00', '37.75', '38.50', '-0.50', '-1.28', '38.50', '38.75', '17,822,985', '692,730.90']
    ['KBANK', '129.00', '129.50', '125.00', '126.00', '-5.50', '-4.18', '125.50', '126.00', '57,355,933', '7,285,204.57']
    ['KTB', '17.40', '17.80', '17.40', '17.50', '+0.80', '+4.79', '17.50', '17.60', '284,130,167', '4,999,397.47']
    ['KTC', '53.75', '54.50', '53.50', '53.75', '0.00', '0.00', '53.75', '54.00', '3,268,631', '176,450.93']
    ['LH', '9.75', '9.80', '9.70', '9.80', '0.00', '0.00', '9.75', '9.80', '14,502,228', '141,680.20']
    ['MINT', '31.25', '31.75', '30.75', '31.50', '+0.25', '+0.80', '31.25', '31.50', '16,338,391', '512,245.18']
    ['MTC', '36.75', '36.75', '36.00', '36.00', '-0.75', '-2.04', '36.00', '36.25', '7,055,923', '255,984.38']
    ['OR', '22.20', '22.20', '21.70', '22.00', '-0.20', '-0.90', '21.90', '22.00', '38,717,565', '849,101.21']
    ['OSP', '28.25', '28.50', '27.25', '27.75', '-0.50', '-1.77', '27.50', '27.75', '13,510,037', '374,625.02']
    ['PTT', '31.00', '31.25', '30.50', '30.50', '-0.50', '-1.61', '30.50', '30.75', '32,000,973', '986,331.55']
    ['PTTEP', '157.00', '157.00', '153.50', '153.50', '-4.50', '-2.85', '153.50', '154.00', '10,036,777', '1,554,886.07']
    ['PTTGC', '42.00', '42.25', '40.75', '41.00', '-1.00', '-2.38', '41.00', '41.25', '15,889,895', '654,974.23']
    ['RATCH', '38.50', '38.75', '38.00', '38.25', '-0.50', '-1.29', '38.00', '38.25', '2,721,599', '104,424.28']
    ['SAWAD', '55.75', '56.50', '55.25', '56.00', '+0.25', '+0.45', '55.75', '56.00', '4,122,576', '230,629.29']
    ['SCB', '100.00', '100.50', '98.75', '99.75', '-0.75', '-0.75', '99.75', '100.00', '36,607,662', '3,638,772.69']
    ['SCC', '311.00', '312.00', '305.00', '305.00', '-4.00', '-1.29', '305.00', '306.00', '1,841,381', '565,428.88']
    ['SCGP', '43.75', '44.00', '43.25', '43.50', '0.00', '0.00', '43.25', '43.50', '5,902,257', '257,442.65']
    ['TIDLOR\n       \n          XD', '23.10', '23.20', '22.80', '22.90', '-0.30', '-1.29', '22.90', '23.00', '13,315,404', '305,958.49']
    ['TISCO', '99.75', '100.50', '99.50', '99.75', '0.00', '0.00', '99.50', '99.75', '11,325,175', '1,130,895.68']
    ['TOP', '49.75', '50.00', '48.25', '48.75', '-0.75', '-1.52', '48.75', '49.00', '16,190,521', '793,920.35']
    ['TRUE', '7.90', '8.05', '7.90', '8.00', '+0.05', '+0.63', '7.95', '8.00', '29,574,578', '235,550.94']
    ['TTB', '1.45', '1.46', '1.40', '1.42', '0.00', '0.00', '1.41', '1.42', '591,730,403', '843,745.44']
    ['TU', '14.00', '14.00', '13.80', '13.80', '-0.30', '-2.13', '13.80', '13.90', '24,019,571', '333,447.59']

dfset50 =pd.DataFrame(set50_list)
dfset50

012345678910
0ADVANC211.00212.00209.00210.00-2.00-0.94210.00211.004,691,365986,973.28
1AOT72.2572.7572.0072.25-0.25-0.3472.0072.2517,551,4551,269,367.13
2AWC5.355.455.305.350.000.005.355.4034,728,779186,514.97
3BANPU9.659.659.509.50-0.10-1.049.509.5554,118,634517,509.20
4BBL158.50159.50156.50158.50+0.50+0.32158.00158.509,138,4711,443,339.52
5BDMS29.7529.7529.0029.500.000.0029.2529.5057,222,6281,675,890.88
6BEM9.009.058.908.90-0.15-1.668.908.9516,640,178148,715.88
7BGRIM39.2539.2538.0038.50-0.75-1.9138.2538.507,556,798289,970.67
8BH241.00242.00239.00242.00-1.00-0.41241.00242.001,952,385469,645.61
9BTS7.707.807.557.60-0.10-1.307.557.6032,545,775248,895.92
10CBG80.2581.0076.7577.25-4.00-4.9277.0077.2514,004,2031,097,867.26
11CENTEL54.2555.5053.7555.00+0.75+1.3854.7555.003,049,516167,007.28
12COM728.5029.0027.7528.00-0.25-0.8828.0028.2510,717,883302,077.08
13CPALL64.0064.2563.5063.75-0.25-0.3963.7564.0011,075,918706,749.57
14CPF20.9020.9020.7020.70-0.20-0.9620.7020.806,615,716137,399.75
15CPN69.0070.0068.7569.00-0.25-0.3669.0069.256,480,281449,116.05
16CRC43.7544.2543.5043.50-0.25-0.5743.5043.755,739,627251,292.06
17DELTA966.00970.00956.00960.00-12.00-1.23960.00962.00470,997452,708.58
18EA73.7574.0072.5072.75-1.00-1.3672.5072.7511,800,814861,601.68
19EGCO161.50161.50158.00158.00-4.00-2.47158.00158.50587,37893,394.65
20GLOBAL17.4017.4016.9017.10-0.40-2.2917.0017.1015,313,248262,140.87
21GPSC65.0065.0063.5063.75-1.50-2.3063.7564.004,184,165267,989.58
22GULF52.7553.0051.5051.50-1.50-2.8351.5051.7521,681,0461,126,171.71
23HMPRO\n \n XD13.9014.0013.5013.50-0.50-3.5713.5013.6033,904,124462,121.80
24INTUCH73.2574.2573.2573.500.000.0073.5073.752,667,470196,586.17
25IVL33.7534.0032.5032.50-1.25-3.7032.5032.7519,821,754653,723.54
26JMART19.8020.2019.1019.20-0.40-2.0419.1019.2030,470,676596,668.01
27JMT39.7540.0037.7538.50-0.50-1.2838.5038.7517,822,985692,730.90
28KBANK129.00129.50125.00126.00-5.50-4.18125.50126.0057,355,9337,285,204.57
29KTB17.4017.8017.4017.50+0.80+4.7917.5017.60284,130,1674,999,397.47
30KTC53.7554.5053.5053.750.000.0053.7554.003,268,631176,450.93
31LH9.759.809.709.800.000.009.759.8014,502,228141,680.20
32MINT31.2531.7530.7531.50+0.25+0.8031.2531.5016,338,391512,245.18
33MTC36.7536.7536.0036.00-0.75-2.0436.0036.257,055,923255,984.38
34OR22.2022.2021.7022.00-0.20-0.9021.9022.0038,717,565849,101.21
35OSP28.2528.5027.2527.75-0.50-1.7727.5027.7513,510,037374,625.02
36PTT31.0031.2530.5030.50-0.50-1.6130.5030.7532,000,973986,331.55
37PTTEP157.00157.00153.50153.50-4.50-2.85153.50154.0010,036,7771,554,886.07
38PTTGC42.0042.2540.7541.00-1.00-2.3841.0041.2515,889,895654,974.23
39RATCH38.5038.7538.0038.25-0.50-1.2938.0038.252,721,599104,424.28
40SAWAD55.7556.5055.2556.00+0.25+0.4555.7556.004,122,576230,629.29
41SCB100.00100.5098.7599.75-0.75-0.7599.75100.0036,607,6623,638,772.69
42SCC311.00312.00305.00305.00-4.00-1.29305.00306.001,841,381565,428.88
43SCGP43.7544.0043.2543.500.000.0043.2543.505,902,257257,442.65
44TIDLOR\n \n XD23.1023.2022.8022.90-0.30-1.2922.9023.0013,315,404305,958.49
45TISCO99.75100.5099.5099.750.000.0099.5099.7511,325,1751,130,895.68
46TOP49.7550.0048.2548.75-0.75-1.5248.7549.0016,190,521793,920.35
47TRUE7.908.057.908.00+0.05+0.637.958.0029,574,578235,550.94
48TTB1.451.461.401.420.000.001.411.42591,730,403843,745.44
49TU14.0014.0013.8013.80-0.30-2.1313.8013.9024,019,571333,447.59

Cleaning Data:


Remove unnecessary characters like ‘\n’ and ‘XD’:


dfset50[0]=dfset50[0].str.replace('\n','')
dfset50[0]=dfset50[0].str.replace('XD','')
dfset50

012345678910
0ADVANC211.00212.00209.00210.00-2.00-0.94210.00211.004,691,365986,973.28
1AOT72.2572.7572.0072.25-0.25-0.3472.0072.2517,551,4551,269,367.13
2AWC5.355.455.305.350.000.005.355.4034,728,779186,514.97
3BANPU9.659.659.509.50-0.10-1.049.509.5554,118,634517,509.20
4BBL158.50159.50156.50158.50+0.50+0.32158.00158.509,138,4711,443,339.52
5BDMS29.7529.7529.0029.500.000.0029.2529.5057,222,6281,675,890.88
6BEM9.009.058.908.90-0.15-1.668.908.9516,640,178148,715.88
7BGRIM39.2539.2538.0038.50-0.75-1.9138.2538.507,556,798289,970.67
8BH241.00242.00239.00242.00-1.00-0.41241.00242.001,952,385469,645.61
9BTS7.707.807.557.60-0.10-1.307.557.6032,545,775248,895.92
10CBG80.2581.0076.7577.25-4.00-4.9277.0077.2514,004,2031,097,867.26
11CENTEL54.2555.5053.7555.00+0.75+1.3854.7555.003,049,516167,007.28
12COM728.5029.0027.7528.00-0.25-0.8828.0028.2510,717,883302,077.08
13CPALL64.0064.2563.5063.75-0.25-0.3963.7564.0011,075,918706,749.57
14CPF20.9020.9020.7020.70-0.20-0.9620.7020.806,615,716137,399.75
15CPN69.0070.0068.7569.00-0.25-0.3669.0069.256,480,281449,116.05
16CRC43.7544.2543.5043.50-0.25-0.5743.5043.755,739,627251,292.06
17DELTA966.00970.00956.00960.00-12.00-1.23960.00962.00470,997452,708.58
18EA73.7574.0072.5072.75-1.00-1.3672.5072.7511,800,814861,601.68
19EGCO161.50161.50158.00158.00-4.00-2.47158.00158.50587,37893,394.65
20GLOBAL17.4017.4016.9017.10-0.40-2.2917.0017.1015,313,248262,140.87
21GPSC65.0065.0063.5063.75-1.50-2.3063.7564.004,184,165267,989.58
22GULF52.7553.0051.5051.50-1.50-2.8351.5051.7521,681,0461,126,171.71
23HMPRO13.9014.0013.5013.50-0.50-3.5713.5013.6033,904,124462,121.80
24INTUCH73.2574.2573.2573.500.000.0073.5073.752,667,470196,586.17
25IVL33.7534.0032.5032.50-1.25-3.7032.5032.7519,821,754653,723.54
26JMART19.8020.2019.1019.20-0.40-2.0419.1019.2030,470,676596,668.01
27JMT39.7540.0037.7538.50-0.50-1.2838.5038.7517,822,985692,730.90
28KBANK129.00129.50125.00126.00-5.50-4.18125.50126.0057,355,9337,285,204.57
29KTB17.4017.8017.4017.50+0.80+4.7917.5017.60284,130,1674,999,397.47
30KTC53.7554.5053.5053.750.000.0053.7554.003,268,631176,450.93
31LH9.759.809.709.800.000.009.759.8014,502,228141,680.20
32MINT31.2531.7530.7531.50+0.25+0.8031.2531.5016,338,391512,245.18
33MTC36.7536.7536.0036.00-0.75-2.0436.0036.257,055,923255,984.38
34OR22.2022.2021.7022.00-0.20-0.9021.9022.0038,717,565849,101.21
35OSP28.2528.5027.2527.75-0.50-1.7727.5027.7513,510,037374,625.02
36PTT31.0031.2530.5030.50-0.50-1.6130.5030.7532,000,973986,331.55
37PTTEP157.00157.00153.50153.50-4.50-2.85153.50154.0010,036,7771,554,886.07
38PTTGC42.0042.2540.7541.00-1.00-2.3841.0041.2515,889,895654,974.23
39RATCH38.5038.7538.0038.25-0.50-1.2938.0038.252,721,599104,424.28
40SAWAD55.7556.5055.2556.00+0.25+0.4555.7556.004,122,576230,629.29
41SCB100.00100.5098.7599.75-0.75-0.7599.75100.0036,607,6623,638,772.69
42SCC311.00312.00305.00305.00-4.00-1.29305.00306.001,841,381565,428.88
43SCGP43.7544.0043.2543.500.000.0043.2543.505,902,257257,442.65
44TIDLOR23.1023.2022.8022.90-0.30-1.2922.9023.0013,315,404305,958.49
45TISCO99.75100.5099.5099.750.000.0099.5099.7511,325,1751,130,895.68
46TOP49.7550.0048.2548.75-0.75-1.5248.7549.0016,190,521793,920.35
47TRUE7.908.057.908.00+0.05+0.637.958.0029,574,578235,550.94
48TTB1.451.461.401.420.000.001.411.42591,730,403843,745.44
49TU14.0014.0013.8013.80-0.30-2.1313.8013.9024,019,571333,447.59

Set column name ‘symbol’ to column 0:


dfset50 = dfset50.rename(columns={0: 'symbol'})
dfset50

symbol12345678910
0ADVANC211.00212.00209.00210.00-2.00-0.94210.00211.004,691,365986,973.28
1AOT72.2572.7572.0072.25-0.25-0.3472.0072.2517,551,4551,269,367.13
2AWC5.355.455.305.350.000.005.355.4034,728,779186,514.97
3BANPU9.659.659.509.50-0.10-1.049.509.5554,118,634517,509.20
4BBL158.50159.50156.50158.50+0.50+0.32158.00158.509,138,4711,443,339.52
5BDMS29.7529.7529.0029.500.000.0029.2529.5057,222,6281,675,890.88
6BEM9.009.058.908.90-0.15-1.668.908.9516,640,178148,715.88
7BGRIM39.2539.2538.0038.50-0.75-1.9138.2538.507,556,798289,970.67
8BH241.00242.00239.00242.00-1.00-0.41241.00242.001,952,385469,645.61
9BTS7.707.807.557.60-0.10-1.307.557.6032,545,775248,895.92
10CBG80.2581.0076.7577.25-4.00-4.9277.0077.2514,004,2031,097,867.26
11CENTEL54.2555.5053.7555.00+0.75+1.3854.7555.003,049,516167,007.28
12COM728.5029.0027.7528.00-0.25-0.8828.0028.2510,717,883302,077.08
13CPALL64.0064.2563.5063.75-0.25-0.3963.7564.0011,075,918706,749.57
14CPF20.9020.9020.7020.70-0.20-0.9620.7020.806,615,716137,399.75
15CPN69.0070.0068.7569.00-0.25-0.3669.0069.256,480,281449,116.05
16CRC43.7544.2543.5043.50-0.25-0.5743.5043.755,739,627251,292.06
17DELTA966.00970.00956.00960.00-12.00-1.23960.00962.00470,997452,708.58
18EA73.7574.0072.5072.75-1.00-1.3672.5072.7511,800,814861,601.68
19EGCO161.50161.50158.00158.00-4.00-2.47158.00158.50587,37893,394.65
20GLOBAL17.4017.4016.9017.10-0.40-2.2917.0017.1015,313,248262,140.87
21GPSC65.0065.0063.5063.75-1.50-2.3063.7564.004,184,165267,989.58
22GULF52.7553.0051.5051.50-1.50-2.8351.5051.7521,681,0461,126,171.71
23HMPRO13.9014.0013.5013.50-0.50-3.5713.5013.6033,904,124462,121.80
24INTUCH73.2574.2573.2573.500.000.0073.5073.752,667,470196,586.17
25IVL33.7534.0032.5032.50-1.25-3.7032.5032.7519,821,754653,723.54
26JMART19.8020.2019.1019.20-0.40-2.0419.1019.2030,470,676596,668.01
27JMT39.7540.0037.7538.50-0.50-1.2838.5038.7517,822,985692,730.90
28KBANK129.00129.50125.00126.00-5.50-4.18125.50126.0057,355,9337,285,204.57
29KTB17.4017.8017.4017.50+0.80+4.7917.5017.60284,130,1674,999,397.47
30KTC53.7554.5053.5053.750.000.0053.7554.003,268,631176,450.93
31LH9.759.809.709.800.000.009.759.8014,502,228141,680.20
32MINT31.2531.7530.7531.50+0.25+0.8031.2531.5016,338,391512,245.18
33MTC36.7536.7536.0036.00-0.75-2.0436.0036.257,055,923255,984.38
34OR22.2022.2021.7022.00-0.20-0.9021.9022.0038,717,565849,101.21
35OSP28.2528.5027.2527.75-0.50-1.7727.5027.7513,510,037374,625.02
36PTT31.0031.2530.5030.50-0.50-1.6130.5030.7532,000,973986,331.55
37PTTEP157.00157.00153.50153.50-4.50-2.85153.50154.0010,036,7771,554,886.07
38PTTGC42.0042.2540.7541.00-1.00-2.3841.0041.2515,889,895654,974.23
39RATCH38.5038.7538.0038.25-0.50-1.2938.0038.252,721,599104,424.28
40SAWAD55.7556.5055.2556.00+0.25+0.4555.7556.004,122,576230,629.29
41SCB100.00100.5098.7599.75-0.75-0.7599.75100.0036,607,6623,638,772.69
42SCC311.00312.00305.00305.00-4.00-1.29305.00306.001,841,381565,428.88
43SCGP43.7544.0043.2543.500.000.0043.2543.505,902,257257,442.65
44TIDLOR23.1023.2022.8022.90-0.30-1.2922.9023.0013,315,404305,958.49
45TISCO99.75100.5099.5099.750.000.0099.5099.7511,325,1751,130,895.68
46TOP49.7550.0048.2548.75-0.75-1.5248.7549.0016,190,521793,920.35
47TRUE7.908.057.908.00+0.05+0.637.958.0029,574,578235,550.94
48TTB1.451.461.401.420.000.001.411.42591,730,403843,745.44
49TU14.0014.0013.8013.80-0.30-2.1313.8013.9024,019,571333,447.59

Add ‘.BK’ to the ‘symbol’ column to download data from yfinance:


dfset50=dfset50['symbol']+'.BK'
dfset50
    0                      ADVANC.BK
    1                         AOT.BK
    2                         AWC.BK
    3                       BANPU.BK
    4                         BBL.BK
    5                        BDMS.BK
    6                         BEM.BK
    7                       BGRIM.BK
    8                          BH.BK
    9                         BTS.BK
    10                        CBG.BK
    11                     CENTEL.BK
    12                       COM7.BK
    13                      CPALL.BK
    14                        CPF.BK
    15                        CPN.BK
    16                        CRC.BK
    17                      DELTA.BK
    18                         EA.BK
    19                       EGCO.BK
    20                     GLOBAL.BK
    21                       GPSC.BK
    22                       GULF.BK
    23     HMPRO                 .BK
    24                     INTUCH.BK
    25                        IVL.BK
    26                      JMART.BK
    27                        JMT.BK
    28                      KBANK.BK
    29                        KTB.BK
    30                        KTC.BK
    31                         LH.BK
    32                       MINT.BK
    33                        MTC.BK
    34                         OR.BK
    35                        OSP.BK
    36                        PTT.BK
    37                      PTTEP.BK
    38                      PTTGC.BK
    39                      RATCH.BK
    40                      SAWAD.BK
    41                        SCB.BK
    42                        SCC.BK
    43                       SCGP.BK
    44    TIDLOR                 .BK
    45                      TISCO.BK
    46                        TOP.BK
    47                       TRUE.BK
    48                        TTB.BK
    49                         TU.BK
    Name: symbol, dtype: object

dfset50=pd.DataFrame(dfset50)
symbol_list_set50 = dfset50['symbol'].tolist()
symbol_list_set50
   ['ADVANC.BK',
     'AOT.BK',
     'AWC.BK',
     'BANPU.BK',
     'BBL.BK',
     'BDMS.BK',
     'BEM.BK',
     'BGRIM.BK',
     'BH.BK',
     'BTS.BK',
     'CBG.BK',
     'CENTEL.BK',
     'COM7.BK',
     'CPALL.BK',
     'CPF.BK',
     'CPN.BK',
     'CRC.BK',
     'DELTA.BK',
     'EA.BK',
     'EGCO.BK',
     'GLOBAL.BK',
     'GPSC.BK',
     'GULF.BK',
     'HMPRO                 .BK',
     'INTUCH.BK',
     'IVL.BK',
     'JMART.BK',
     'JMT.BK',
     'KBANK.BK',
     'KTB.BK',
     'KTC.BK',
     'LH.BK',
     'MINT.BK',
     'MTC.BK',
     'OR.BK',
     'OSP.BK',
     'PTT.BK',
     'PTTEP.BK',
     'PTTGC.BK',
     'RATCH.BK',
     'SAWAD.BK',
     'SCB.BK',
     'SCC.BK',
     'SCGP.BK',
     'TIDLOR                 .BK',
     'TISCO.BK',
     'TOP.BK',
     'TRUE.BK',
     'TTB.BK',
     'TU.BK']

Clean data to remove white space:


symbol_list_set50 = [symbol.replace(' ', '') for symbol in symbol_list_set50]
symbol_list_set50
  ['ADVANC.BK',
     'AOT.BK',
     'AWC.BK',
     'BANPU.BK',
     'BBL.BK',
     'BDMS.BK',
     'BEM.BK',
     'BGRIM.BK',
     'BH.BK',
     'BTS.BK',
     'CBG.BK',
     'CENTEL.BK',
     'COM7.BK',
     'CPALL.BK',
     'CPF.BK',
     'CPN.BK',
     'CRC.BK',
     'DELTA.BK',
     'EA.BK',
     'EGCO.BK',
     'GLOBAL.BK',
     'GPSC.BK',
     'GULF.BK',
     'HMPRO.BK',
     'INTUCH.BK',
     'IVL.BK',
     'JMART.BK',
     'JMT.BK',
     'KBANK.BK',
     'KTB.BK',
     'KTC.BK',
     'LH.BK',
     'MINT.BK',
     'MTC.BK',
     'OR.BK',
     'OSP.BK',
     'PTT.BK',
     'PTTEP.BK',
     'PTTGC.BK',
     'RATCH.BK',
     'SAWAD.BK',
     'SCB.BK',
     'SCC.BK',
     'SCGP.BK',
     'TIDLOR.BK',
     'TISCO.BK',
     'TOP.BK',
     'TRUE.BK',
     'TTB.BK',
     'TU.BK']

Download data from the symbol ticker list:


data = yf.download(symbol_list_set50, period='2y' , interval='1d')
    [*********************100%***********************]  50 of 50 completed
data
Adj CloseVolume
ADVANC.BKAOT.BKAWC.BKBANPU.BKBBL.BKBDMS.BKBEM.BKBGRIM.BKBH.BKBTS.BKSAWAD.BKSCB.BKSCC.BKSCGP.BKTIDLOR.BKTISCO.BKTOP.BKTRUE.BKTTB.BKTU.BK
Date
2021-04-20162.43656965.754.84928410.588833120.30122421.1331947.77213743.162712134.8633428.48671685769200NaN332740040092000NaN181873009110200114465400.043586740019536700
2021-04-21166.14941464.754.78941710.502039121.74483520.9410767.72386243.409363133.9000248.44008419975800NaN505740011496600NaN11952800888450079843700.030420820050532200
2021-04-22162.90066563.754.74950510.328451118.85760520.6528977.72386243.409363131.0100868.39345544729100NaN64973007890900NaN7348900927150073508100.069941950031829200
2021-04-23161.50837763.004.66968110.328451116.93278521.0371347.77213742.176140131.9733898.39345525841800NaN294000022495000NaN7459900614600056871900.059470560027583200
2021-04-26160.11605863.504.52999010.154864116.45158421.4213737.72386241.682846134.3816838.30019521250300NaN35294004658800NaN7776300818100033715300.051176030014662800
2023-04-12211.00000072.505.6000009.950000157.00000029.5000009.00000040.000000234.0000007.600000637780019913600.0122450077426008876900.0638290044579000.031380130025480700
2023-04-17213.00000072.755.6000009.800000159.00000030.2500009.05000040.000000237.0000007.750000746450017896200.012808001412090011467500.01088160062740000.021910630059864400
2023-04-18212.00000072.505.4500009.600000160.00000030.0000009.05000039.750000243.0000007.70000044000006897700.01004400819070013660087.07862600119002000.023212350050972300
2023-04-19212.00000072.505.3500009.600000158.00000029.5000009.05000039.250000243.0000007.700000142787009688200.024479001014610023555200.019367400141854000.015440520026477600
2023-04-20210.00000072.255.3500009.500000158.50000029.5000008.90000038.500000242.0000007.600000412257637657462.01841381590225713315404.0113251751619052129574578.059173040326221271

486 rows × 300 columns


Clean data and set round to 2:


data.fillna(method='ffill',inplace=True)
df = data.Close
df= np.round(df,2)
df

ADVANC.BKAOT.BKAWC.BKBANPU.BKBBL.BKBDMS.BKBEM.BKBGRIM.BKBH.BKBTS.BKSAWAD.BKSCB.BKSCC.BKSCGP.BKTIDLOR.BKTISCO.BKTOP.BKTRUE.BKTTB.BKTU.BK
Date
2021-04-20175.065.754.8612.20127.522.008.0543.75140.09.1088.25NaN418.050.00NaN101.0055.253.321.2014.7
2021-04-21179.064.754.8012.10126.521.808.0044.00139.09.0588.25NaN422.049.75NaN102.0054.253.361.2115.3
2021-04-22175.563.754.7611.90123.521.508.0044.00136.09.0083.50NaN430.049.50NaN101.0054.003.321.1914.9
2021-04-23174.063.004.6811.90121.521.908.0542.75137.09.0082.00NaN428.049.25NaN101.0053.503.301.1614.8
2021-04-26172.563.504.5411.70121.022.308.0042.25139.58.9084.25NaN434.049.50NaN101.0053.753.321.1714.8
2023-04-12211.072.505.609.95157.029.509.0040.00234.07.6056.50105.50316.044.7526.50101.5052.508.401.4013.3
2023-04-17213.072.755.609.80159.030.259.0540.00237.07.7556.75101.50315.043.7526.50102.0052.008.401.4013.7
2023-04-18212.072.505.459.60160.030.009.0539.75243.07.7057.00101.00312.044.0023.78101.5050.508.401.4214.1
2023-04-19212.072.505.359.60158.029.509.0539.25243.07.7055.75100.50309.043.5023.2099.7549.508.401.4214.1
2023-04-20210.072.255.359.50158.529.508.9038.50242.07.6056.0099.75305.043.5022.9099.7548.758.001.4213.8

486 rows × 50 columns


Calculating the daily percentage change of the stock prices using the pct_change() function. Then, we apply the np.sign() function to determine whether the change is positive (advancing), negative (declining), or zero (unchanged):


np.sign(df.pct_change())

ADVANC.BKAOT.BKAWC.BKBANPU.BKBBL.BKBDMS.BKBEM.BKBGRIM.BKBH.BKBTS.BKSAWAD.BKSCB.BKSCC.BKSCGP.BKTIDLOR.BKTISCO.BKTOP.BKTRUE.BKTTB.BKTU.BK
Date
2021-04-20NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
2021-04-211.0-1.0-1.0-1.0-1.0-1.0-1.01.0-1.0-1.00.0NaN1.0-1.0NaN1.0-1.01.01.01.0
2021-04-22-1.0-1.0-1.0-1.0-1.0-1.00.00.0-1.0-1.0-1.0NaN1.0-1.0NaN-1.0-1.0-1.0-1.0-1.0
2021-04-23-1.0-1.0-1.00.0-1.01.01.0-1.01.00.0-1.0NaN-1.0-1.0NaN0.0-1.0-1.0-1.0-1.0
2021-04-26-1.01.0-1.0-1.0-1.01.0-1.0-1.01.0-1.01.0NaN1.01.0NaN0.01.01.01.00.0
2023-04-12-1.0-1.00.0-1.0-1.0-1.01.00.0-1.01.01.0-1.01.0-1.0-1.01.0-1.00.0-1.0-1.0
2023-04-171.01.00.0-1.01.01.01.00.01.01.01.0-1.0-1.0-1.00.01.0-1.00.00.01.0
2023-04-18-1.0-1.0-1.0-1.01.0-1.00.0-1.01.0-1.01.0-1.0-1.01.0-1.0-1.0-1.00.01.01.0
2023-04-190.00.0-1.00.0-1.0-1.00.0-1.00.00.0-1.0-1.0-1.0-1.0-1.0-1.0-1.00.00.00.0
2023-04-20-1.0-1.00.0-1.01.00.0-1.0-1.0-1.0-1.01.0-1.0-1.00.0-1.00.0-1.0-1.00.0-1.0

486 rows × 50 columns


np.sign(df.pct_change()).stack()
Date                 
    2021-04-21  ADVANC.BK    1.0
                AOT.BK      -1.0
                AWC.BK      -1.0
                BANPU.BK    -1.0
                BBL.BK      -1.0
                            ... 
    2023-04-20  TISCO.BK     0.0
                TOP.BK      -1.0
                TRUE.BK     -1.0
                TTB.BK       0.0
                TU.BK       -1.0
    Length: 23996, dtype: float64

np.sign(df.pct_change()).stack().groupby(level=[0]).value_counts()
    Date            
    2021-04-21  -1.0    24
                 1.0    16
                 0.0     8
    2021-04-22  -1.0    36
                 0.0     8
                        ..
    2023-04-19   0.0    13
                 1.0     2
    2023-04-20  -1.0    37
                 0.0     8
                 1.0     5
    Length: 1450, dtype: int64

After processing the data, we stack it and group it by date using the groupby() function. The value_counts() function is used to count the occurrences of each sign (advancers, decliners, and unchanged) for each date:


np.sign(df.pct_change()).stack().groupby(level=[0]).value_counts().unstack()

-1.00.01.0
Date
2021-04-2124.08.016.0
2021-04-2236.08.04.0
2021-04-2333.09.06.0
2021-04-2619.09.020.0
2021-04-2719.011.018.0
2023-04-1227.07.016.0
2023-04-1711.010.029.0
2023-04-1829.08.013.0
2023-04-1935.013.02.0
2023-04-2037.08.05.0

485 rows × 3 columns


Next, we create a new DataFrame called breadth_df to store the results and rename the columns to ‘decliners’, ‘unchanged’, and ‘advancers’:


breadth_df = np.sign(df.pct_change()).stack().groupby(level=[0]).value_counts().unstack()

breadth_df.columns=['decliners','unchanged','advancers']

Calculate the ADR by dividing the number of advancers by the number of decliners:


# Calculate ADR (ADvancer/Decliner)
breadth_df['ADR']=breadth_df['advancers']/breadth_df['decliners']

To prepare the data for visualization, we reset the index of the DataFrame:


breadth_df1=breadth_df.reset_index()
breadth_df1

DatedeclinersunchangedadvancersADR
02021-04-2124.08.016.00.666667
12021-04-2236.08.04.00.111111
22021-04-2333.09.06.00.181818
32021-04-2619.09.020.01.052632
42021-04-2719.011.018.00.947368
4802023-04-1227.07.016.00.592593
4812023-04-1711.010.029.02.636364
4822023-04-1829.08.013.00.448276
4832023-04-1935.013.02.00.057143
4842023-04-2037.08.05.00.135135

485 rows × 5 columns


Finally, we use Plotly Express to create a bar chart that plots the ADR over time:


import plotly.express as px

fig = px.bar(breadth_df1,y='ADR',x='Date',text='ADR',template='simple_white',title='Market Breadth :Advance /Decline Ratio')
fig.update_traces(texttemplate='%{text:.2s}',textposition='outside')
fig.update_traces(textfont_size=28,textangle=0,textposition='outside',cliponaxis=False)
fig.update_xaxes(rangebreaks=[dict(bounds=['sat','mon'])])
fig.update_xaxes(rangeslider_visible=True)
fig.show()



Conclusion