iT邦幫忙

2024 iThome 鐵人賽

DAY 25
0
AI/ ML & Data

打開就會 AI 與數據分析的投資理財術系列 第 25

Day24:基本面研究--利用基本面篩選股票進行投資

  • 分享至 

  • xImage
  •  

在本課中,我們將延伸昨日所學的基本面分析去學習如何使用基本面分析來篩選股票,並比較不同篩選標準所選出的股票的報酬率。我們還將探討巴菲特的選股邏輯,並比較其篩選出的股票的表現。此外,我們將加入每月定期定額投資的報酬率比較,並通過圖表視覺化不同投資方法之間的資產成長曲線,以直觀了解各策略的表現。今日 Colab


一、引言

1. 為什麼要使用基本面篩選股票?

  • 長期投資價值:基本面分析關注公司的內在價值,有助於發現被低估的優質公司。
  • 降低風險:選擇財務穩健、盈利能力強的公司,可以降低投資風險。
  • 策略指導:通過設置篩選條件,可以制定系統性的投資策略。
    https://ithelp.ithome.com.tw/upload/images/20241009/20120549bjCPsBqRAp.jpg

2. 本課目標

  • 瞭解基本面篩選的常用指標和方法。
  • 使用 Python 實現股票篩選,並獲取歷史股價數據。
  • 比較不同篩選標準所選股票的歷史報酬率。
  • 探討巴菲特的選股邏輯,並分析其篩選出的股票表現。
  • 加入定期定額投資的報酬率比較,並通過圖表視覺化不同投資方法之間的資產成長曲線。

二、環境設置

1. 安裝必要的庫

我們需要以下 Python 庫:

  • Pandas:數據處理和分析。
  • NumPy:科學計算。
  • Matplotlib:數據可視化。
  • YFinance:獲取金融數據。
  • Requests:HTTP 請求,用於獲取網絡數據。
!pip install pandas numpy matplotlib
!pip install yfinance
!pip install requests

2. 導入庫

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import yfinance as yf
import requests
import datetime

三、定義篩選標準

1. 常用的基本面指標

  • 市盈率(P/E Ratio):股價與每股收益的比率。較低的 P/E 可能表示被低估。
  • 股息收益率(Dividend Yield):每股股息與股價的比率。較高的收益率可能表示穩定的股息支付。
  • 淨資產收益率(ROE):淨利潤與股東權益的比率。較高的 ROE 表示盈利能力強。
  • 負債比率(Debt to Equity Ratio):總負債與股東權益的比率。較低的比率表示財務穩健。

2. 篩選標準設置

我們將定義以下三種篩選標準:

  • 標準A:P/E Ratio < 15,Dividend Yield > 2%
  • 標準B:ROE > 15%,Debt to Equity Ratio < 0.5
  • 巴菲特標準:符合巴菲特選股邏輯的公司,將於下面解釋

四、獲取股票清單

1. 選取股票池

為了實現篩選,我們需要一個股票池。這裡我們選擇標準普爾500指數(S&P 500)的成份股。

sp500_url = 'https://en.wikipedia.org/wiki/List_of_S%26P_500_companies'
tables = pd.read_html(sp500_url)
sp500_df = tables[0]
sp500_symbols = sp500_df['Symbol'].tolist()

會印出一堆:
https://ithelp.ithome.com.tw/upload/images/20241009/20120549rYvRYM9iLC.png


五、獲取財務數據並進行篩選

1. 定義函數以獲取財務數據

我們將使用 yfinance 獲取財務指標。

def get_financial_data(symbols):
    financial_data = []
    for symbol in symbols:
        try:
            stock = yf.Ticker(symbol)
            info = stock.info
            data = {
                'Symbol': symbol,
                'PE': info.get('forwardPE'),
                'DividendYield': info.get('dividendYield'),
                'ROE': info.get('returnOnEquity'),
                'DebtToEquity': info.get('debtToEquity'),
            }
            financial_data.append(data)
        except Exception as e:
            print(f"Error fetching data for {symbol}: {e}")
    return pd.DataFrame(financial_data)

2. 獲取財務數據

financial_df = get_financial_data(sp500_symbols)

3. 數據清洗

# 去除缺失值
financial_df.dropna(inplace=True)

4. 根據標準進行篩選

(1) 標準A:P/E Ratio < 15,Dividend Yield > 2%

criteria_a = financial_df[
    (financial_df['PE'] < 15) &
    (financial_df['DividendYield'] > 0.02)
]

(2) 標準B:ROE > 15%,Debt to Equity Ratio < 0.5

criteria_b = financial_df[
    (financial_df['ROE'] > 0.15) &
    (financial_df['DebtToEquity'] < 50)  # 注意:DebtToEquity 通常以百分比表示
]

(3) 巴菲特標準

巴菲特的選股邏輯包括:

  • 持續的盈利能力:高且穩定的 ROE。
  • 財務穩健:低負債水平。
  • 競爭優勢:行業領先地位(此處難以量化)。
  • 合理的估值:P/E Ratio 合理。

我們將簡化為:

  • ROE > 20%
  • Debt to Equity Ratio < 0.5
  • P/E Ratio < 20
buffett_criteria = financial_df[
    (financial_df['ROE'] > 0.20) &
    (financial_df['DebtToEquity'] < 50) &
    (financial_df['PE'] < 20)
]

六、比較篩選結果的報酬率

1. 定義函數計算單筆投資報酬率

def calculate_return(symbols, start_date, end_date):
    returns = []
    for symbol in symbols:
        try:
            data = yf.download(symbol, start=start_date, end=end_date)
            if len(data) > 0:
                data = data['Adj Close']
                # data = data.reindex(pd.date_range(start_date, end_date, freq='D')).fillna(method='ffill')
                initial_investment = 10000  # 初始投資 10000 美元
                shares = initial_investment / data.iloc[0]
                portfolio = shares * data
                returns.append(portfolio)
        except Exception as e:
            print(f"Error fetching data for {symbol}: {e}")
    if returns:
        total_portfolio = pd.concat(returns, axis=1).mean(axis=1)
        return total_portfolio
    else:
        return None

2. 定義函數計算定期定額投資報酬率

def calculate_dca_return(symbols, start_date, end_date, monthly_investment):
    portfolios = []
    for symbol in symbols:
        try:
            data = yf.download(symbol, start=start_date, end=end_date)
            if len(data) > 0:
                data = data['Adj Close']
                # data = data.reindex(pd.date_range(start_date, end_date, freq='D')).fillna(method='ffill')
                total_shares = 0
                dates = pd.date_range(start=start_date, end=end_date, freq='MS')
                for date in dates:
                    if date in data.index:
                        price = data.loc[date]
                        shares = monthly_investment / price
                        total_shares += shares
                portfolio = total_shares * data
                portfolios.append(portfolio)
        except Exception as e:
            print(f"Error fetching data for {symbol}: {e}")
    if portfolios:
        total_portfolio = pd.concat(portfolios, axis=1).mean(axis=1)
        return total_portfolio
    else:
        return None

3. 設定回測時間範圍

start_date = '2022-01-01'
end_date = '2023-01-01'

4. 計算並視覺化單筆投資的資產成長曲線

(1) 標準A

portfolio_a = calculate_return(criteria_a['Symbol'], start_date, end_date)

(2) 標準B

portfolio_b = calculate_return(criteria_b['Symbol'], start_date, end_date)

(3) 巴菲特標準

portfolio_buffett = calculate_return(buffett_criteria['Symbol'], start_date, end_date)

(4) S&P 500 指數

data_sp500 = yf.download('^GSPC', start=start_date, end=end_date)
data_sp500 = data_sp500['Adj Close']
data_sp500 = data_sp500.reindex(pd.date_range(start_date, end_date, freq='D')).fillna(method='ffill')
initial_investment = 10000
shares_sp500 = initial_investment / data_sp500.iloc[0]
portfolio_sp500 = shares_sp500 * data_sp500

(5) 繪製單筆投資的資產成長曲線

plt.figure(figsize=(12, 6))
if portfolio_a is not None:
    plt.plot(portfolio_a.index, portfolio_a.values, label='標準A')
if portfolio_b is not None:
    plt.plot(portfolio_b.index, portfolio_b.values, label='標準B')
if portfolio_buffett is not None:
    plt.plot(portfolio_buffett.index, portfolio_buffett.values, label='巴菲特標準')
plt.plot(portfolio_sp500.index, portfolio_sp500.values, label='S&P 500 指數')
plt.title('單筆投資資產成長曲線')
plt.xlabel('日期')
plt.ylabel('資產價值(美元)')
plt.legend()
plt.show()

5. 計算並視覺化定期定額投資的資產成長曲線

(1) 每月投資金額設定

monthly_investment = 1000  # 每月投資 1000 美元

(2) 標準A

dca_portfolio_a = calculate_dca_return(criteria_a['Symbol'], start_date, end_date, monthly_investment)

(3) 標準B

dca_portfolio_b = calculate_dca_return(criteria_b['Symbol'], start_date, end_date, monthly_investment)

(4) 巴菲特標準

dca_portfolio_buffett = calculate_dca_return(buffett_criteria['Symbol'], start_date, end_date, monthly_investment)

(5) S&P 500 指數

data_sp500_dca = data_sp500.copy()
total_shares = 0
dates = pd.date_range(start=start_date, end=end_date, freq='MS')
for date in dates:
    if date in data_sp500_dca.index:
        price = data_sp500_dca.loc[date]
        shares = monthly_investment / price
        total_shares += shares
dca_portfolio_sp500 = total_shares * data_sp500_dca

(6) 繪製定期定額投資的資產成長曲線

plt.figure(figsize=(12, 6))
if dca_portfolio_a is not None:
    plt.plot(dca_portfolio_a.index, dca_portfolio_a.values, label='標準A')
if dca_portfolio_b is not None:
    plt.plot(dca_portfolio_b.index, dca_portfolio_b.values, label='標準B')
if dca_portfolio_buffett is not None:
    plt.plot(dca_portfolio_buffett.index, dca_portfolio_buffett.values, label='巴菲特標準')
plt.plot(dca_portfolio_sp500.index, dca_portfolio_sp500.values, label='S&P 500 指數')
plt.title('定期定額投資資產成長曲線')
plt.xlabel('日期')
plt.ylabel('資產價值(美元)')
plt.legend()
plt.show()

七、結果分析

1. 單筆投資資產成長曲線分析

  • 比較各策略的資產成長曲線,可以直觀地看到哪種策略在該期間表現更好。
  • 標準A、B和巴菲特標準:觀察資產價值的變化趨勢,了解波動性和收益性。
  • S&P 500 指數:作為基準,與各策略進行比較。

2. 定期定額投資資產成長曲線分析

  • 定期定額的優勢:在市場波動時,定期定額可以平滑投資成本,降低風險。
  • 資產成長曲線:觀察資產價值隨時間的增長情況,了解不同策略的累積效果。
  • 比較各策略:哪種策略在定期定額投資中表現最佳。

3. 總結

  • 投資策略的選擇:根據資產成長曲線,可以選擇適合自己風險偏好的投資策略。
  • 持續投資的重要性:定期定額投資有助於在市場波動中獲得平均成本,長期來看可能獲得穩定收益。

八、總結

  • 投資策略多樣化:結合基本面篩選和定期定額投資,可以構建更加穩健的投資組合。
  • 投資方式的選擇:單筆投資適合對市場時機有信心的投資者,定期定額適合想要平滑風險的投資者。
  • 視覺化分析:通過資產成長曲線,可以直觀地比較不同策略的效果,為投資決策提供參考。

九、作業

  1. 調整投資金額和頻率:嘗試不同的每月投資金額或投資頻率,觀察對報酬率的影響。
  2. 比較不同時期的表現:選擇其他年份作為投資期間,分析不同市場環境下的結果。
  3. 深入個股分析:對定期定額報酬率較高或較低的個股進行分析,了解原因。
  4. 模擬投資組合:將篩選出的股票組成投資組合,進行回測和風險分析。

提示

  • 資料完整性:確保獲取的股價數據涵蓋所需的時間範圍,避免因缺失數據導致計算錯誤。
  • 投資成本考量:在實際投資中,需考慮交易費用和稅費對報酬率的影響。
  • 風險管理:定期定額並非適用於所有投資標的,需根據資產特性進行評估。

注意事項

  • 投資風險:過去的表現不代表未來,投資需謹慎。
  • 合規要求:遵守法律法規和市場規則,不從事內線交易等違法行為。
  • 倫理考量:投資時應考慮環境、社會和治理(ESG)等因素,促進可持續發展。

希望通過本課的學習,能夠掌握基本面篩選股票的方法,並能夠根據不同的投資策略進行比較和選擇。


十、完整程式碼

# 安裝必要的庫
!pip install pandas numpy matplotlib
!pip install yfinance
!pip install requests

# 導入庫
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import yfinance as yf
import requests
import datetime

# 獲取 S&P 500 成份股列表
sp500_url = 'https://en.wikipedia.org/wiki/List_of_S%26P_500_companies'
tables = pd.read_html(sp500_url)
sp500_df = tables[0]
sp500_symbols = sp500_df['Symbol'].tolist()

# 定義函數以獲取財務數據
def get_financial_data(symbols):
    financial_data = []
    for symbol in symbols:
        try:
            stock = yf.Ticker(symbol)
            info = stock.info
            data = {
                'Symbol': symbol,
                'PE': info.get('forwardPE'),
                'DividendYield': info.get('dividendYield'),
                'ROE': info.get('returnOnEquity'),
                'DebtToEquity': info.get('debtToEquity'),
            }
            financial_data.append(data)
        except Exception as e:
            print(f"Error fetching data for {symbol}: {e}")
    return pd.DataFrame(financial_data)

# 獲取財務數據
financial_df = get_financial_data(sp500_symbols)

# 數據清洗
financial_df.dropna(inplace=True)

# 篩選標準A
criteria_a = financial_df[
    (financial_df['PE'] < 15) &
    (financial_df['DividendYield'] > 0.02)
]

# 篩選標準B
criteria_b = financial_df[
    (financial_df['ROE'] > 0.15) &
    (financial_df['DebtToEquity'] < 50)
]

# 巴菲特標準
buffett_criteria = financial_df[
    (financial_df['ROE'] > 0.20) &
    (financial_df['DebtToEquity'] < 50) &
    (financial_df['PE'] < 20)
]

# 定義函數計算單筆投資報酬率
def calculate_return(symbols, start_date, end_date):
    returns = []
    for symbol in symbols:
        try:
            data = yf.download(symbol, start=start_date, end=end_date)
            if len(data) > 0:
                data = data['Adj Close']
                # data = data.reindex(pd.date_range(start_date, end_date, freq='D')).fillna(method='ffill')
                initial_investment = 10000  # 初始投資 10000 美元
                shares = initial_investment / data.iloc[0]
                portfolio = shares * data
                returns.append(portfolio)
        except Exception as e:
            print(f"Error fetching data for {symbol}: {e}")
    if returns:
        total_portfolio = pd.concat(returns, axis=1).mean(axis=1)
        return total_portfolio
    else:
        return None

# 定義函數計算定期定額投資報酬率
def calculate_dca_return(symbols, start_date, end_date, monthly_investment):
    portfolios = []
    for symbol in symbols:
        try:
            data = yf.download(symbol, start=start_date, end=end_date)
            if len(data) > 0:
                data = data['Adj Close']
                # data = data.reindex(pd.date_range(start_date, end_date, freq='D')).fillna(method='ffill')
                total_shares = 0
                dates = pd.date_range(start=start_date, end=end_date, freq='MS')
                for date in dates:
                    if date in data.index:
                        price = data.loc[date]
                        shares = monthly_investment / price
                        total_shares += shares
                portfolio = total_shares * data
                portfolios.append(portfolio)
        except Exception as e:
            print(f"Error fetching data for {symbol}: {e}")
    if portfolios:
        total_portfolio = pd.concat(portfolios, axis=1).mean(axis=1)
        return total_portfolio
    else:
        return None

# 設定回測時間範圍
start_date = '2022-01-01'
end_date = '2023-01-01'

# 單筆投資資產成長曲線
portfolio_a = calculate_return(criteria_a['Symbol'], start_date, end_date)
portfolio_b = calculate_return(criteria_b['Symbol'], start_date, end_date)
portfolio_buffett = calculate_return(buffett_criteria['Symbol'], start_date, end_date)

# S&P 500 指數
data_sp500 = yf.download('^GSPC', start=start_date, end=end_date)
data_sp500 = data_sp500['Adj Close']
data_sp500 = data_sp500.reindex(pd.date_range(start_date, end_date, freq='D')).fillna(method='ffill')
initial_investment = 10000
shares_sp500 = initial_investment / data_sp500.iloc[0]
portfolio_sp500 = shares_sp500 * data_sp500

# 繪製單筆投資的資產成長曲線
plt.figure(figsize=(12, 6))
if portfolio_a is not None:
    plt.plot(portfolio_a.index, portfolio_a.values, label='標準A')
if portfolio_b is not None:
    plt.plot(portfolio_b.index, portfolio_b.values, label='標準B')
if portfolio_buffett is not None:
    plt.plot(portfolio_buffett.index, portfolio_buffett.values, label='巴菲特標準')
plt.plot(portfolio_sp500.index, portfolio_sp500.values, label='S&P 500 指數')
plt.title('單筆投資資產成長曲線')
plt.xlabel('日期')
plt.ylabel('資產價值(美元)')
plt.legend()
plt.show()

# 定期定額投資資產成長曲線
monthly_investment = 1000  # 每月投資 1000 美元

dca_portfolio_a = calculate_dca_return(criteria_a['Symbol'], start_date, end_date, monthly_investment)
dca_portfolio_b = calculate_dca_return(criteria_b['Symbol'], start_date, end_date, monthly_investment)
dca_portfolio_buffett = calculate_dca_return(buffett_criteria['Symbol'], start_date, end_date, monthly_investment)

# S&P 500 指數定期定額
data_sp500_dca = data_sp500.copy()
total_shares = 0
dates = pd.date_range(start=start_date, end=end_date, freq='MS')
for date in dates:
    if date in data_sp500_dca.index:
        price = data_sp500_dca.loc[date]
        shares = monthly_investment / price
        total_shares += shares
dca_portfolio_sp500 = total_shares * data_sp500_dca

# 繪製定期定額投資的資產成長曲線
plt.figure(figsize=(12, 6))
if dca_portfolio_a is not None:
    plt.plot(dca_portfolio_a.index, dca_portfolio_a.values, label='標準A')
if dca_portfolio_b is not None:
    plt.plot(dca_portfolio_b.index, dca_portfolio_b.values, label='標準B')
if dca_portfolio_buffett is not None:
    plt.plot(dca_portfolio_buffett.index, dca_portfolio_buffett.values, label='巴菲特標準')
plt.plot(dca_portfolio_sp500.index, dca_portfolio_sp500.values, label='S&P 500 指數')
plt.title('定期定額投資資產成長曲線')
plt.xlabel('日期')
plt.ylabel('資產價值(美元)')
plt.legend()
plt.show()

注意

  • 數據完整性yfinance 可能會遇到缺失數據的情況,請注意處理。
  • 執行時間:篩選大量股票並下載數據可能需要較長時間,請耐心等待。
  • 實際投資考量:本示例未考慮交易成本和稅費,實際投資時應納入考慮。

上一篇
Day23:基本面研究--財務數據分析與可視化
下一篇
Day25:期貨期權量化對沖策略以及風險&資金管理
系列文
打開就會 AI 與數據分析的投資理財術30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
youi
iT邦新手 5 級 ‧ 2024-12-09 15:55:40

Hi, portfolio 都是NAN,能否修改程式碼,感恩(,,・ω・,,)

zivzhong iT邦新手 3 級 ‧ 2024-12-09 17:22:09 檢舉

youiHi 你好,首先感謝你青睞這個系列,但當初這個系列在撰寫時因為是每日要繳交一篇所以過於匆忙,內容上可能有些不盡完美以及舉例可能有欠妥部分。後續有時間會在講整個系列整理一次!
另外你提及全部都是 Nan 的部分目前有上去簡單修整應該現在不是 NAN。最後再次感謝你關注這個系列!也歡迎多多推廣出去!任何鼓勵都是持續更新的最大動力!

我要留言

立即登入留言