<jupyter_start><jupyter_text>指數是什麼？動手算一次就懂> 寫點 Python 程式計算道瓊工業平均指數與標準普爾 500 指數>> 標籤：程式設計，獲取載入郭耀仁  from [datainpoint](https://datainpoint.substack.com/about)<jupyter_code># 載入專案需要使用的套件
import numpy as np
import pandas as pd
from tqdm import tqdm<jupyter_output><empty_output><jupyter_text>TL; DR在這個專案中，我們打算寫點 Python 程式，先將道瓊工業平均指數與標準普爾 500 指數的成分股代號由網頁中抓取出來，接著再取得這些成分股的股價以及流通股數，最後再套用加權計算的公式求解。透過這個專案，我們能夠暸解如何使用 Python 實作網頁資料擷取、數值計算以及陣列運算。  取得道瓊工業平均指數成分股道瓊工業平均指數成分股由 30 間在紐約證券交易所與 NASDAQ 上市的藍籌股（Blue Chip）公司所組成，可以由  取得這些公司的代號；使用 `pandas` 套件中的 `read_html()` 函式可以將網頁中的 `` 標記擷取下來為資料框。<jupyter_code>request_url = "https://en.wikipedia.org/wiki/Dow_Jones_Industrial_Average"
html_tables = pd.read_html(request_url)
djia_symbols = [e.replace("\xa0", "").replace("NYSE:", "") for e in html_tables[1]['Symbol'].values]
print(djia_symbols)
print(len(djia_symbols))<jupyter_output>['MMM', 'AXP', 'AMGN', 'AAPL', 'BA', 'CAT', 'CVX', 'CSCO', 'KO', 'DOW', 'GS', 'HD', 'HON', 'IBM', 'INTC', 'JNJ', 'JPM', 'MCD', 'MRK', 'MSFT', 'NKE', 'PG', 'CRM', 'TRV', 'UNH', 'VZ', 'V', 'WBA', 'WMT', 'DIS']
30<jupyter_text>取得標準普爾 500 指數成分股標準普爾 500 指數成分股由 505 間在紐約證券交易所、NASDAQ 與芝加哥期貨選擇權交易所上市的藍籌股（Blue Chip）公司所組成，可以由  取得這些公司的代號；使用 `pandas` 套件中的 `read_html()` 函式可以將網頁中的 `` 標記擷取下來為資料框。<jupyter_code>request_url = "https://en.wikipedia.org/wiki/List_of_S%26P_500_companies"
html_tables = pd.read_html(request_url)
sp500_symbols = list(html_tables[0]['Symbol'].values)
print(sp500_symbols[:10]) # print the first 10 symbols
print(len(sp500_symbols))<jupyter_output>['MMM', 'ABT', 'ABBV', 'ABMD', 'ACN', 'ATVI', 'ADBE', 'AMD', 'AAP', 'AES']
505<jupyter_text>取得成分股的股價以及流通股數使用 `pandas` 套件取得成份股的股價以及流通股數（Float shares），我們撰寫了一個類別 `GetPriceFloatShares` 可以接受美股代號的輸入，回傳 [Yahoo! Finance](https://finance.yahoo.com/) 的資料，以蘋果電腦（美股代號 AAPL）為例：<jupyter_code>class GetPriceFloatShares:
    def __init__(self, symbol):
        self._symbol = symbol
        query_str_params = {
            "p": symbol
        }
        self._query_str_params = query_str_params
    def get_price(self):
        request_url = "https://finance.yahoo.com/quote/{}/history".format(self._symbol)
        html_tables = pd.read_html(request_url)
        price = html_tables[0].iloc[0, 4].replace(",", "")
        return price
    def get_float_shares(self):
        request_url = "https://finance.yahoo.com/quote/{}/key-statistics".format(self._symbol)
        html_tables = pd.read_html(request_url)
        float_shares = html_tables[2].set_index(0).loc['Float', :].values[0]
        return float_shares
aapl = GetPriceFloatShares("AAPL")
print(aapl.get_price())
print(aapl.get_float_shares())
html_tables = pd.read_html("https://finance.yahoo.com/quote/AAPL/history?p=AAPL")
html_tables[0].iloc[0, 4]<jupyter_output><empty_output><jupyter_text>計算道瓊工業平均指數我們寫一個 `calculate_djia()` 函式計算道瓊工業平均指數，依據股價加權計算公式，是將成分股每個公司的股價加總後再除以道瓊除數（Dow divisor）：\begin{equation}\text{Dow Jones Industrial Average} = \frac{\sum_i P_i}{\text{Dow divisor}}\end{equation}道瓊除數在 2020-08-31 為 0.15198707565833（）。<jupyter_code>def calculate_djia(dow_divisor, djia_symbols):
    sum_of_prices = 0
    for symb in tqdm(djia_symbols):
        gpfs = GetPriceFloatShares(symb)
        price = gpfs.get_price()
        sum_of_prices += float(price)
    return sum_of_prices / dow_divisor

djia = calculate_djia(0.15198707565833, djia_symbols)
print(djia)<jupyter_output>27940.46784311068<jupyter_text>計算標準普爾 500 指數我們寫一個 `calculate_sp500()` 函式計算標準普爾 500 指數，依據市值加權計算公式，將成分股每個公司的股價乘以市值加總後再除以 S&P 500 除數（S&P 500 divisor）：\begin{equation}\text{S&P 500} = \frac{\sum_i (P_i \times Q_i)}{\text{S&P 500 divisor}} \\\text{where} \; Q_i = \text{Float shares of stock i}\end{equation}S&P 500 除數目前約為 83 億美元（）。由於從 [Yahoo! Finance](https://finance.yahoo.com/) 擷取 500+ 個成分股資訊要一段時間，因此我們先擷取了 2020-09-09 的資訊，存在 `data/sp500.csv`，可以用 `pandas` 的 `read_csv()` 函式讀入成為資料框。<jupyter_code>sp500_df = pd.read_csv("data/sp500.csv")
sp500_df.head()<jupyter_output><empty_output><jupyter_text>其中在 `float_shares` 欄位我們可以觀察到流通股數資料是以 B 表示 Billion、M 表示 Million，這時我們可以定義一個函式 `unit_transform()` 判斷 B 或 M 之後進行單位的轉換，亦即乘以 1,000,000,000 或 1,000,000。<jupyter_code>def unit_transform(x):
    if "B" in x:
        x = x.replace("B", "")
        float_x = float(x)
        return float_x * 1000000000
    elif "M" in x:
        x = x.replace("M", "")
        float_x = float(x)
        return float_x * 1000000<jupyter_output><empty_output><jupyter_text>`unit_tranform()` 函式目前可以針對單一成分股的流通股數進行單位轉換。<jupyter_code>print(unit_transform("575.35M"))
print(unit_transform("1.76B"))<jupyter_output>575350000.0
1760000000.0<jupyter_text>若希望能夠將其應用到所有的成分股上，我們可以使用 `np.vectorize()` 將它改變成一個通用函式（Universal function）。<jupyter_code>unit_transform_ufunc = np.vectorize(unit_transform)
print(sp500_df['float_shares'].values[:10])
print(unit_transform_ufunc(sp500_df['float_shares'].values[:10]))<jupyter_output>['575.35M' '1.76B' '1.76B' '44.04M' '635.19M' '763.22M' '477.4M' '1.17B'
 '64.61M' '662.4M']
[5.7535e+08 1.7600e+09 1.7600e+09 4.4040e+07 6.3519e+08 7.6322e+08
 4.7740e+08 1.1700e+09 6.4610e+07 6.6240e+08]<jupyter_text>最後依據市值加權計算公式定義 `calculate_sp500()` 函式計算標準普爾 500 指數。<jupyter_code>def calculate_sp500(sp500_divisor, sp500_df):
    prices = sp500_df['price'].values
    float_shares = unit_transform_ufunc(sp500_df['float_shares'].values)
    return np.sum(prices * float_shares) / sp500_divisor

sp500 = calculate_sp500(8.3e9, sp500_df)
print(sp500)<jupyter_output>3397.1794004698795
{'fraction_non_alphanumeric': 0.1532644780523792, 'fraction_numerical': 0.07285134636665437, 'mean_word_length': 6.201859229747676, 'pattern_counts': {'":': 1, '<': 35, '<?xml version=': 0, '>': 38, 'https://': 8, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '155049', 'license': 'MIT', 'n_tokens_mistral': 3102, 'n_tokens_neox': 3001, 'n_words': 330, 'path': 'project-what-is-index.ipynb', 'repo_name': 'datainpoint/project-what-is-index', 'size': '5422'}