在本課中,我們將延伸昨日所學的基本面分析去學習如何使用基本面分析來篩選股票,並比較不同篩選標準所選出的股票的報酬率。我們還將探討巴菲特的選股邏輯,並比較其篩選出的股票的表現。此外,我們將加入每月定期定額投資的報酬率比較,並通過圖表視覺化不同投資方法之間的資產成長曲線,以直觀了解各策略的表現。今日 Colab
我們需要以下 Python 庫:
!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
我們將定義以下三種篩選標準:
為了實現篩選,我們需要一個股票池。這裡我們選擇標準普爾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()
會印出一堆:
我們將使用 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)
financial_df = get_financial_data(sp500_symbols)
# 去除缺失值
financial_df.dropna(inplace=True)
criteria_a = financial_df[
(financial_df['PE'] < 15) &
(financial_df['DividendYield'] > 0.02)
]
criteria_b = financial_df[
(financial_df['ROE'] > 0.15) &
(financial_df['DebtToEquity'] < 50) # 注意:DebtToEquity 通常以百分比表示
]
巴菲特的選股邏輯包括:
我們將簡化為:
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)
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)
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()
希望通過本課的學習,能夠掌握基本面篩選股票的方法,並能夠根據不同的投資策略進行比較和選擇。
# 安裝必要的庫
!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
可能會遇到缺失數據的情況,請注意處理。Hi, portfolio 都是NAN,能否修改程式碼,感恩(,,・ω・,,)
youiHi 你好,首先感謝你青睞這個系列,但當初這個系列在撰寫時因為是每日要繳交一篇所以過於匆忙,內容上可能有些不盡完美以及舉例可能有欠妥部分。後續有時間會在講整個系列整理一次!
另外你提及全部都是 Nan 的部分目前有上去簡單修整應該現在不是 NAN。最後再次感謝你關注這個系列!也歡迎多多推廣出去!任何鼓勵都是持續更新的最大動力!