├── Explainer_Notebook.ipynb
├── README.md
├── portfolio_overview.py
├── portfolio_overview.xlsm
└── requirements.txt
/Explainer_Notebook.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# 📈 Pull Data From Yahoo Finance"
8 | ]
9 | },
10 | {
11 | "cell_type": "markdown",
12 | "metadata": {
13 | "heading_collapsed": true
14 | },
15 | "source": [
16 | "# Imports"
17 | ]
18 | },
19 | {
20 | "cell_type": "code",
21 | "execution_count": 2,
22 | "metadata": {
23 | "hidden": true
24 | },
25 | "outputs": [],
26 | "source": [
27 | "!pip install pip install yahoofinancials --quiet\n",
28 | "!pip install pandas --quiet"
29 | ]
30 | },
31 | {
32 | "cell_type": "code",
33 | "execution_count": 3,
34 | "metadata": {
35 | "hidden": true
36 | },
37 | "outputs": [],
38 | "source": [
39 | "from yahoofinancials import YahooFinancials\n",
40 | "import pandas as pd"
41 | ]
42 | },
43 | {
44 | "cell_type": "markdown",
45 | "metadata": {
46 | "heading_collapsed": true
47 | },
48 | "source": [
49 | "# Basic Example"
50 | ]
51 | },
52 | {
53 | "cell_type": "markdown",
54 | "metadata": {
55 | "hidden": true
56 | },
57 | "source": [
58 | "**`YahooFinancials`** is a powerful financial data module used for pulling both fundamental and technical data from Yahoo Finance.\n",
59 | ">Documentation: https://github.com/JECSand/yahoofinancials\n",
60 | "\n",
61 | "**Examples of Module Methods:**
\n",
62 | "get_daily_low()
\n",
63 | "get_daily_high()
\n",
64 | "get_currency()
\n",
65 | "get_yearly_high()
\n",
66 | "get_yearly_low()
\n",
67 | "get_dividend_yield()
\n",
68 | "...
"
69 | ]
70 | },
71 | {
72 | "cell_type": "code",
73 | "execution_count": 4,
74 | "metadata": {
75 | "hidden": true
76 | },
77 | "outputs": [],
78 | "source": [
79 | "ticker = 'TSLA'"
80 | ]
81 | },
82 | {
83 | "cell_type": "code",
84 | "execution_count": 5,
85 | "metadata": {
86 | "hidden": true
87 | },
88 | "outputs": [
89 | {
90 | "data": {
91 | "text/plain": [
92 | "641.87"
93 | ]
94 | },
95 | "execution_count": 5,
96 | "metadata": {},
97 | "output_type": "execute_result"
98 | }
99 | ],
100 | "source": [
101 | "data = YahooFinancials(ticker)\n",
102 | "data.get_open_price()"
103 | ]
104 | },
105 | {
106 | "cell_type": "code",
107 | "execution_count": 6,
108 | "metadata": {
109 | "hidden": true
110 | },
111 | "outputs": [
112 | {
113 | "data": {
114 | "text/plain": [
115 | "{'TSLA': {'previousClose': 640.39,\n",
116 | " 'regularMarketOpen': 641.87,\n",
117 | " 'twoHundredDayAverage': 602.2559,\n",
118 | " 'trailingAnnualDividendYield': None,\n",
119 | " 'payoutRatio': 0,\n",
120 | " 'volume24Hr': None,\n",
121 | " 'regularMarketDayHigh': 643.82,\n",
122 | " 'navPrice': None,\n",
123 | " 'averageDailyVolume10Day': 37012214,\n",
124 | " 'totalAssets': None,\n",
125 | " 'regularMarketPreviousClose': 640.39,\n",
126 | " 'fiftyDayAverage': 713.1397,\n",
127 | " 'trailingAnnualDividendRate': None,\n",
128 | " 'open': 641.87,\n",
129 | " 'toCurrency': None,\n",
130 | " 'averageVolume10days': 37012214,\n",
131 | " 'expireDate': '-',\n",
132 | " 'yield': None,\n",
133 | " 'algorithm': None,\n",
134 | " 'dividendRate': None,\n",
135 | " 'exDividendDate': '-',\n",
136 | " 'beta': 2.06013,\n",
137 | " 'circulatingSupply': None,\n",
138 | " 'startDate': '-',\n",
139 | " 'regularMarketDayLow': 599.9,\n",
140 | " 'priceHint': 2,\n",
141 | " 'currency': 'USD',\n",
142 | " 'trailingPE': 966.73444,\n",
143 | " 'regularMarketVolume': 33332909,\n",
144 | " 'lastMarket': None,\n",
145 | " 'maxSupply': None,\n",
146 | " 'openInterest': None,\n",
147 | " 'marketCap': 593871306752,\n",
148 | " 'volumeAllCurrencies': None,\n",
149 | " 'strikePrice': None,\n",
150 | " 'averageVolume': 35782108,\n",
151 | " 'priceToSalesTrailing12Months': 18.831535,\n",
152 | " 'dayLow': 599.9,\n",
153 | " 'ask': 621.5,\n",
154 | " 'ytdReturn': None,\n",
155 | " 'askSize': 900,\n",
156 | " 'volume': 33332909,\n",
157 | " 'fiftyTwoWeekHigh': 900.4,\n",
158 | " 'forwardPE': 112.08515,\n",
159 | " 'maxAge': 1,\n",
160 | " 'fromCurrency': None,\n",
161 | " 'fiveYearAvgDividendYield': None,\n",
162 | " 'fiftyTwoWeekLow': 89.28,\n",
163 | " 'bid': 621,\n",
164 | " 'tradeable': False,\n",
165 | " 'dividendYield': None,\n",
166 | " 'bidSize': 3000,\n",
167 | " 'dayHigh': 643.82}}"
168 | ]
169 | },
170 | "execution_count": 6,
171 | "metadata": {},
172 | "output_type": "execute_result"
173 | }
174 | ],
175 | "source": [
176 | "data.get_summary_data()"
177 | ]
178 | },
179 | {
180 | "cell_type": "markdown",
181 | "metadata": {
182 | "heading_collapsed": true
183 | },
184 | "source": [
185 | "# Create Your Own DataFrame"
186 | ]
187 | },
188 | {
189 | "cell_type": "code",
190 | "execution_count": 7,
191 | "metadata": {
192 | "hidden": true
193 | },
194 | "outputs": [
195 | {
196 | "data": {
197 | "text/html": [
198 | "
\n",
199 | "\n",
212 | "
\n",
213 | " \n",
214 | " \n",
215 | " | \n",
216 | "
\n",
217 | " \n",
218 | " \n",
219 | " \n",
220 | "
\n",
221 | "
"
222 | ],
223 | "text/plain": [
224 | "Empty DataFrame\n",
225 | "Columns: []\n",
226 | "Index: []"
227 | ]
228 | },
229 | "execution_count": 7,
230 | "metadata": {},
231 | "output_type": "execute_result"
232 | }
233 | ],
234 | "source": [
235 | "# Create an empty DataFrame\n",
236 | "df = pd.DataFrame()\n",
237 | "df"
238 | ]
239 | },
240 | {
241 | "cell_type": "code",
242 | "execution_count": 8,
243 | "metadata": {
244 | "hidden": true
245 | },
246 | "outputs": [],
247 | "source": [
248 | "tickers = ['TSLA', 'GOOG', 'MSFT']\n",
249 | "for ticker in tickers:\n",
250 | " # Pull data from YahooFinance\n",
251 | " data = YahooFinancials(ticker)\n",
252 | " open_price = data.get_open_price()\n",
253 | " currency = data.get_currency()\n",
254 | " yearly_high = data.get_yearly_high()\n",
255 | " \n",
256 | " # Create Dictonary \n",
257 | " new_row = {\n",
258 | " \"ticker\": ticker,\n",
259 | " \"open_price\": open_price,\n",
260 | " \"currency\": currency,\n",
261 | " \"yearly_high\": yearly_high,\n",
262 | " }\n",
263 | " \n",
264 | " # Append data (new row) to DataFrame\n",
265 | " df = df.append(new_row, ignore_index=True)\n"
266 | ]
267 | },
268 | {
269 | "cell_type": "code",
270 | "execution_count": 9,
271 | "metadata": {
272 | "hidden": true
273 | },
274 | "outputs": [
275 | {
276 | "data": {
277 | "text/html": [
278 | "\n",
279 | "\n",
292 | "
\n",
293 | " \n",
294 | " \n",
295 | " | \n",
296 | " currency | \n",
297 | " open_price | \n",
298 | " ticker | \n",
299 | " yearly_high | \n",
300 | "
\n",
301 | " \n",
302 | " \n",
303 | " \n",
304 | " 0 | \n",
305 | " USD | \n",
306 | " 641.87 | \n",
307 | " TSLA | \n",
308 | " 900.40 | \n",
309 | "
\n",
310 | " \n",
311 | " 1 | \n",
312 | " USD | \n",
313 | " 2038.86 | \n",
314 | " GOOG | \n",
315 | " 2152.68 | \n",
316 | "
\n",
317 | " \n",
318 | " 2 | \n",
319 | " USD | \n",
320 | " 231.55 | \n",
321 | " MSFT | \n",
322 | " 246.13 | \n",
323 | "
\n",
324 | " \n",
325 | "
\n",
326 | "
"
327 | ],
328 | "text/plain": [
329 | " currency open_price ticker yearly_high\n",
330 | "0 USD 641.87 TSLA 900.40\n",
331 | "1 USD 2038.86 GOOG 2152.68\n",
332 | "2 USD 231.55 MSFT 246.13"
333 | ]
334 | },
335 | "execution_count": 9,
336 | "metadata": {},
337 | "output_type": "execute_result"
338 | }
339 | ],
340 | "source": [
341 | "df"
342 | ]
343 | }
344 | ],
345 | "metadata": {
346 | "kernelspec": {
347 | "display_name": "Python 3",
348 | "language": "python",
349 | "name": "python3"
350 | },
351 | "language_info": {
352 | "codemirror_mode": {
353 | "name": "ipython",
354 | "version": 3
355 | },
356 | "file_extension": ".py",
357 | "mimetype": "text/x-python",
358 | "name": "python",
359 | "nbconvert_exporter": "python",
360 | "pygments_lexer": "ipython3",
361 | "version": "3.8.5"
362 | }
363 | },
364 | "nbformat": 4,
365 | "nbformat_minor": 4
366 | }
367 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Dividend & Portfolio Tracker in Excel by using Python [Free Template]
3 |
4 | In this tutorial, I will show you the Excel Dividend & Portfolio Tracker I have built for you. All you need to do is to enter the Ticker Symbol, the number of shares you own as well as your average purchase price.
5 |
6 | The Excel Spreadsheet is pulling the financial data from Yahoo Finance by using Python. It is not only limited to US stock. You could return any stock, cryptocurrency, forex, mutual fund & ETF data which is available on Yahoo Finance.
7 |
8 | In this video, I will show you:
9 | 1. how to use this template
10 | 2. how you can build your own portfolio tracker from scratch
11 | 3. and last but not least, how to customize this template to your needs and liking
12 |
13 |
14 | ## Screenshot
15 |
16 | 
17 |
18 | ## Video
19 |
20 | [](https://youtu.be/4KsP5Et_aWo)
21 |
22 |
23 |
24 | ## 🤓 Check Out My Excel Add-ins
25 | I've developed some handy Excel add-ins that you might find useful:
26 |
27 | - 📊 **[Dashboard Add-in](https://pythonandvba.com/grafly)**: Easily create interactive and visually appealing dashboards.
28 | - 🎨 **[Cartoon Charts Add-In](https://pythonandvba.com/cuteplots)**: Create engaging and fun cartoon-style charts.
29 | - 🤪 **[Emoji Add-in](https://pythonandvba.com/emojify)**: Add a touch of fun to your spreadsheets with emojis.
30 | - 🛠️ **[MyToolBelt Add-in](https://pythonandvba.com/mytoolbelt)**: A versatile toolbelt for Excel, featuring:
31 | - Creation of Pandas DataFrames and Jupyter Notebooks from Excel ranges
32 | - ChatGPT integration for advanced data analysis
33 | - And much more!
34 |
35 |
36 |
37 | ## 🤝 Connect with Me
38 | - 📺 **YouTube:** [CodingIsFun](https://youtube.com/c/CodingIsFun)
39 | - 🌐 **Website:** [PythonAndVBA](https://pythonandvba.com)
40 | - 💬 **Discord:** [Join the Community](https://pythonandvba.com/discord)
41 | - 💼 **LinkedIn:** [Sven Bosau](https://www.linkedin.com/in/sven-bosau/)
42 | - 📸 **Instagram:** [sven_bosau](https://www.instagram.com/sven_bosau/)
43 |
44 | ## ☕ Support
45 | If you appreciate the project and wish to encourage its continued development, consider [supporting my work](https://pythonandvba.com/coffee-donation).
46 | [](https://pythonandvba.com/coffee-donation)
47 |
48 | ## Feedback & Collaboration
49 | For feedback, suggestions, or potential collaboration opportunities, reach out at contact@pythonandvba.com.
50 | 
51 | If you have any feedback, please reach out to me at contact@pythonandvba.com
52 |
53 |
54 | 
55 |
56 |
--------------------------------------------------------------------------------
/portfolio_overview.py:
--------------------------------------------------------------------------------
1 | from enum import Enum # Standard Python Library
2 | import time, os, sys # Standard Python Library
3 | import xlwings as xw # pip install xlwings
4 | import pandas as pd # pip install pandas
5 | from yahoofinancials import YahooFinancials # pip install yahoofinancials
6 |
7 | # ==============================
8 | # Purpose:
9 | # Returning stock, cryptocurrency, forex, mutual fund, commodity futures, ETF,
10 | # and US Treasury financial data from Yahoo Finance & export it MS EXCEL
11 | #
12 | # Hints:
13 | # In case you want to adjust/change/add more information to the worksheet,
14 | # make sure to do your respective adjustments in the following:
15 | # a) class Column(Enum): change/add the column-number/name
16 | # b) adjust the dictonary "new_row" in the function "pull_stock_data()"
17 | # ==============================
18 |
19 | print(
20 | """
21 | ==============================
22 | Dividend & Portfolio Overview
23 | ==============================
24 | """
25 | )
26 |
27 |
28 | class Column(Enum):
29 | """ Column Name Translation from Excel, 1 = Column A, 2 = Column B, ... """
30 |
31 | long_name = 1
32 | ticker = 2
33 | current_price = 5
34 | currency = 6
35 | conversion_rate = 7
36 | open_price = 8
37 | daily_low = 9
38 | daily_high = 10
39 | yearly_low = 11
40 | yearly_high = 12
41 | fifty_day_moving_avg = 13
42 | twohundred_day_moving_avg = 14
43 | payout_ratio = 19
44 | exdividend_date = 20
45 | yield_rel = 21
46 | dividend_rate = 22
47 |
48 |
49 | def timestamp():
50 | t = time.localtime()
51 | timestamp = time.strftime("%b-%d-%Y_%H:%M:%S", t)
52 | return timestamp
53 |
54 |
55 | def clear_content_in_excel():
56 | """Clear the old contents in Excel"""
57 | if LAST_ROW > START_ROW:
58 | print(f"Clear Contents from row {START_ROW} to {LAST_ROW}")
59 | for data in Column:
60 | if not data.name == "ticker":
61 | sht.range((START_ROW, data.value), (LAST_ROW, data.value)).options(
62 | expand="down"
63 | ).clear_contents()
64 | return None
65 |
66 |
67 | def convert_to_target_currency(yf_retrieve_data, conversion_rate):
68 | """If value is not available on Yahoo finance, it will return None"""
69 | if yf_retrieve_data is None:
70 | return None
71 | return yf_retrieve_data * conversion_rate
72 |
73 |
74 | def get_coversion_rate(ticker_currency):
75 | """
76 | Calculate the coversion rate between
77 | ticker currency & desired output currency (TARGET_CURRENCY)
78 | Return: conversion rate
79 | """
80 | if TARGET_CURRENCY == "TICKER CURRENCY":
81 | print(f"Display values in {ticker_currency}")
82 | conversion_rate = 1
83 | return conversion_rate
84 | conversion_rate = YahooFinancials(
85 | f"{ticker_currency}{TARGET_CURRENCY}=X"
86 | ).get_current_price()
87 | print(
88 | f"Conversion Rate from {ticker_currency} to {TARGET_CURRENCY}: {conversion_rate}"
89 | )
90 | return conversion_rate
91 |
92 |
93 | def pull_stock_data():
94 | """
95 | Steps:
96 | 1) Create an empty DataFrame
97 | 2) Iterate over tickers, pull data from Yahoo Finance & add data to dictonary "new row"
98 | 3) Append "new row" to DataFrame
99 | 4) Return DataFrame
100 | """
101 | if tickers:
102 | print(f"Iterating over the following tickers: {tickers}")
103 | df = pd.DataFrame()
104 | for ticker in tickers:
105 | print(f"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
106 | print(f"Pulling financial data for: {ticker} ...")
107 | data = YahooFinancials(ticker)
108 | open_price = data.get_open_price()
109 |
110 | # If no open price can be found, Yahoo Finance will return 'None'
111 | if open_price is None:
112 | # If opening price is None, append empty dataframe (row)
113 | print(f"Ticker: {ticker} not found on Yahoo Finance. Please check")
114 | df = df.append(pd.Series(dtype=str), ignore_index=True)
115 | else:
116 | try:
117 | try:
118 | long_name = data.get_stock_quote_type_data()[ticker]["longName"]
119 | except (TypeError, KeyError):
120 | long_name = None
121 | try:
122 | yield_rel = data.get_summary_data()[ticker]["yield"]
123 | except (TypeError, KeyError):
124 | yield_rel = None
125 |
126 | ticker_currency = data.get_currency()
127 | conversion_rate = get_coversion_rate(ticker_currency)
128 |
129 | new_row = {
130 | "ticker": ticker,
131 | "currency": ticker_currency,
132 | "long_name": long_name,
133 | "conversion_rate": conversion_rate,
134 | "yield_rel": yield_rel,
135 | "exdividend_date": data.get_exdividend_date(),
136 | "payout_ratio": data.get_payout_ratio(),
137 | "open_price": convert_to_target_currency(
138 | open_price, conversion_rate
139 | ),
140 | "current_price": convert_to_target_currency(
141 | data.get_current_price(), conversion_rate
142 | ),
143 | "daily_low": convert_to_target_currency(
144 | data.get_daily_low(), conversion_rate
145 | ),
146 | "daily_high": convert_to_target_currency(
147 | data.get_daily_high(), conversion_rate
148 | ),
149 | "yearly_low": convert_to_target_currency(
150 | data.get_yearly_low(), conversion_rate
151 | ),
152 | "yearly_high": convert_to_target_currency(
153 | data.get_yearly_high(), conversion_rate
154 | ),
155 | "fifty_day_moving_avg": convert_to_target_currency(
156 | data.get_50day_moving_avg(), conversion_rate
157 | ),
158 | "twohundred_day_moving_avg": convert_to_target_currency(
159 | data.get_200day_moving_avg(), conversion_rate
160 | ),
161 | "dividend_rate": convert_to_target_currency(
162 | data.get_dividend_rate(), conversion_rate
163 | ),
164 | }
165 | df = df.append(new_row, ignore_index=True)
166 | print(f"Successfully pulled financial data for: {ticker}")
167 |
168 | except Exception as e:
169 | # Error Handling
170 | exc_type, exc_obj, exc_tb = sys.exc_info()
171 | fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
172 | print(exc_type, fname, exc_tb.tb_lineno)
173 | # Append Empty Row
174 | df = df.append(pd.Series(dtype=str), ignore_index=True)
175 | return df
176 | return pd.DataFrame()
177 |
178 |
179 | def write_value_to_excel(df):
180 | if not df.empty:
181 | print(f"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
182 | print(f"Writing data to Excel...")
183 | options = dict(index=False, header=False)
184 | for data in Column:
185 | if not data.name == "ticker":
186 | sht.range(START_ROW, data.value).options(**options).value = df[
187 | data.name
188 | ]
189 | return None
190 |
191 |
192 | def main():
193 | print(f"Please wait. The program is running ...")
194 | clear_content_in_excel()
195 | df = pull_stock_data()
196 | write_value_to_excel(df)
197 | print(f"Program ran successfully!")
198 | show_msgbox("DONE!")
199 |
200 |
201 | # --- GET VALUES FROM EXCEL
202 | # xw.Book.caller() References the calling book
203 | # when the Python function is called from Excel via RunPython.
204 | wb = xw.Book.caller()
205 | sht = wb.sheets("Portfolio")
206 | show_msgbox = wb.macro("modMsgBox.ShowMsgBox")
207 | TARGET_CURRENCY = sht.range("TARGET_CURRENCY").value
208 | START_ROW = sht.range("TICKER").row + 1 # Plus one row after the heading
209 | LAST_ROW = sht.range(sht.cells.last_cell.row, Column.ticker.value).end("up").row
210 | sht.range("TIMESTAMP").value = timestamp()
211 | tickers = (
212 | sht.range(START_ROW, Column.ticker.value).options(expand="down", numbers=str).value
213 | )
214 |
--------------------------------------------------------------------------------
/portfolio_overview.xlsm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Sven-Bo/portfolio-tracking-excel-python/252b7c9785fd9f75b7d78c20c7aa96c228e8f7b0/portfolio_overview.xlsm
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | yahoofinancials==1.6
2 | pandas==1.2.0
3 | xlwings==0.22.2
4 |
5 |
--------------------------------------------------------------------------------