├── 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 | " \n", 297 | " \n", 298 | " \n", 299 | " \n", 300 | " \n", 301 | " \n", 302 | " \n", 303 | " \n", 304 | " \n", 305 | " \n", 306 | " \n", 307 | " \n", 308 | " \n", 309 | " \n", 310 | " \n", 311 | " \n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | "
currencyopen_pricetickeryearly_high
0USD641.87TSLA900.40
1USD2038.86GOOG2152.68
2USD231.55MSFT246.13
\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 | ![Excel File Screenshot](https://content.screencast.com/users/jubbel3/folders/Snagit/media/d31d9bd9-2837-4451-86b4-a37c24bcbf12/09.29.2021-10.35.jpg) 17 | 18 | ## Video 19 | 20 | [![YouTube Video](https://img.youtube.com/vi/4KsP5Et_aWo/0.jpg)](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 | [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://pythonandvba.com/coffee-donation) 47 | 48 | ## Feedback & Collaboration 49 | For feedback, suggestions, or potential collaboration opportunities, reach out at contact@pythonandvba.com. 50 | ![Logo](https://www.pythonandvba.com/banner-img) 51 | If you have any feedback, please reach out to me at contact@pythonandvba.com 52 | 53 | 54 | ![Logo](https://www.pythonandvba.com/banner-img) 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 | --------------------------------------------------------------------------------