{"id":5093,"date":"2023-11-01T16:39:52","date_gmt":"2023-11-01T23:39:52","guid":{"rendered":"https:\/\/jeremywhittaker.com\/?p=5093"},"modified":"2023-11-01T16:41:55","modified_gmt":"2023-11-01T23:41:55","slug":"using-python-to-save-corporate-financial-data-locally-from-eodhd","status":"publish","type":"post","link":"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/","title":{"rendered":"Using Python to save corporate financial data locally from EODHD"},"content":{"rendered":"\n<p>In a previous post, I showed how to&nbsp;<a href=\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/10\/20\/python-for-finance-storing-stock-market-indices-and-symbols-with-eodhd\/\">store symbol data<\/a>&nbsp;from&nbsp;<a href=\"https:\/\/eodhd.com\/\">EODHD<\/a>. The purpose of this code is to now iterate through all those symbols and grab the corporate financial data from&nbsp;<a href=\"https:\/\/eodhd.com\/\">EODHD<\/a>&nbsp;using their API. If you&#8217;re interested in downloading Open, High, Low, Close, Adjusted Cose, and Volume data you can find that in this blog <a href=\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/10\/24\/using-python-to-save-open-high-low-close-adjusted-close-and-volume-data-locally-from-eodhd\/\">post<\/a>. <\/p>\n\n\n\n<p><\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Output<\/h1>\n\n\n\n<h1 class=\"wp-block-heading\"><\/h1>\n\n\n\n<h1 class=\"wp-block-heading\">Script Overview<\/h1>\n\n\n\n<p>The Python script is primarily designed to perform the following tasks:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Fetch financial fundamentals for a<a href=\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/10\/20\/python-for-finance-storing-stock-market-indices-and-symbols-with-eodhd\/\"> list of stock symbols<\/a>.<\/li>\n\n\n\n<li>Store this information in an HDF5 file for optimized data storage.<\/li>\n\n\n\n<li>Handle various categories of financial data including &#8216;General&#8217;, &#8216;Earnings&#8217;, &#8216;Financials&#8217;, and more.<\/li>\n\n\n\n<li>Log activities for better debugging and monitoring.<\/li>\n<\/ol>\n\n\n\n<p>The script relies on the EOD Historical Data API and uses Python libraries like Pandas, Requests, and h5py.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Utility Functions<\/h1>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>save_dataframe_to_h5()<\/code>: Saves a Pandas DataFrame to an HDF5 file.<\/li>\n\n\n\n<li><code>key_exists_in_h5()<\/code>: Checks if a key exists in an HDF5 file.<\/li>\n\n\n\n<li><code>print_all_keys_in_h5()<\/code>: Prints all keys in an HDF5 file.<\/li>\n\n\n\n<li><code>symbol_exists_in_h5()<\/code>: Checks if a symbol exists in an HDF5 file.<\/li>\n\n\n\n<li><code>convert_columns_to_numeric()<\/code>: Converts DataFrame columns to numeric types, if applicable.<\/li>\n\n\n\n<li><code>update_dataframe()<\/code>: Updates the DataFrame with new rows of data.<\/li>\n<\/ul>\n\n\n\n<h1 class=\"wp-block-heading\">Main Function: <code>fetch_and_store_fundamentals()<\/code><\/h1>\n\n\n\n<p>This function performs the core operation of fetching and storing data. It takes an API token, a list of stock symbols, a data directory, and some optional parameters as arguments.<\/p>\n\n\n\n<p>The function goes through the following steps for each symbol:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Check If Data Exists<\/strong>: If the <code>skip_existing<\/code> flag is true, it checks whether the data already exists in the HDF5 storage.<\/li>\n\n\n\n<li><strong>Fetch Data<\/strong>: Downloads JSON data for the stock symbol from the EOD Historical Data API.<\/li>\n\n\n\n<li><strong>Data Processing<\/strong>: Processes different categories of data (<code>General<\/code>, <code>Financials<\/code>, <code>Earnings<\/code>, etc.) and stores them in separate HDF5 files.<\/li>\n\n\n\n<li><strong>Log Update<\/strong>: Updates a log file with the timestamp of the last fetch for each symbol.<\/li>\n<\/ol>\n\n\n\n<h1 class=\"wp-block-heading\">Helper Function: <code>get_symbols()<\/code><\/h1>\n\n\n\n<p>This function populates a global dictionary, <code>symbol_exchange_map<\/code>, mapping stock symbols to their respective exchanges. It reads this information from an existing HDF5 file.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Code Execution<\/h1>\n\n\n\n<p>Finally, the script fetches a list of stock symbols using <code>get_symbols()<\/code> and then calls the <code>fetch_and_store_fundamentals()<\/code> function to perform the data fetching and storing.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Conclusion<\/h1>\n\n\n\n<p>Automating the process of financial data collection and storage is a crucial step in building robust trading algorithms or investment strategies. This script serves as a foundational block for such endeavors, allowing you to focus more on data analysis rather than data collection.<\/p>\n\n\n\n<h1 class=\"wp-block-heading\">Code<\/h1>\n\n\n\n<pre class=\"wp-block-code\"><code>from keys import api_token\r\nimport time\r\nimport h5py\r\n\r\n\r\ndata_dir = '\/home\/shared\/algos\/data\/'\r\nlogs_dir = '\/home\/shared\/algos\/eodhd_data\/logs\/'\r\n\r\nimport logging\r\nimport pandas as pd\r\n# Configure Pandas to display all columns\r\npd.set_option('display.max_columns', None)\r\nimport requests\r\nimport io\r\nfrom pathlib import Path\r\nfrom tqdm import tqdm\r\nimport json\r\nimport os\r\nfrom datetime import datetime, timedelta\r\n\r\nfrom dictionaries_and_lists import homebuilders, sp500, companies_with_treasuries, largest_banks\r\n\r\n\r\nlogging.basicConfig(level=logging.INFO)\r\npd.set_option('display.expand_frame_repr', False)\r\n\r\n\r\nsymbol_exchange_map = {}\r\n\r\ndef save_dataframe_to_h5(df, h5_path, key, drop_columns=None):\r\n    convert_columns_to_numeric(df)  # Assuming this function converts numeric columns\r\n\r\n    column_type_mapping = {\r\n        'Symbol': 'object',\r\n        'Country': 'object',\r\n        'reportDate': 'datetime',\r\n        'filing_date': 'datetime',\r\n        'date': 'datetime',\r\n        'Date': 'datetime',\r\n        'currency_symbol': 'object',\r\n        'currency': 'object',\r\n        'beforeAfterMarket': 'object'\r\n    }\r\n\r\n    for column, dtype in column_type_mapping.items():\r\n        if column in df.columns:\r\n            if dtype == 'datetime':\r\n                df&#91;column] = pd.to_datetime(df&#91;column], errors='coerce')\r\n            else:\r\n                df&#91;column] = df&#91;column].astype(dtype)\r\n\r\n    if drop_columns:\r\n        df.drop(columns=drop_columns, errors='ignore', inplace=True)\r\n\r\n    try:\r\n        df.to_hdf(h5_path, key=key, mode='a')\r\n    except Exception as e:\r\n        logging.error(f\"Failed to save DataFrame to HDF5 file {h5_path} with key {key}. Error: {e}\")\r\n\r\ndef key_exists_in_h5(h5_filename, key):\r\n    with pd.HDFStore(h5_filename, 'r') as store:\r\n        return key in store\r\n\r\ndef print_all_keys_in_h5(h5_filename):\r\n    with pd.HDFStore(h5_filename, 'r') as store:\r\n        print(\"Keys in HDF5 file:\")\r\n        for key in store.keys():\r\n            print(key)\r\n\r\ndef symbol_exists_in_h5(h5_filepath, symbol):\r\n    try:\r\n        with pd.HDFStore(h5_filepath, 'r') as store:\r\n            return f'\/{symbol}' in store.keys()\r\n    except HDF5Error:\r\n        return False\r\n\r\ndef convert_columns_to_numeric(df):\r\n    for col in df.columns:\r\n        df&#91;col] = pd.to_numeric(df&#91;col], errors='ignore')\r\n\r\n\r\ndef update_dataframe(df, symbol, sub_category, sub_category_data, time_frame=None):\r\n    row_data = {\r\n        'Symbol': symbol,\r\n        'SubCategory': sub_category,\r\n    }\r\n\r\n    if time_frame is not None:\r\n        row_data&#91;'TimeFrame'] = time_frame\r\n\r\n    if isinstance(sub_category_data, dict):\r\n        row_data.update(sub_category_data)\r\n    else:\r\n        row_data&#91;'Data'] = str(sub_category_data)\r\n\r\n    new_row = pd.DataFrame(&#91;row_data])\r\n    df = pd.concat(&#91;df, new_row], ignore_index=True)\r\n    return df\r\n\r\n\r\ndef fetch_and_store_fundamentals(api_token, symbols, data_dir, skip_existing=False, hours_to_skip=72):\r\n    log_file = logs_dir + 'symbol_time_log.json'\r\n    symbol_time_dict = {}\r\n\r\n    try:\r\n        with open(log_file, 'r') as f:\r\n            content = f.read()\r\n        symbol_time_dict = json.loads(content)\r\n    except json.JSONDecodeError as e:\r\n        logging.error(\r\n            f\"JSON Decode Error at line {e.lineno}, column {e.colno}. Content around error position: {content&#91;e.pos - 10:e.pos + 10]}\")\r\n    except FileNotFoundError:\r\n        logging.info(f\"File {log_file} not found. An empty dictionary will be used.\")\r\n    except Exception as e:\r\n        logging.error(f\"An unexpected error occurred: {e}\")\r\n\r\n\r\n    for symbol in tqdm(symbols, desc='Fetching and storing fundamentals'):\r\n        last_downloaded_time = symbol_time_dict.get(symbol, None)\r\n        if last_downloaded_time:\r\n            last_downloaded_time = datetime.fromisoformat(last_downloaded_time)\r\n            time_since_last_download = datetime.now() - last_downloaded_time\r\n            if time_since_last_download &lt; timedelta(hours=hours_to_skip):\r\n                logging.info(f\"Data for symbol {symbol} was downloaded recently. Skipping...\")\r\n                continue\r\n\r\n        if skip_existing:\r\n            # Assuming the symbol is known at this point in your code\r\n            exchange = symbol_exchange_map.get(symbol, '')  # Get exchange name from global dict\r\n\r\n            # Create the new h5 path with the exchange name prepended\r\n            h5_path_check = Path(data_dir, f\"{exchange}_General.h5\") if exchange else Path(data_dir, \"General.h5\")\r\n\r\n            if h5_path_check.exists() and key_exists_in_h5(h5_path_check, f'\/{symbol}'):\r\n                logging.info(f\"Data for symbol {symbol} already exists. Skipping...\")\r\n                continue\r\n\r\n        logging.info(f\"\\n{symbol}: Downloading from EODHD...\")\r\n\r\n        try:\r\n            url = f\"https:\/\/eodhd.com\/api\/fundamentals\/{symbol}.US?api_token={api_token}\"\r\n            response = requests.get(url)\r\n        except ConnectionError:\r\n            logging.error(f\"ConnectionError occurred while fetching data for symbol {symbol}. Retrying in 60 seconds.\")\r\n            time.sleep(60)\r\n            continue\r\n        except ConnectionRefusedError:\r\n            logging.error(\r\n                f\"ConnectionRefusedError occurred while fetching data for symbol {symbol}. Retrying in 60 seconds.\")\r\n            time.sleep(60)\r\n            continue\r\n        except Exception as e:\r\n            logging.error(f\"An unexpected error occurred: {e}\")\r\n            continue\r\n\r\n        if response.status_code != 200:\r\n            logging.error(f\"Failed to fetch data for symbol {symbol}. HTTP Status Code: {response.status_code} \\n Sleeping for 60 seconds\")\r\n            time.sleep(60)  # Sleep for 60 seconds\r\n            continue  # Continue to next iteration\r\n\r\n        json_data = response.json()\r\n\r\n        logging.info(f\"\\n{symbol}: Finished downloading from EODHD...\")\r\n\r\n        # Check if the logging level is set to DEBUG\r\n        if logging.getLogger().getEffectiveLevel() == logging.DEBUG:\r\n            json_dump_file = logs_dir + 'api_response_output.txt'\r\n\r\n            with open(json_dump_file, 'w') as f:\r\n                f.write(\"JSON Data:\\n\")\r\n                f.write(json.dumps(json_data, indent=4))\r\n\r\n        for category, category_data in json_data.items():\r\n            if category_data is None:\r\n                logging.warning(f\"Data for category {category} is None.\")\r\n                continue\r\n\r\n            exchange = symbol_exchange_map.get(symbol, 'Other')\r\n            h5_path = Path(data_dir, f\"{exchange}_{category}.h5\")\r\n            df = pd.DataFrame()\r\n\r\n            if category == 'ESGScores':\r\n                continue\r\n\r\n            elif category == 'Holders':\r\n                for holder_type, holder_data in category_data.items():\r\n                    h5_path = Path(data_dir, f\"{exchange}_Holders_{holder_type}.h5\")\r\n\r\n                    for holder_id, holder_info in holder_data.items():\r\n                        df = update_dataframe(df, symbol, holder_type, holder_info)\r\n                    save_dataframe_to_h5(df, h5_path, key=symbol, drop_columns=&#91;'SubCategory'])\r\n                    logging.info(f\"{symbol} finished processing category Holders\")\r\n\r\n            elif category == 'SplitsDividends':\r\n                logging.debug(f\"Processing 'SplitsDividends' category. Data: {category_data}\")\r\n\r\n                # h5_path = Path(data_dir, f\"{category}.h5\")\r\n                for sub_key, sub_data in category_data.items():\r\n                    logging.debug(f\"Processing key: {sub_key}\")\r\n                    logging.debug(f\"Data for key {sub_key}: {sub_data}\")\r\n\r\n                    if sub_key == 'NumberDividendsByYear':\r\n                        nested_h5_path = Path(data_dir, f\"{exchange}_SplitsDividends_{sub_key}.h5\")\r\n\r\n                        nested_df = pd.DataFrame()\r\n\r\n                        for item_key, item_data in sub_data.items():\r\n                            logging.debug(f\"Item data: {item_key}\")\r\n                            nested_df = update_dataframe(nested_df, symbol, sub_key, item_data)\r\n\r\n                        # Sort and remove duplicates based on the Year column\r\n                        # Before sorting, check if 'Year' exists\r\n                        if 'Year' in nested_df.columns:\r\n                            nested_df = nested_df.sort_values(by=&#91;'Year'])\r\n                            nested_df.drop_duplicates(subset=&#91;'Year'], keep='last', inplace=True)\r\n\r\n                        save_dataframe_to_h5(nested_df, nested_h5_path, key=symbol, drop_columns=&#91;'SubCategory'])\r\n                    else:\r\n                        df = update_dataframe(df, symbol, sub_key, sub_data)\r\n\r\n                save_dataframe_to_h5(df, h5_path, key=symbol)\r\n                logging.info(f\"{symbol} finished processing category SplitsDividends\")\r\n\r\n\r\n            elif category == 'General':\r\n                logging.debug(f\"Processing 'General' category. Data: {category_data}\")\r\n                for sub_key, sub_data in category_data.items():\r\n                    logging.debug(f\"Processing key: {sub_key}\")\r\n                    logging.debug(f\"Data for key {sub_key}: {sub_data}\")\r\n\r\n                    if sub_key in &#91;'Listings', 'Officers']:\r\n                        continue  # Skip 'Listings' and 'Officers'\r\n\r\n                    df = update_dataframe(df, symbol, sub_key, sub_data)\r\n\r\n\r\n                save_dataframe_to_h5(df, h5_path, key=symbol)\r\n                logging.info(f\"{symbol} finished processing category General\")\r\n\r\n\r\n            elif category == 'Financials':\r\n                # Iterate through report types like 'Balance Sheet', 'Cash Flow', 'Income Statements'\r\n                for report_type, report_data in category_data.items():\r\n                    # Iterate through time frames like 'annual' or 'quarterly'\r\n                    for time_frame, time_frame_data in report_data.items():\r\n                        if time_frame == 'currency_symbol':\r\n                            continue  # Skip the 'currency_symbol'\r\n\r\n                        # Create a specific .h5 path for each combination of report_type and time_frame\r\n                        h5_path = Path(data_dir, f\"{exchange}_{report_type}_{time_frame}.h5\")\r\n\r\n                        df = pd.DataFrame()\r\n\r\n                        # Update the DataFrame with financial data\r\n                        for sub_category, sub_category_data in time_frame_data.items():\r\n                            df = update_dataframe(df, symbol, sub_category, sub_category_data)\r\n\r\n                        # Save this specific DataFrame to its respective .h5 file\r\n                        save_dataframe_to_h5(df, h5_path, key=symbol, drop_columns=&#91;'SubCategory'])\r\n                        logging.info(f\"{symbol} finished processing category Financials\")\r\n\r\n\r\n            elif category == 'Earnings':\r\n                for sub_category, sub_category_data in category_data.items():\r\n                    # Create a specific .h5 path for each sub-category\r\n                    h5_path = Path(data_dir, f\"{exchange}_{category}_{sub_category}.h5\")\r\n\r\n                    df = pd.DataFrame()\r\n\r\n\r\n                    for date_entry, date_entry_data in sub_category_data.items():  # Iterate through each date entry in the subcategory\r\n                        if date_entry == 'currency_symbol':\r\n                            continue\r\n\r\n                        # Adding a date field to each entry\r\n                        date_entry_data&#91;'Date'] = date_entry\r\n\r\n                        # Update the dataframe with new row\r\n                        df = update_dataframe(df, symbol, sub_category, date_entry_data)\r\n\r\n                    # Save this specific DataFrame to its respective .h5 file\r\n                    if 'SubCategory' in df.columns:\r\n                        save_dataframe_to_h5(df, h5_path, key=symbol, drop_columns=&#91;'SubCategory'])  # Drop the 'SubCategory' column\r\n                    else:\r\n                        save_dataframe_to_h5(df, h5_path, key=symbol)\r\n                    logging.info(f\"{symbol} finished processing category Earnings\")\r\n\r\n\r\n            elif category == 'outstandingShares':\r\n                for time_frame, time_frame_data in category_data.items():  # time_frame can be 'annual' or 'quarterly'\r\n                    # Create a specific .h5 path for each time frame\r\n                    h5_path = Path(data_dir, f\"{exchange}_{category}_{time_frame}.h5\")\r\n\r\n                    df = pd.DataFrame()\r\n\r\n\r\n                    for entry_id, entry_data in time_frame_data.items():\r\n                        df = update_dataframe(df, symbol, time_frame, entry_data)\r\n\r\n                    # Save this specific DataFrame to its respective .h5 file\r\n                    save_dataframe_to_h5(df, h5_path, key=symbol, drop_columns=&#91;'SubCategory'])\r\n                    logging.info(f\"{symbol} finished processing category outstandingShares\")\r\n\r\n\r\n            elif category == 'ETF_Data':\r\n                # DataFrame for the top-level ETF_Data, excluding subcategories that will be handled separately\r\n                top_level_df = pd.DataFrame()\r\n\r\n                # Dictionary to hold top-level data for this stock symbol\r\n                top_level_data = {'Symbol': symbol}\r\n\r\n                for sub_category, sub_category_data in category_data.items():\r\n                    if sub_category in &#91;'Asset_Allocation', 'World_Regions', 'Sector_Weights', 'Fixed_Income', 'Top_10_Holdings', 'Holdings', 'Valuations_Growth', 'Market_Capitalisation', 'MorningStar', 'Performance', ]:\r\n                        # Create a specific .h5 path for each sub-category\r\n                        h5_path = Path(data_dir, f\"{exchange}_ETF_{sub_category}.h5\")\r\n\r\n                        df = pd.DataFrame()\r\n\r\n                        # Update the DataFrame with sub-category data\r\n                        for item_key, item_data in sub_category_data.items():\r\n                            df = update_dataframe(df, symbol, item_key, item_data)\r\n\r\n                        # Save this specific DataFrame to its respective .h5 file\r\n                        save_dataframe_to_h5(df, h5_path, key=symbol)\r\n\r\n                    else:\r\n                        # Populate the top-level data dictionary\r\n                        top_level_data&#91;sub_category] = sub_category_data\r\n\r\n                # Convert the top-level data dictionary to a DataFrame and append it to top_level_df\r\n                new_row = pd.DataFrame(&#91;top_level_data])\r\n                top_level_df = pd.concat(&#91;top_level_df, new_row], ignore_index=True)\r\n\r\n                # Save the top-level ETF_Data DataFrame to its respective .h5 file\r\n                top_level_h5_path = Path(data_dir, \"ETF_Data.h5\")\r\n                save_dataframe_to_h5(top_level_df, top_level_h5_path, key=symbol)\r\n                logging.info(f\"{symbol} finished processing category ETF_Data\")\r\n\r\n            else:\r\n                logging.debug(f'Processing other category {category}')\r\n\r\n                if h5_path.exists() and symbol_exists_in_h5(h5_path, symbol):\r\n                    df = pd.read_hdf(h5_path, key=symbol)\r\n                else:\r\n                    df = pd.DataFrame()\r\n\r\n                if isinstance(category_data, dict):\r\n                    for sub_category, sub_category_data in category_data.items():\r\n                        df = update_dataframe(df, symbol, sub_category, sub_category_data)\r\n                else:\r\n                    df = update_dataframe(df, symbol, category, category_data)\r\n\r\n                save_dataframe_to_h5(df, h5_path, key=symbol)\r\n                logging.info(f\"{symbol} finished processing category {category}\")\r\n\r\n        logging.info(f\"{symbol} updating symbol status log file.\")\r\n        symbol_time_dict&#91;symbol] = datetime.now().isoformat()\r\n        with open(log_file, 'w') as f:\r\n            json.dump(symbol_time_dict, f)\r\n\r\n        logging.info(f\"{symbol} finished processing\")\r\n\r\n\r\ndef get_symbols(h5_file_path, key='US'):\r\n    \"\"\"\r\n    Open an HDF5 file and populate the global dictionary symbol_exchange_map\r\n    where the symbol is the key and the exchange is the value.\r\n\r\n    Parameters:\r\n        h5_file_path (str): The path to the HDF5 file.\r\n        key (str): The key to use when reading the HDF5 file. Default is 'US'.\r\n\r\n    Returns:\r\n        None\r\n    \"\"\"\r\n\r\n    h5_file_path = Path(h5_file_path)\r\n\r\n    # Check if the file exists\r\n    if not h5_file_path.exists():\r\n        logging.info(f\"The file {h5_file_path} does not exist.\")\r\n        return\r\n\r\n    try:\r\n        # Read the DataFrame from the HDF5 file\r\n        df = pd.read_hdf(h5_file_path, key=key)\r\n\r\n        # Check if 'Code' and 'Exchange' columns exist\r\n        if 'Code' not in df.columns or 'Exchange' not in df.columns:\r\n            logging.info(f\"The 'Code' or 'Exchange' column does not exist in the DataFrame.\")\r\n            return\r\n\r\n        # Populate the global symbol_exchange_map\r\n        global symbol_exchange_map\r\n        symbol_exchange_map = dict(zip(df&#91;'Code'], df&#91;'Exchange']))\r\n        return list(symbol_exchange_map.keys())\r\n    except Exception as e:\r\n        logging.error(f\"An error occurred: {e}\")\r\n        return\r\n\r\n\r\nsymbols = get_symbols(data_dir + 'symbols.h5', key='US')\r\n\r\nfetch_and_store_fundamentals(api_token, symbols, data_dir, skip_existing=True, hours_to_skip=72)<\/code><\/pre>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In a previous post, I showed how to&nbsp;store symbol data&nbsp;from&nbsp;EODHD. The purpose of this code is to now iterate through all those symbols and grab the corporate financial&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-5093","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.2 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Using Python to save corporate financial data locally from EODHD - Jeremy Whittaker<\/title>\n<meta name=\"robots\" content=\"noindex, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Using Python to save corporate financial data locally from EODHD - Jeremy Whittaker\" \/>\n<meta property=\"og:description\" content=\"In a previous post, I showed how to&nbsp;store symbol data&nbsp;from&nbsp;EODHD. The purpose of this code is to now iterate through all those symbols and grab the corporate financial...\" \/>\n<meta property=\"og:url\" content=\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/\" \/>\n<meta property=\"og:site_name\" content=\"Jeremy Whittaker\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/WhittakerJeremy\" \/>\n<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/WhittakerJeremy\" \/>\n<meta property=\"article:published_time\" content=\"2023-11-01T23:39:52+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2023-11-01T23:41:55+00:00\" \/>\n<meta name=\"author\" content=\"JeremyWhittaker\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"JeremyWhittaker\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"2 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/\"},\"author\":{\"name\":\"JeremyWhittaker\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\"},\"headline\":\"Using Python to save corporate financial data locally from EODHD\",\"datePublished\":\"2023-11-01T23:39:52+00:00\",\"dateModified\":\"2023-11-01T23:41:55+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/\"},\"wordCount\":399,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/\",\"url\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/\",\"name\":\"Using Python to save corporate financial data locally from EODHD - Jeremy Whittaker\",\"isPartOf\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/#website\"},\"datePublished\":\"2023-11-01T23:39:52+00:00\",\"dateModified\":\"2023-11-01T23:41:55+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/new.jeremywhittaker.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Using Python to save corporate financial data locally from EODHD\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/new.jeremywhittaker.com\/#website\",\"url\":\"https:\/\/new.jeremywhittaker.com\/\",\"name\":\"Jeremy Whittaker\",\"description\":\"Research, software, markets, housing, and energy\",\"publisher\":{\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/new.jeremywhittaker.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c\",\"name\":\"JeremyWhittaker\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g\",\"caption\":\"JeremyWhittaker\"},\"logo\":{\"@id\":\"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g\"},\"sameAs\":[\"http:\/\/www.jeremywhittaker.com\",\"https:\/\/www.facebook.com\/WhittakerJeremy\",\"https:\/\/www.linkedin.com\/in\/jeremywhittaker\/\"],\"url\":\"https:\/\/new.jeremywhittaker.com\/index.php\/author\/jeremywhittaker\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Using Python to save corporate financial data locally from EODHD - Jeremy Whittaker","robots":{"index":"noindex","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"og_locale":"en_US","og_type":"article","og_title":"Using Python to save corporate financial data locally from EODHD - Jeremy Whittaker","og_description":"In a previous post, I showed how to&nbsp;store symbol data&nbsp;from&nbsp;EODHD. The purpose of this code is to now iterate through all those symbols and grab the corporate financial...","og_url":"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/","og_site_name":"Jeremy Whittaker","article_publisher":"https:\/\/www.facebook.com\/WhittakerJeremy","article_author":"https:\/\/www.facebook.com\/WhittakerJeremy","article_published_time":"2023-11-01T23:39:52+00:00","article_modified_time":"2023-11-01T23:41:55+00:00","author":"JeremyWhittaker","twitter_card":"summary_large_image","twitter_misc":{"Written by":"JeremyWhittaker","Est. reading time":"2 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/#article","isPartOf":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/"},"author":{"name":"JeremyWhittaker","@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c"},"headline":"Using Python to save corporate financial data locally from EODHD","datePublished":"2023-11-01T23:39:52+00:00","dateModified":"2023-11-01T23:41:55+00:00","mainEntityOfPage":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/"},"wordCount":399,"commentCount":0,"publisher":{"@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c"},"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/","url":"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/","name":"Using Python to save corporate financial data locally from EODHD - Jeremy Whittaker","isPartOf":{"@id":"https:\/\/new.jeremywhittaker.com\/#website"},"datePublished":"2023-11-01T23:39:52+00:00","dateModified":"2023-11-01T23:41:55+00:00","breadcrumb":{"@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/new.jeremywhittaker.com\/index.php\/2023\/11\/01\/using-python-to-save-corporate-financial-data-locally-from-eodhd\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/new.jeremywhittaker.com\/"},{"@type":"ListItem","position":2,"name":"Using Python to save corporate financial data locally from EODHD"}]},{"@type":"WebSite","@id":"https:\/\/new.jeremywhittaker.com\/#website","url":"https:\/\/new.jeremywhittaker.com\/","name":"Jeremy Whittaker","description":"Research, software, markets, housing, and energy","publisher":{"@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/new.jeremywhittaker.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/new.jeremywhittaker.com\/#\/schema\/person\/ed0edfdefb3e180693efef453372980c","name":"JeremyWhittaker","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g","caption":"JeremyWhittaker"},"logo":{"@id":"https:\/\/secure.gravatar.com\/avatar\/c8ac20e6dfa86b5f27ce9bffee4851099770cbea5ae7338a274865bfbc8c0218?s=96&d=retro&r=g"},"sameAs":["http:\/\/www.jeremywhittaker.com","https:\/\/www.facebook.com\/WhittakerJeremy","https:\/\/www.linkedin.com\/in\/jeremywhittaker\/"],"url":"https:\/\/new.jeremywhittaker.com\/index.php\/author\/jeremywhittaker\/"}]}},"_links":{"self":[{"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts\/5093","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/comments?post=5093"}],"version-history":[{"count":5,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts\/5093\/revisions"}],"predecessor-version":[{"id":5098,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/posts\/5093\/revisions\/5098"}],"wp:attachment":[{"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/media?parent=5093"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/categories?post=5093"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/new.jeremywhittaker.com\/index.php\/wp-json\/wp\/v2\/tags?post=5093"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}