分享 Kevin Boller如何利用一些非常有用的在線資源,Yahoo Finance API
(需要解決,并且可能需要替換未來的數(shù)據(jù)源)和Jupyter notebook
,以在很大程度上自動跟蹤和衡量股票投資組合相關情況 。
PME概述和個股票表現(xiàn)
簡述背景,作者從2002年開始投資他的股票投資組合,并在幾年前為其投資組合開發(fā)了一個財務模型。多年來,他會下載歷史價格,并將數(shù)據(jù)加載到財務模型中-雖然在線經(jīng)紀人計算已實現(xiàn)和未實現(xiàn)的回報,以及收入和股息,但在他進行自己的分析以評估時,喜歡在模型中包含歷史數(shù)據(jù)。他說有一種觀點/報告是從未從在線經(jīng)紀人和服務中找到的,那就是類似于“公開市場”的分析。簡而言之,公開市場等值(PME)是私募股權(quán)行業(yè)用來比較私募股權(quán)基金相對于行業(yè)基準表現(xiàn)的一組分析。具體參考https://docs.preqin.com/reports/Preqin-Special-Report-PME-July-2015.pdf。
與此相關的是,絕大多數(shù)股票投資組合經(jīng)理無法選擇長期表現(xiàn)優(yōu)于大盤的股票組合,例如標準普爾500指數(shù)(約每20只積極管理的國內(nèi)基金中就有1只優(yōu)于指數(shù)基金)。即使一些個股表現(xiàn)優(yōu)異,其他個股表現(xiàn)不佳的股票往往會超過表現(xiàn)較好的股票,這意味著總體而言,投資者的境況比簡單地投資指數(shù)基金更糟糕。在商學院期間,作者了解了PME,在評估他當時持有的公共股票時,他在概念上也加入了類似的分析。要恰當?shù)刈龅竭@一點,我們應該衡量每個投資組合頭寸(持有期)相對于同一持有期內(nèi)標準普爾500指數(shù)等值美元投資的具體投資流入時間。舉個例子,如果你在2016年6月1日購買了一只股票,而且你仍然擁有它,你會想要將這只股票在那段時間的回報與2016年6月1日在標準普爾500指數(shù)(我們的基準例子)中同等美元投資的回報進行比較。在其他方面,你可能會發(fā)現(xiàn),即使一只股票表現(xiàn)相對較好,它在同一時間段內(nèi)仍可能落后于標準普爾500指數(shù)的回報。
過去,作者從雅虎財經(jīng)(Yahoo Finance)下載歷史價格數(shù)據(jù),并使用EXCEL中的指數(shù)和匹配函數(shù)來計算每個頭寸與標準普爾500指數(shù)的相對持有期表現(xiàn)。雖然這是實現(xiàn)這一目標的一種不錯的方式,但使用Jupyter notebook
中的pandas
進行同樣的操作更具伸縮性和可擴展性。無論何時下載新數(shù)據(jù)并加載到excel中,都不可避免地需要修改一些公式并驗證錯誤。使pandas
,添加新的計算,例如累計ROI倍數(shù)(后面將介紹),幾乎不需要任何時間就能實現(xiàn)。并且使用Plotly
的可視化效果具有很高的重現(xiàn)性,在預測方面也更有用。
聲明:這篇文章中的任何內(nèi)容都不應被視為投資建議。過去的表現(xiàn)不一定預示著未來的回報。這些是關于如何使用
pandas
導入不同時間間隔內(nèi)的一小部分股票樣本的數(shù)據(jù),以及如何根據(jù)指數(shù)對它們的個人表現(xiàn)進行基準測試的一般示例。你應該把所有與投資有關的問題都告訴你的財務顧問。
除了貢獻本教程之外,作者還在繼續(xù)修改和構(gòu)建此方法,并在本文末尾概述了進一步開發(fā)的一些注意事項。作者相信這個帖子會對面向數(shù)據(jù)科學的初學者和中級金融專業(yè)人員有幫助,特別是因為這應該延伸到許多其他類型的金融分析。這種方法在某種意義上是“類似PME的”,即它衡量的是相等持有期內(nèi)的投資流入。由于公開市場投資的流動性比私募股權(quán)高得多,假設你遵循的是落后止損方法,從作者的角度來看,更重要的是專注于積極持股-通常情況下,明智的做法是剝離表現(xiàn)遜于基準的持股,或者你出于各種原因不再想持有的持股,作者主張著眼長遠,只要他們表現(xiàn)優(yōu)異,作者就很高興長期持有這些份額的股票。
資源:作者現(xiàn)在是DataCamp的訂閱者(未來的帖子將在DataCamp上發(fā)布),他說這個關于Python for Finance的社區(qū)教程非常棒。
作者已經(jīng)為這篇文章創(chuàng)建了一個repo,包括這里的Python筆記本和這里的excel文件。
如果你想看到完整的交互式版本(因為jupyter <←>> gihub集成非常棒),你可以在這里使用nbviewer查看。https://nbviewer.jupyter.org/github/kdboller/pythonsp500/blob/b45eb79daf15d3c3032a8f398c207c9d2721ac19/Investment%20Portfolio%20Python%20Notebook_03_2018_blog%20example.ipynb
我們要完成的大綱:
導入標準普爾500指數(shù)和樣本股票代碼數(shù)據(jù),使用雅虎財經(jīng)API創(chuàng)建一個合并的投資組合‘主’文件,該文件將樣本投資組合數(shù)據(jù)與歷史股票代碼和歷史標準普爾500指數(shù)數(shù)據(jù)相結(jié)合,確定每項投資的收購日期標準普爾500指數(shù)的收盤價是多少,這使我們可以計算標準普爾500指數(shù)的等值股票position與相同美元投資的股票position計算投資組合position與標準普爾500指數(shù)在這段時間內(nèi)的相對百分比和美元價值回報,計算累計投資組合回報和ROI倍數(shù)。
為了評估這個示例投資組合與市場指數(shù)相比有多好,一個更重要的項目:動態(tài)計算每個position相對于落后止損的表現(xiàn),例如,如果一個position收盤比收盤高點低25%,考慮在下一個交易日賣出該position。
可視化
總回報比較-隨著時間的推移,每個position相對于指數(shù)基準累計回報的百分比回報-每個position相對于基準累積投資的美元收益/(虧損)
-鑒于上述情況,總體投資回報與標準普爾500指數(shù)投資的同等權(quán)重和時間段相比如何?調(diào)整后的高收盤價百分比比較-每個position最近的收盤價相對于其自買入以來的調(diào)整后收盤價是多少?
Data Import和Dataframe操作
從導入必要的Python庫開始,導入Plotly offline模塊,然后讀入我們的示例項目組合DataFrame。
# Import initial libraries
import pandas as pd
import numpy as np
import datetime
import matplotlib.pyplot as plt
import plotly.graph_objs as go
%matplotlib inline
# Imports in order to be able to use Plotly offline.
from plotly import __version__
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
print(__version__) # requires version >= 1.9.0
init_notebook_mode(connected=True)
# Import the Sample worksheet with acquisition dates and initial cost basis:
portfolio_df = pd.read_excel('Sample stocks acquisition dates_costs.xlsx', sheet_name='Sample')
portfolio_df.head(10)
2020410訂正:sheetname 改為 sheet_name
,可能由于時間過去2年,函數(shù)改了參數(shù)。上面在.xlsx文件下載鏈接:https://github.com/kdboller/pythonsp500
現(xiàn)在您已經(jīng)閱讀了示例投資組合文件,您將創(chuàng)建一些變量來捕捉標準普爾500指數(shù)和所有投資組合的股票代碼的日期范圍。請注意,這是此notebook為數(shù)不多的需要每周更新的方面之一(調(diào)整日期范圍以包括最近的交易周-在此,我們將此價格運行到2018年3月9日)。
# Date Ranges for SP 500 and for all tickers
# Modify these date ranges each week.
# The below will pull back stock prices from the start date until end date specified.
start_sp = datetime.datetime(2013, 1, 1)
end_sp = datetime.datetime(2018, 3, 9)
# This variable is used for YTD performance.
end_of_last_year = datetime.datetime(2017, 12, 29)
# These are separate if for some reason want different date range than SP.
stocks_start = datetime.datetime(2013, 1, 1)
stocks_end = datetime.datetime(2018, 3, 9)
正如Python Finance培訓帖子中所提到的,pandas-datareader
包使我們能夠讀取來自Google, Yahoo! Finance and the World Bank。在這里,作者將重點介紹雅虎金融(Yahoo! Finance),雖然他已經(jīng)與Quantopian
進行了非常初步的合作,也開始將quandl
作為數(shù)據(jù)源進行研究。正如DataCamp帖子中也提到的,Yahoo API端點最近發(fā)生了更改,這需要安裝臨時修復程序才能支持Yahoo! Finance投入到工作中去。作者已經(jīng)在下面的代碼中做了需要的細微調(diào)整。作者注意到一些小的數(shù)據(jù)問題,數(shù)據(jù)并不總是像預期的那樣讀入,或者有時會錯過最后一個交易日。雖然這些問題相對較少發(fā)生,但他正在繼續(xù)關注Yahoo! Finance并且認為將是未來最好和最可靠的數(shù)據(jù)來源。
# Leveraged from the helpful Datacamp Python Finance trading blog post.
from pandas_datareader import data as pdr
import fix_yahoo_finance as yf
yf.pdr_override() # <== that's all it takes :-)
sp500 = pdr.get_data_yahoo('^GSPC',
start_sp,
end_sp)
sp500.head()
訂正
:
*** `fix_yahoo_finance` was renamed to `yfinance`. ***
Please install and use `yfinance` directly using `pip install yfinance -U`
More information: https://github.com/ranaroussi/yfinance
如果你帶著你自己的notebook,一旦你成功地從雅虎的API讀取了數(shù)據(jù),你應該會看到類似下面的內(nèi)容:
加載標準普爾500數(shù)據(jù)后,您將看到作者檢查了dataframe的頭部和尾部,并將數(shù)據(jù)壓縮為只包括Adj Close列。Adjusted Close與Close columns之間的區(qū)別在于,Adjusted Close會反映紅利(請參閱下面的未來開發(fā)區(qū)域)。當一家公司發(fā)放股息時,股價會減去每股股息的大小,因為公司正在分配公司收益的一部分。出于此分析的目的,您只需要分析此列。作者還創(chuàng)建了一個dataframe,其中只包括標準普爾指數(shù)在2017年最后一天(2018年初)adjusted close;這是為了運行單個股票相對于標準普爾500指數(shù)表現(xiàn)的YTD比較。
在下面的代碼中,您將創(chuàng)建示例投資組合dataframe中所有自動收報機的數(shù)組。然后編寫一個函數(shù),將所有的股票代碼及其相關數(shù)據(jù)讀入一個新的dataframe中,這基本上與您對標準普爾500指數(shù)采取的方法相同,但適用于投資組合的所有股票代碼。
# Generate a dynamic list of tickers to pull from Yahoo Finance API based on the imported file with tickers.
tickers = portfolio_df['Ticker'].unique()
tickers
# Stock comparison code
def get(tickers, startdate, enddate):
def data(ticker):
return (pdr.get_data_yahoo(ticker, start=startdate, end=enddate))
datas = map(data, tickers)
return(pd.concat(datas, keys=tickers, names=['Ticker', 'Date']))
all_data = get(tickers, stocks_start, stocks_end)
與標準普爾500dataframe一樣,您將創(chuàng)建一個adj_close
dataframe,它只包含所有股票代碼的Adj Closecolumn
。如果您查看上面作者鏈接到的repo中的notebook,您會發(fā)現(xiàn)這段代碼包含的代碼塊比下面顯示的更多。為了在這里描述這一點,作者在下面包含了通向初始merged_portfolio
dataframe的所有代碼。
# Also only pulling the ticker, date and adj. close columns for our tickers.
adj_close = all_data[['Adj Close']].reset_index()
adj_close.head()
# Grabbing the ticker close from the end of last year
adj_close_start = adj_close[adj_close['Date']==end_of_last_year]
adj_close_start.head()
# Grab the latest stock close price
adj_close_latest = adj_close[adj_close['Date']==stocks_end]
adj_close_latest
adj_close_latest.set_index('Ticker', inplace=True)
adj_close_latest.head()
# Set portfolio index prior to merging with the adj close latest.
portfolio_df.set_index(['Ticker'], inplace=True)
portfolio_df.head()
# Merge the portfolio dataframe with the adj close dataframe; they are being joined by their indexes.
merged_portfolio = pd.merge(portfolio_df, adj_close_latest, left_index=True, right_index=True)
merged_portfolio.head()
# The below creates a new column which is the ticker return; takes the latest adjusted close for each position
# and divides that by the initial share cost.
merged_portfolio['ticker return'] = merged_portfolio['Adj Close'] / merged_portfolio['Unit Cost'] - 1
merged_portfolio
這取決于你對pandas的熟悉程度,這將是非常直接的到稍微令人不知所措的。下面,作者解釋了這些命令行在做的事情:
- The overall approach you are taking is an example of split-apply-combine (note this downloads a PDF).
- The
all_data[['Adj Close']]
line creates a new dataframe with only the columns provided in the list; hereAdj Close
is the only item provided in the list. - Using this line of code,
adj_close[adj_close['Date']==end_of_last_year]
, you are filtering theadj_close
dataframe to only the row where the data’sDate
column equals the date which you earlier specified in theend_of_last_year
variable (2017, 12, 29). - You also set the index of the
adj_close_latest
andportfolio_df
dataframes. I did this because this is how you’ll merge the two dataframes. Themerge
function, very similar to SQL joins, is an extremely useful function which I use very often. - Within the
merge
function, you specify the left dataframe (portfolio_df
) and our right dataframe (adj_close_latest
). By specifyingleft_index
andright_index
equal True, you are stating that the two dataframes share a common index and you will join both on this. - Last, you create a new column called
'ticker return'
. This calculates the percent return for each stock position by dividing theAdj Close
by theUnit Cost
(initial purchase price for stock) and subtracting 1. This is similar to calculating a formula in excel and carrying it down, but inpandas
this is accomplished with one-line of code.
你已經(jīng)獲得了標準普爾500指數(shù)和個股的單個數(shù)據(jù)框,你開始開發(fā)一個‘master’ dataframe,我們將使用它來進行計算、可視化和任何進一步的分析。接下來,將繼續(xù)在這個‘master’ dataframe上構(gòu)建,并進一步使用pandas merge 函數(shù)。下面,將重置當前dataframe的索引,并開始將較小的dataframe與‘master’ dataframe連接起來。下面的代碼塊再次在Jupyter notebook中進一步展開;在這里,作者采取與前面類似的方法,將共享下面的代碼,然后分解代碼塊下面的關鍵標注。
merged_portfolio.reset_index(inplace=True)
# Here we are merging the new dataframe with the sp500 adjusted closes since the sp start price based on
# each ticker's acquisition date and sp500 close date.
merged_portfolio_sp = pd.merge(merged_portfolio, sp_500_adj_close, left_on='Acquisition Date', right_on='Date')
# .set_index('Ticker')
# We will delete the additional date column which is created from this merge.
# We then rename columns to Latest Date and then reflect Ticker Adj Close and SP 500 Initial Close.
del merged_portfolio_sp['Date_y']
merged_portfolio_sp.rename(columns={'Date_x': 'Latest Date', 'Adj Close_x': 'Ticker Adj Close'
, 'Adj Close_y': 'SP 500 Initial Close'}, inplace=True)
# This new column determines what SP 500 equivalent purchase would have been at purchase date of stock.
merged_portfolio_sp['Equiv SP Shares'] = merged_portfolio_sp['Cost Basis'] / merged_portfolio_sp['SP 500 Initial Close']
merged_portfolio_sp.head()
# We are joining the developing dataframe with the sp500 closes again, this time with the latest close for SP.
merged_portfolio_sp_latest = pd.merge(merged_portfolio_sp, sp_500_adj_close, left_on='Latest Date', right_on='Date')
# Once again need to delete the new Date column added as it's redundant to Latest Date.
# Modify Adj Close from the sp dataframe to distinguish it by calling it the SP 500 Latest Close.
del merged_portfolio_sp_latest['Date']
merged_portfolio_sp_latest.rename(columns={'Adj Close': 'SP 500 Latest Close'}, inplace=True)
merged_portfolio_sp_latest.head()
- 可以在
merged_portfolio
上使用reset_index
,以便扁平化主數(shù)據(jù)框并連接較小的數(shù)據(jù)框的相關列。 - 在
merged_portfolio_sp
行中,您可以將當前主數(shù)據(jù)框(merged_portfolio)與sp_500_adj_close合并;這樣做是為了使標準普爾在每個position的采購日期的收盤價-這允許您跟蹤每個position持有的同一時間期(從收購日期到最近的市場收盤日期)的標準普爾業(yè)績。 - 這里的合并與以前略有不同,因為我們在左側(cè)數(shù)據(jù)框的
Acquisition Date
列和右側(cè)數(shù)據(jù)框的Date
列上連接。 - 完成此合并后,您將擁有不需要的額外列-由于我們的主數(shù)據(jù)框最終將有相當數(shù)量的列用于分析,因此在此過程中刪除重復和不必要的列非常重要。
- 有幾種方法可以刪除不必要的列并執(zhí)行各種列名清理;為簡單起見,作者使用
python del
,然后使用pandasrename
方法重命名幾個列,通過重命名為Ticker Adj Close
來澄清股票代碼的Adj Close
列;您可以使用SP 500 Initial Close
來區(qū)分標準普爾指數(shù)的初始調(diào)整收盤。 - 在計算
merged_portfolio_sp['Equiv SP Shares']
時,這樣做是為了能夠計算標準普爾500指數(shù)在收購每個股票position的當天收盤時的等價值:如果你花5,000美元購買一個新的股票position,你可以花5,000美元購買標準普爾500指數(shù);繼續(xù)這個例子,如果標準普爾500指數(shù)在購買時的交易價格是每股2,500美元,你就可以購買2股股票。稍后,如果標準普爾500指數(shù)的交易價格為每股3,000美元,你的股份將價值6,000美元(2股等值股票*每股3,000美元),在這段可比的時間段內(nèi),你將獲得1,000美元的賬面利潤。 - 在代碼塊的其余部分,接下來執(zhí)行類似的合并,這一次加入了標準普爾500指數(shù)的最新收盤價-這提供了計算標準普爾500指數(shù)相對于每個position持有期的可比回報所需的第二個部分:每個股票收購日的標準普爾500指數(shù)價格和標準普爾500指數(shù)的最新收盤價。
現(xiàn)在,您已經(jīng)使用以下內(nèi)容進一步開發(fā)了您的主數(shù)據(jù)框:
- 每個投資組合position的價格、股票和position收購日的價值,以及最新的市場收盤價。
- 每個股票的等值position收購日的等值標準普爾500指數(shù)價格、股票和價值,以及標準普爾500指數(shù)的最新收盤價。
鑒于上述情況,接下來您將執(zhí)行必要的計算,以相對于標準普爾500指數(shù)的可比美元投資和持有時間,比較每個position的表現(xiàn),以及這一策略/一籃子股票的整體表現(xiàn)。
下面是您要添加到“主”數(shù)據(jù)框中的新列的摘要。
- 在第一列
['SP Return']
中,您創(chuàng)建了一個列,該列計算標準普爾500指數(shù)在每個position持有期間的絕對百分比回報(注意,這是絕對回報,而不是年化回報)。在第二列(['Abs. Return Compare']
)中,您將在同一時間段內(nèi)將['ticker return']
(每個位置的回報)與['SP Return']
進行比較。 - 在接下來的三欄
['Ticker Share Value'], ['SP 500 Value'] and ['Abs Value Compare']
中,我們根據(jù)我們持有的股票乘以最新調(diào)整后的收盤價來計算美元價值(市值)等值(并從股票代碼中減去標準普爾回報,以計算超出/(低于)業(yè)績)。 - 最后,
['Stock Gain / (Loss)']
和['SP 500 Gain / (Loss)']
列計算我們每個position的未實現(xiàn)美元收益/虧損和可比的標準普爾500收益/虧損;這使我們能夠比較每個position的價值影響與簡單地將這些美元投資于標準普爾500指數(shù)。
# Percent return of SP from acquisition date of position through latest trading day.
merged_portfolio_sp_latest['SP Return'] = merged_portfolio_sp_latest['SP 500 Latest Close'] / merged_portfolio_sp_latest['SP 500 Initial Close'] - 1
# This is a new column which takes the tickers return and subtracts the sp 500 equivalent range return.
merged_portfolio_sp_latest['Abs. Return Compare'] = merged_portfolio_sp_latest['ticker return'] - merged_portfolio_sp_latest['SP Return']
# This is a new column where we calculate the ticker's share value by multiplying the original quantity by the latest close.
merged_portfolio_sp_latest['Ticker Share Value'] = merged_portfolio_sp_latest['Quantity'] * merged_portfolio_sp_latest['Ticker Adj Close']
# We calculate the equivalent SP 500 Value if we take the original SP shares * the latest SP 500 share price.
merged_portfolio_sp_latest['SP 500 Value'] = merged_portfolio_sp_latest['Equiv SP Shares'] * merged_portfolio_sp_latest['SP 500 Latest Close']
# This is a new column where we take the current market value for the shares and subtract the SP 500 value.
merged_portfolio_sp_latest['Abs Value Compare'] = merged_portfolio_sp_latest['Ticker Share Value'] - merged_portfolio_sp_latest['SP 500 Value']
# This column calculates profit / loss for stock position.
merged_portfolio_sp_latest['Stock Gain / (Loss)'] = merged_portfolio_sp_latest['Ticker Share Value'] - merged_portfolio_sp_latest['Cost Basis']
# This column calculates profit / loss for SP 500.
merged_portfolio_sp_latest['SP 500 Gain / (Loss)'] = merged_portfolio_sp_latest['SP 500 Value'] - merged_portfolio_sp_latest['Cost Basis']
merged_portfolio_sp_latest.head()
現(xiàn)在你有了需要的東西,可以將你的投資組合的表現(xiàn)與同等投資于標準普爾500指數(shù)的投資組合進行比較。接下來的兩個代碼塊部分允許您:i)比較每個position相對于標準普爾500指數(shù)的YTD表現(xiàn)(衡量動能和您的position的走勢);ii)比較每個投資組合position的最新收盤價與其最近的收盤高點(這允許您評估position是否觸發(fā)了跟蹤止損,例如,收盤時比收盤高點低25%)。
下面,作者將從YTD performance代碼塊開始,并在下面進一步提供有關代碼的詳細信息。
# Merge the overall dataframe with the adj close start of year dataframe for YTD tracking of tickers.
merged_portfolio_sp_latest_YTD = pd.merge(merged_portfolio_sp_latest, adj_close_start, on='Ticker')
# , how='outer'
# Deleting date again as it's an unnecessary column. Explaining that new column is the Ticker Start of Year Close.
del merged_portfolio_sp_latest_YTD['Date']
merged_portfolio_sp_latest_YTD.rename(columns={'Adj Close': 'Ticker Start Year Close'}, inplace=True)
# Join the SP 500 start of year with current dataframe for SP 500 ytd comparisons to tickers.
merged_portfolio_sp_latest_YTD_sp = pd.merge(merged_portfolio_sp_latest_YTD, sp_500_adj_close_start
, left_on='Start of Year', right_on='Date')
# Deleting another unneeded Date column.
del merged_portfolio_sp_latest_YTD_sp['Date']
# Renaming so that it's clear this column is SP 500 start of year close.
merged_portfolio_sp_latest_YTD_sp.rename(columns={'Adj Close': 'SP Start Year Close'}, inplace=True)
# YTD return for portfolio position.
merged_portfolio_sp_latest_YTD_sp['Share YTD'] = merged_portfolio_sp_latest_YTD_sp['Ticker Adj Close'] / merged_portfolio_sp_latest_YTD_sp['Ticker Start Year Close'] - 1
# YTD return for SP to run compares.
merged_portfolio_sp_latest_YTD_sp['SP 500 YTD'] = merged_portfolio_sp_latest_YTD_sp['SP 500 Latest Close'] / merged_portfolio_sp_latest_YTD_sp['SP Start Year Close'] - 1
- 在創(chuàng)建
merged_portfolio_sp_latest_YTD
數(shù)據(jù)框時,您現(xiàn)在正在將主數(shù)據(jù)框與adj_close_start
數(shù)據(jù)框合并;作為提醒,您是通過篩選adj_close
數(shù)據(jù)框創(chuàng)建此數(shù)據(jù)框的,其中'Date'
列等于變量end_of_last_year
;這樣做是因為YTD(年初至今)股票和指數(shù)表現(xiàn)是通過它來衡量的;去年的結(jié)束收盤價是下一年的結(jié)束收盤價;您這樣做是因為它是衡量YTD(年初至今)股票和指數(shù)表現(xiàn)的方式;去年的結(jié)束收盤價就是下一年的結(jié)束收盤價;您這樣做是因為它是衡量YTD(年初至今)股票和指數(shù)表現(xiàn)的方式;去年的結(jié)束收盤價是次年的結(jié)束日期. - 在這里,我們再次使用
del
刪除不必要的列,并使用rename
方法來澄清主數(shù)據(jù)框新添加的列。 - 最后,我們提取每個Ticker(在
['Ticker Adj Close']
列中),并計算每個Ticker的YTD回報(SP 500 Latest Close
列中的每個值也有一個標準普爾500等效值)。
在下面的代碼塊中,您使用sort_values
方法對‘主’數(shù)據(jù)框重新排序,然后計算累計投資組合投資(持倉成本之和),以及投資組合頭寸的累計價值和理論標準普爾500指數(shù)投資的累計價值。這使你能夠看到你的總投資組合(在整個時期的不同時間對頭寸進行的投資)與簡單地投資于指數(shù)的策略進行了整體比較。稍后,您將使用['Cum Ticker ROI Mult']
來幫助您可視化每項投資對總體投資回報(ROI)的貢獻或減少程度。
merged_portfolio_sp_latest_YTD_sp = merged_portfolio_sp_latest_YTD_sp.sort_values(by='Ticker', ascending=True)
# Cumulative sum of original investment
merged_portfolio_sp_latest_YTD_sp['Cum Invst'] = merged_portfolio_sp_latest_YTD_sp['Cost Basis'].cumsum()
# Cumulative sum of Ticker Share Value (latest FMV based on initial quantity purchased).
merged_portfolio_sp_latest_YTD_sp['Cum Ticker Returns'] = merged_portfolio_sp_latest_YTD_sp['Ticker Share Value'].cumsum()
# Cumulative sum of SP Share Value (latest FMV driven off of initial SP equiv purchase).
merged_portfolio_sp_latest_YTD_sp['Cum SP Returns'] = merged_portfolio_sp_latest_YTD_sp['SP 500 Value'].cumsum()
# Cumulative CoC multiple return for stock investments
merged_portfolio_sp_latest_YTD_sp['Cum Ticker ROI Mult'] = merged_portfolio_sp_latest_YTD_sp['Cum Ticker Returns'] / merged_portfolio_sp_latest_YTD_sp['Cum Invst']
merged_portfolio_sp_latest_YTD_sp.head()
您現(xiàn)在已經(jīng)接近最后沖刺階段,幾乎準備好開始可視化您的數(shù)據(jù),并評估您的投資組合的單個報價器和整體策略性能的優(yōu)勢和劣勢。
與前面一樣,作者已經(jīng)包含了主代碼塊,用于確定倉位相對于最近收盤高點的交易位置;然后,并將在下面進一步解釋代碼。
# Need to factor in that some positions were purchased much more recently than others.
# Join adj_close dataframe with portfolio in order to have acquisition date.
portfolio_df.reset_index(inplace=True)
adj_close_acq_date = pd.merge(adj_close, portfolio_df, on='Ticker')
# delete_columns = ['Quantity', 'Unit Cost', 'Cost Basis', 'Start of Year']
del adj_close_acq_date['Quantity']
del adj_close_acq_date['Unit Cost']
del adj_close_acq_date['Cost Basis']
del adj_close_acq_date['Start of Year']
# Sort by these columns in this order in order to make it clearer where compare for each position should begin.
adj_close_acq_date.sort_values(by=['Ticker', 'Acquisition Date', 'Date'], ascending=[True, True, True], inplace=True)
# Anything less than 0 means that the stock close was prior to acquisition.
adj_close_acq_date['Date Delta'] = adj_close_acq_date['Date'] - adj_close_acq_date['Acquisition Date']
adj_close_acq_date['Date Delta'] = adj_close_acq_date[['Date Delta']].apply(pd.to_numeric)
# Modified the dataframe being evaluated to look at highest close which occurred after Acquisition Date (aka, not prior to purchase).
adj_close_acq_date_modified = adj_close_acq_date[adj_close_acq_date['Date Delta']>=0]
# This pivot table will index on the Ticker and Acquisition Date, and find the max adjusted close.
adj_close_pivot = adj_close_acq_date_modified.pivot_table(index=['Ticker', 'Acquisition Date'], values='Adj Close', aggfunc=np.max)
adj_close_pivot.reset_index(inplace=True)
# Merge the adj close pivot table with the adj_close table in order to grab the date of the Adj Close High (good to know).
adj_close_pivot_merged = pd.merge(adj_close_pivot, adj_close
, on=['Ticker', 'Adj Close'])
# Merge the Adj Close pivot table with the master dataframe to have the closing high since you have owned the stock.
merged_portfolio_sp_latest_YTD_sp_closing_high = pd.merge(merged_portfolio_sp_latest_YTD_sp, adj_close_pivot_merged
, on=['Ticker', 'Acquisition Date'])
# Renaming so that it's clear that the new columns are closing high and closing high date.
merged_portfolio_sp_latest_YTD_sp_closing_high.rename(columns={'Adj Close': 'Closing High Adj Close', 'Date': 'Closing High Adj Close Date'}, inplace=True)
merged_portfolio_sp_latest_YTD_sp_closing_high['Pct off High'] = merged_portfolio_sp_latest_YTD_sp_closing_high['Ticker Adj Close'] / merged_portfolio_sp_latest_YTD_sp_closing_high['Closing High Adj Close'] - 1
merged_portfolio_sp_latest_YTD_sp_closing_high
這是一個相當重大的提升,現(xiàn)在是我們期待已久的可視化的時候了。如果您在自己的筆記本中繼續(xù)跟蹤,您現(xiàn)在就有了一個非常豐富的數(shù)據(jù)框架,其中包含許多經(jīng)過計算的投資組合指標,如下圖所示:
可視化
# Ploty is an outstanding resource for interactive charts.
trace1 = go.Bar(
x = merged_portfolio_sp_latest_YTD_sp['Ticker'][0:10],
y = merged_portfolio_sp_latest_YTD_sp['Share YTD'][0:10],
name = 'Ticker YTD')
trace2 = go.Scatter(
x = merged_portfolio_sp_latest_YTD_sp['Ticker'][0:10],
y = merged_portfolio_sp_latest_YTD_sp['SP 500 YTD'][0:10],
name = 'SP500 YTD')
data = [trace1, trace2]
layout = go.Layout(title = 'YTD Return vs S&P 500 YTD'
, barmode = 'group'
, yaxis=dict(title='Returns', tickformat=".2%")
, xaxis=dict(title='Ticker')
, legend=dict(x=.8,y=1)
)
fig = go.Figure(data=data, layout=layout)
iplot(fig)
# Current Share Price versus Closing High Since Purchased
trace1 = go.Bar(
x = merged_portfolio_sp_latest_YTD_sp_closing_high['Ticker'][0:10],
y = merged_portfolio_sp_latest_YTD_sp_closing_high['Pct off High'][0:10],
name = 'Pct off High')
data = [trace1]
layout = go.Layout(title = 'Adj Close % off of High'
, barmode = 'group'
, yaxis=dict(title='% Below Adj Close High', tickformat=".2%")
, xaxis=dict(title='Ticker')
, legend=dict(x=.8,y=1)
)
fig = go.Figure(data=data, layout=layout)
iplot(fig)
Total Return Comparison Charts
trace1 = go.Bar(
x = merged_portfolio_sp_latest_YTD_sp_closing_high['Ticker'][0:10],
y = merged_portfolio_sp_latest_YTD_sp_closing_high['ticker return'][0:10],
name = 'Ticker Total Return')
trace2 = go.Scatter(
x = merged_portfolio_sp_latest_YTD_sp_closing_high['Ticker'][0:10],
y = merged_portfolio_sp_latest_YTD_sp_closing_high['SP Return'][0:10],
name = 'SP500 Total Return')
data = [trace1, trace2]
layout = go.Layout(title = 'Total Return vs S&P 500'
, barmode = 'group'
, yaxis=dict(title='Returns', tickformat=".2%")
, xaxis=dict(title='Ticker', tickformat=".2%")
, legend=dict(x=.8,y=1)
)
fig = go.Figure(data=data, layout=layout)
iplot(fig)
Cumulative Returns Over Time
trace1 = go.Bar(
x = merged_portfolio_sp_latest_YTD_sp_closing_high['Ticker'][0:10],
y = merged_portfolio_sp_latest_YTD_sp_closing_high['Stock Gain / (Loss)'][0:10],
name = 'Ticker Total Return ($)')
trace2 = go.Bar(
x = merged_portfolio_sp_latest_YTD_sp_closing_high['Ticker'][0:10],
y = merged_portfolio_sp_latest_YTD_sp_closing_high['SP 500 Gain / (Loss)'][0:10],
name = 'SP 500 Total Return ($)')
trace3 = go.Scatter(
x = merged_portfolio_sp_latest_YTD_sp_closing_high['Ticker'][0:10],
y = merged_portfolio_sp_latest_YTD_sp_closing_high['ticker return'][0:10],
name = 'Ticker Total Return %',
yaxis='y2')
data = [trace1, trace2, trace3]
layout = go.Layout(title = 'Gain / (Loss) Total Return vs S&P 500'
, barmode = 'group'
, yaxis=dict(title='Gain / (Loss) ($)')
, yaxis2=dict(title='Ticker Return', overlaying='y', side='right', tickformat=".2%")
, xaxis=dict(title='Ticker')
, legend=dict(x=.75,y=1)
)
fig = go.Figure(data=data, layout=layout)
iplot(fig)
trace1 = go.Bar(
x = merged_portfolio_sp_latest_YTD_sp_closing_high['Ticker'],
y = merged_portfolio_sp_latest_YTD_sp_closing_high['Cum Invst'],
# mode = 'lines+markers',
name = 'Cum Invst')
trace2 = go.Bar(
x = merged_portfolio_sp_latest_YTD_sp_closing_high['Ticker'],
y = merged_portfolio_sp_latest_YTD_sp_closing_high['Cum SP Returns'],
# mode = 'lines+markers',
name = 'Cum SP500 Returns')
trace3 = go.Bar(
x = merged_portfolio_sp_latest_YTD_sp_closing_high['Ticker'],
y = merged_portfolio_sp_latest_YTD_sp_closing_high['Cum Ticker Returns'],
# mode = 'lines+markers',
name = 'Cum Ticker Returns')
trace4 = go.Scatter(
x = merged_portfolio_sp_latest_YTD_sp_closing_high['Ticker'],
y = merged_portfolio_sp_latest_YTD_sp_closing_high['Cum Ticker ROI Mult'],
# mode = 'lines+markers',
name = 'Cum ROI Mult'
, yaxis='y2')
data = [trace1, trace2, trace3, trace4]
layout = go.Layout(title = 'Total Cumulative Investments Over Time'
, barmode = 'group'
, yaxis=dict(title='Returns')
, xaxis=dict(title='Ticker')
, legend=dict(x=.4,y=1)
, yaxis2=dict(title='Cum ROI Mult', overlaying='y', side='right')
)
fig = go.Figure(data=data, layout=layout)
iplot(fig)
References:
https://www.datacamp.com/community/tutorials/finance-python-trading
https://pypi.python.org/pypi/fix-yahoo-finance
http://www.learndatasci.com/python-finance-part-yahoo-finance-api-pandas-matplotlib/