├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md └── workflows │ └── test-package.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── docs ├── Makefile ├── make.bat ├── rtd_requirements.txt └── source │ ├── conf.py │ ├── index.md │ └── notebooks │ └── usage.ipynb ├── poetry.lock ├── pyproject.toml ├── pytest.ini ├── src └── fmp_py │ ├── __init__.py │ ├── fmp_base.py │ ├── fmp_chart_data.py │ ├── fmp_company_information.py │ ├── fmp_company_search.py │ ├── fmp_crypto.py │ ├── fmp_dividends.py │ ├── fmp_earnings.py │ ├── fmp_financial_statements.py │ ├── fmp_forex.py │ ├── fmp_historical_data.py │ ├── fmp_ipo_calendar.py │ ├── fmp_mergers_and_aquisitions.py │ ├── fmp_price_targets.py │ ├── fmp_quote.py │ ├── fmp_splits.py │ ├── fmp_statement_analysis.py │ ├── fmp_stock_list.py │ ├── fmp_upgrades_downgrades.py │ ├── fmp_valuation.py │ └── models │ ├── company_information.py │ ├── price_targets.py │ ├── quote.py │ ├── statement_analysis.py │ ├── upgrades_downgrades.py │ └── valuation.py └── tests ├── __init__.py ├── test_fmp_chart_data.py ├── test_fmp_company_information.py ├── test_fmp_company_search.py ├── test_fmp_crypto.py ├── test_fmp_dividends.py ├── test_fmp_earnings.py ├── test_fmp_financial_statements.py ├── test_fmp_forex.py ├── test_fmp_historical_data.py ├── test_fmp_ipo_calendar.py ├── test_fmp_mergers_and_aquisitions.py ├── test_fmp_price_targets.py ├── test_fmp_quote.py ├── test_fmp_splits.py ├── test_fmp_statement_analysis.py ├── test_fmp_stock_list.py ├── test_fmp_upgrades_downgrades.py └── test_fmp_valuation.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/test-package.yaml: -------------------------------------------------------------------------------- 1 | # inspired by https://jacobian.org/til/github-actions-poetry/ 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | 8 | env: 9 | POETRY_VERSION: 1.8.3 10 | FMP_API_KEY: ${{ secrets.FMP_API_KEY }} 11 | jobs: 12 | build: 13 | runs-on: ${{ matrix.os }} 14 | strategy: 15 | max-parallel: 1 16 | fail-fast: false 17 | matrix: 18 | python-version: ["3.12"] 19 | os: [ubuntu-latest, macOS-latest] 20 | env: 21 | POETRY_VIRTUALENVS_IN_PROJECT: true 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: actions/setup-python@v5 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | 28 | # Cache the installation of Poetry itself, e.g. the next step. This prevents the workflow 29 | # from installing Poetry every time, which can be slow. Note the use of the Poetry version 30 | # number in the cache key, and the "-0" suffix: this allows you to invalidate the cache 31 | # manually if/when you want to upgrade Poetry, or if something goes wrong. 32 | - name: cache poetry install 33 | uses: actions/cache@v4 34 | with: 35 | path: ~/.local 36 | key: poetry-cache-${{ runner.os }}-${{ matrix.python-version }}-${{ env.POETRY_VERSION }} 37 | 38 | # Install Poetry. You could do this manually, or there are several actions that do this. 39 | # `snok/install-poetry` seems to be minimal yet complete, and really just calls out to 40 | # Poetry's default install script, which feels correct. I pin the Poetry version here 41 | # because Poetry does occasionally change APIs between versions and I don't want my 42 | # actions to break if it does. 43 | # 44 | # The key configuration value here is `virtualenvs-in-project: true`: this creates the 45 | # venv as a `.venv` in your testing directory, which allows the next step to easily 46 | # cache it. 47 | - uses: snok/install-poetry@v1 48 | with: 49 | version: 1.8.3 50 | virtualenvs-create: true 51 | virtualenvs-in-project: true 52 | 53 | # Cache your dependencies (i.e. all the stuff in your `pyproject.toml`) 54 | - name: cache venv 55 | uses: actions/cache@v4 56 | with: 57 | path: .venv 58 | key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }} 59 | - run: poetry install --no-interaction --no-root 60 | if: steps.cache-deps.outputs.cache-hit != 'true' 61 | - run: poetry install --no-interaction 62 | - run: poetry run ruff check --fix 63 | - run: poetry run pytest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | sandbox.py 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/astral-sh/ruff-pre-commit 3 | # Ruff version. 4 | rev: v0.6.3 5 | hooks: 6 | # Run the linter. 7 | - id: ruff 8 | args: [ --fix ] 9 | # Run the formatter. 10 | - id: ruff-format -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: "ubuntu-20.04" 5 | tools: 6 | python: "3.10" 7 | sphinx: 8 | configuration: docs/source/conf.py 9 | 10 | python: 11 | install: 12 | - requirements: docs/rtd_requirements.txt -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.testing.pytestArgs": [ 3 | "tests" 4 | ], 5 | "python.testing.unittestEnabled": false, 6 | "python.testing.pytestEnabled": true 7 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | jeff10278@me.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jeff West 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # For the full list of built-in configuration values, see the documentation: 4 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 5 | 6 | # -- Project information ----------------------------------------------------- 7 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information 8 | from pathlib import Path 9 | 10 | project = "Fmp-Py" 11 | copyright = "2024, TexasCoding" 12 | author = "TexasCoding" 13 | release = "0.0.6" 14 | 15 | # -- General configuration --------------------------------------------------- 16 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration 17 | 18 | extensions = [ 19 | "myst_parser", 20 | "sphinx.ext.duration", 21 | "sphinx.ext.autosectionlabel", 22 | "sphinx.ext.autodoc", 23 | "sphinx.ext.napoleon", 24 | "autoapi.extension", 25 | "nbsphinx", 26 | ] 27 | 28 | autoapi_type = "python" 29 | autoapi_dirs = [f"{Path(__file__).parents[2]}/src"] 30 | 31 | templates_path = ["_templates"] 32 | exclude_patterns = [] 33 | 34 | 35 | # -- Options for HTML output ------------------------------------------------- 36 | # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output 37 | 38 | html_theme = "sphinx_rtd_theme" 39 | html_static_path = ["_static"] 40 | -------------------------------------------------------------------------------- /docs/source/index.md: -------------------------------------------------------------------------------- 1 | % Fmp-Py documentation master file, created by 2 | % sphinx-quickstart on Sat Jul 20 16:58:03 2024. 3 | % You can adapt this file completely to your liking, but it should at least 4 | % contain the root `toctree` directive. 5 | 6 | # Fmp-Py documentation 7 | 8 | ```{toctree} 9 | :caption: 'Contents:' 10 | :maxdepth: 2 11 | 12 | notebooks/usage 13 | ``` 14 | -------------------------------------------------------------------------------- /docs/source/notebooks/usage.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "### Getting Started With Chart Data" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 9, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "from pprint import pprint\n", 17 | "from fmp_py.fmp_charts import FmpCharts\n", 18 | "import pendulum\n", 19 | "\n", 20 | "yesterday = pendulum.yesterday().to_date_string()\n", 21 | "four_year_age = pendulum.now().subtract(years=4).to_date_string()" 22 | ] 23 | }, 24 | { 25 | "cell_type": "markdown", 26 | "metadata": {}, 27 | "source": [ 28 | "## Basic Historical Data" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": 10, 34 | "metadata": {}, 35 | "outputs": [ 36 | { 37 | "name": "stdout", 38 | "output_type": "stream", 39 | "text": [ 40 | " open low high close volume\n", 41 | "date \n", 42 | "2020-07-27 93.71 93.48 94.91 94.81 121214192\n", 43 | "2020-07-28 94.37 93.25 94.55 93.25 103625500\n", 44 | "2020-07-29 93.75 93.71 95.23 95.04 90329256\n", 45 | "2020-07-30 94.19 93.77 96.30 96.19 158130020\n", 46 | "2020-07-31 102.88 100.83 106.42 106.26 374295468\n", 47 | "... ... ... ... ... ...\n", 48 | "2024-07-19 224.82 223.28 226.80 224.31 49151453\n", 49 | "2024-07-22 227.01 223.09 227.78 223.96 48201835\n", 50 | "2024-07-23 224.37 222.68 226.94 225.01 39960260\n", 51 | "2024-07-24 224.00 217.13 224.80 218.54 61777576\n", 52 | "2024-07-25 218.93 214.62 220.85 217.49 51391199\n", 53 | "\n", 54 | "[1006 rows x 5 columns]\n" 55 | ] 56 | } 57 | ], 58 | "source": [ 59 | "fmp = FmpCharts(symbol=\"AAPL\", from_date=four_year_age, to_date=yesterday)\n", 60 | "\n", 61 | "pprint(fmp.return_chart())" 62 | ] 63 | }, 64 | { 65 | "cell_type": "markdown", 66 | "metadata": {}, 67 | "source": [ 68 | "## Add SMA Indicator to Data" 69 | ] 70 | }, 71 | { 72 | "cell_type": "code", 73 | "execution_count": 11, 74 | "metadata": {}, 75 | "outputs": [ 76 | { 77 | "name": "stdout", 78 | "output_type": "stream", 79 | "text": [ 80 | " open low high close volume sma14\n", 81 | "date \n", 82 | "2020-07-27 93.71 93.48 94.91 94.81 121214192 94.81\n", 83 | "2020-07-28 94.37 93.25 94.55 93.25 103625500 94.03\n", 84 | "2020-07-29 93.75 93.71 95.23 95.04 90329256 94.37\n", 85 | "2020-07-30 94.19 93.77 96.30 96.19 158130020 94.82\n", 86 | "2020-07-31 102.88 100.83 106.42 106.26 374295468 97.11\n", 87 | "... ... ... ... ... ... ...\n", 88 | "2024-07-19 224.82 223.28 226.80 224.31 49151453 227.08\n", 89 | "2024-07-22 227.01 223.09 227.78 223.96 48201835 227.59\n", 90 | "2024-07-23 224.37 222.68 226.94 225.01 39960260 227.93\n", 91 | "2024-07-24 224.00 217.13 224.80 218.54 61777576 227.72\n", 92 | "2024-07-25 218.93 214.62 220.85 217.49 51391199 227.08\n", 93 | "\n", 94 | "[1006 rows x 6 columns]\n" 95 | ] 96 | } 97 | ], 98 | "source": [ 99 | "fmp.sma(14)\n", 100 | "\n", 101 | "pprint(fmp.return_chart())" 102 | ] 103 | }, 104 | { 105 | "cell_type": "markdown", 106 | "metadata": {}, 107 | "source": [ 108 | "## Add RSI Indicator to Data" 109 | ] 110 | }, 111 | { 112 | "cell_type": "code", 113 | "execution_count": 12, 114 | "metadata": {}, 115 | "outputs": [ 116 | { 117 | "name": "stdout", 118 | "output_type": "stream", 119 | "text": [ 120 | " open low high close volume sma14 rsi14\n", 121 | "date \n", 122 | "2020-07-27 93.71 93.48 94.91 94.81 121214192 94.81 100.00\n", 123 | "2020-07-28 94.37 93.25 94.55 93.25 103625500 94.03 0.00\n", 124 | "2020-07-29 93.75 93.71 95.23 95.04 90329256 94.37 55.27\n", 125 | "2020-07-30 94.19 93.77 96.30 96.19 158130020 94.82 67.64\n", 126 | "2020-07-31 102.88 100.83 106.42 106.26 374295468 97.11 91.03\n", 127 | "... ... ... ... ... ... ... ...\n", 128 | "2024-07-19 224.82 223.28 226.80 224.31 49151453 227.08 57.05\n", 129 | "2024-07-22 227.01 223.09 227.78 223.96 48201835 227.59 56.54\n", 130 | "2024-07-23 224.37 222.68 226.94 225.01 39960260 227.93 57.76\n", 131 | "2024-07-24 224.00 217.13 224.80 218.54 61777576 227.72 48.69\n", 132 | "2024-07-25 218.93 214.62 220.85 217.49 51391199 227.08 47.39\n", 133 | "\n", 134 | "[1006 rows x 7 columns]\n" 135 | ] 136 | } 137 | ], 138 | "source": [ 139 | "fmp.rsi(14)\n", 140 | "\n", 141 | "pprint(fmp.return_chart())" 142 | ] 143 | } 144 | ], 145 | "metadata": { 146 | "kernelspec": { 147 | "display_name": "fmp-py-m43qA0g1-py3.12", 148 | "language": "python", 149 | "name": "python3" 150 | }, 151 | "language_info": { 152 | "codemirror_mode": { 153 | "name": "ipython", 154 | "version": 3 155 | }, 156 | "file_extension": ".py", 157 | "mimetype": "text/x-python", 158 | "name": "python", 159 | "nbconvert_exporter": "python", 160 | "pygments_lexer": "ipython3", 161 | "version": "3.12.4" 162 | } 163 | }, 164 | "nbformat": 4, 165 | "nbformat_minor": 2 166 | } 167 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "fmp-py" 3 | version = "0.0.20.3" 4 | description = "Python package for Financial Modeling Prep API" 5 | authors = ["TexasCoding "] 6 | readme = "README.md" 7 | 8 | homepage = "https://pypi.org/project/fmp-py/" 9 | repository = "https://github.com/TexasCoding/fmp-py" 10 | documentation = "https://fmp-py.readthedocs.io/en/latest/index.html" 11 | 12 | packages = [{include = "fmp_py", from = "src"}] 13 | 14 | [tool.poetry.dependencies] 15 | python = "^3.10" 16 | requests = "^2.32.3" 17 | python-dotenv = "^1.0.1" 18 | pandas = "^2.2.2" 19 | requests-mock = "^1.12.1" 20 | pendulum = "^3.0.0" 21 | ta = "^0.11.0" 22 | requests-cache = "^1.2.1" 23 | requests-ratelimiter = "^0.7.0" 24 | 25 | 26 | [tool.poetry.group.tests.dependencies] 27 | pytest = "^8.2.2" 28 | pytest-mock = "^3.14.0" 29 | 30 | 31 | [tool.poetry.group.dev.dependencies] 32 | ruff = "^0.5.2" 33 | pre-commit = "^3.7.1" 34 | ipykernel = "^6.29.5" 35 | 36 | 37 | [tool.poetry.group.docs.dependencies] 38 | sphinx = "^7.4.7" 39 | sphinx-autobuild = "^2024.4.16" 40 | myst-parser = "^3.0.1" 41 | nbsphinx = "^0.9.4" 42 | sphinx-autoapi = "^3.2.0" 43 | sphinx-rtd-theme = "^2.0.0" 44 | 45 | [tool.ruff] 46 | # Exclude a variety of commonly ignored directories. 47 | exclude = [ 48 | ".bzr", 49 | ".direnv", 50 | ".eggs", 51 | ".git", 52 | ".git-rewrite", 53 | ".hg", 54 | ".ipynb_checkpoints", 55 | ".mypy_cache", 56 | ".nox", 57 | ".pants.d", 58 | ".pyenv", 59 | ".pytest_cache", 60 | ".pytype", 61 | ".ruff_cache", 62 | ".svn", 63 | ".tox", 64 | ".venv", 65 | ".vscode", 66 | "__pypackages__", 67 | "_build", 68 | "buck-out", 69 | "build", 70 | "dist", 71 | "node_modules", 72 | "site-packages", 73 | "venv", 74 | ] 75 | 76 | # Same as Black. 77 | line-length = 88 78 | indent-width = 4 79 | 80 | # Assume Python 3.8 81 | target-version = "py38" 82 | 83 | [tool.ruff.lint] 84 | # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. 85 | # Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or 86 | # McCabe complexity (`C901`) by default. 87 | select = ["E4", "E7", "E9", "F"] 88 | ignore = [] 89 | 90 | # Allow fix for all enabled rules (when `--fix`) is provided. 91 | fixable = ["ALL"] 92 | unfixable = [] 93 | 94 | # Allow unused variables when underscore-prefixed. 95 | dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" 96 | 97 | [tool.ruff.format] 98 | # Like Black, use double quotes for strings. 99 | quote-style = "double" 100 | 101 | # Like Black, indent with spaces, rather than tabs. 102 | indent-style = "space" 103 | 104 | # Like Black, respect magic trailing commas. 105 | skip-magic-trailing-comma = false 106 | 107 | # Like Black, automatically detect the appropriate line ending. 108 | line-ending = "auto" 109 | 110 | # Enable auto-formatting of code examples in docstrings. Markdown, 111 | # reStructuredText code/literal blocks and doctests are all supported. 112 | # 113 | # This is currently disabled by default, but it is planned for this 114 | # to be opt-out in the future. 115 | docstring-code-format = false 116 | 117 | # Set the line length limit used when formatting code snippets in 118 | # docstrings. 119 | # 120 | # This only has an effect when the `docstring-code-format` setting is 121 | # enabled. 122 | docstring-code-line-length = "dynamic" 123 | 124 | [build-system] 125 | requires = ["poetry-core"] 126 | build-backend = "poetry.core.masonry.api" 127 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | filterwarnings = 3 | ignore::DeprecationWarning 4 | ignore::PendingDeprecationWarning 5 | ignore::UserWarning 6 | ignore::RuntimeWarning 7 | ignore::ResourceWarning 8 | ignore::ImportWarning 9 | ignore::FutureWarning 10 | ignore::SyntaxWarning 11 | ignore::Warning 12 | ignore::pytest.PytestUnknownMarkWarning 13 | ignore::pytest.PytestUnhandledCoroutineWarning 14 | ignore::pytest.PytestCollectionWarning -------------------------------------------------------------------------------- /src/fmp_py/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TexasCoding/fmp-py/3770dcc3254ce59c2ba7fc5578aacafea54a19df/src/fmp_py/__init__.py -------------------------------------------------------------------------------- /src/fmp_py/fmp_base.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pandas as pd 3 | import requests 4 | from requests.adapters import HTTPAdapter 5 | from urllib3.util import Retry 6 | from dotenv import load_dotenv 7 | from typing import Dict, Any 8 | 9 | load_dotenv() 10 | 11 | FMP_API_KEY = os.getenv("FMP_API_KEY", "") 12 | FMP_BASE_URL = "https://financialmodelingprep.com/api/" 13 | 14 | 15 | class FmpBase: 16 | def __init__(self, api_key: str = FMP_API_KEY) -> None: 17 | """ 18 | Initialize the FmpBase class. 19 | 20 | Args: 21 | api_key (str): The API key for Financial Modeling Prep. Defaults to the value from environment variable. 22 | """ 23 | if not api_key: 24 | raise ValueError( 25 | "API Key is required. Set it as environment variable 'FMP_API_KEY' or pass it directly." 26 | ) 27 | self.api_key = api_key 28 | 29 | status_forcelist = [429, 500, 502, 503, 504] 30 | 31 | self.retry_strategy = Retry( 32 | total=3, 33 | backoff_factor=2.0, 34 | status_forcelist=status_forcelist, 35 | ) 36 | self.adapter = HTTPAdapter(max_retries=self.retry_strategy) 37 | self.session = requests.Session() 38 | self.session.mount("https://", self.adapter) 39 | self.session.mount("http://", self.adapter) 40 | 41 | def fill_na(self, df: pd.DataFrame) -> pd.DataFrame: 42 | for col in df: 43 | dt = df[col].dtype 44 | if dt is int or dt is float: 45 | df[col].fillna(0, inplace=True) 46 | else: 47 | df[col].fillna("", inplace=True) 48 | 49 | return df 50 | 51 | def clean_value(self, value, type) -> Any: 52 | if type is int: 53 | return int(value) if value else int(0) 54 | elif type is float: 55 | return float(value) if value else float(0.0) 56 | elif type is str: 57 | return str(value) if value else str("") 58 | elif type is bool: 59 | return bool(value) if value else bool(False) 60 | else: 61 | return value 62 | 63 | def get_request(self, url: str, params: Dict[str, Any] = None) -> Dict[str, Any]: 64 | """ 65 | Make a GET request to the specified URL with the given parameters. 66 | 67 | Args: 68 | url (str): The URL endpoint to make the request to. 69 | params (Dict[str, Any]): Additional parameters for the request. 70 | 71 | Returns: 72 | Dict[str, Any]: The JSON response from the server. 73 | 74 | Raises: 75 | Exception: If the response status code is not 200 or if there is a request exception. 76 | """ 77 | params = params or {} 78 | params["apikey"] = self.api_key 79 | full_url = f"{FMP_BASE_URL}{url}" 80 | 81 | try: 82 | response = self.session.get(full_url, params=params) 83 | response.raise_for_status() 84 | except requests.exceptions.RequestException as e: 85 | raise Exception(f"Request failed: {e}") 86 | 87 | try: 88 | return response.json() 89 | except ValueError: 90 | raise Exception("Failed to parse JSON response") 91 | 92 | def __del__(self): 93 | """ 94 | Ensure the session is properly closed when the object is deleted. 95 | """ 96 | self.session.close() 97 | -------------------------------------------------------------------------------- /src/fmp_py/fmp_company_search.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from fmp_py.fmp_base import FmpBase 3 | import os 4 | from dotenv import load_dotenv 5 | 6 | load_dotenv() 7 | 8 | """ 9 | This class provides methods for searching for companies on Financial Modeling Prep (FMP). 10 | https://site.financialmodelingprep.com/developer/docs#company-search 11 | 12 | def general_search(self, query: str, exchange: str = None, limit: int = None) -> pd.DataFrame: 13 | Referance: https://site.financialmodelingprep.com/developer/docs#general-search-company-search 14 | 15 | def ticker_search(self, query: str, limit: int = None, exchange: str = None) -> pd.DataFrame: 16 | Referance: https://site.financialmodelingprep.com/developer/docs#ticker-search-company-search 17 | 18 | def name_search(self, query: str, limit: int = None, exchange: str = None) -> pd.DataFrame: 19 | Referance: https://site.financialmodelingprep.com/developer/docs#name-search-company-search 20 | 21 | def cik_name_search(self,company_name: str) -> pd.DataFrame: 22 | Referance: https://site.financialmodelingprep.com/developer/docs#cik-name-search-company-search 23 | 24 | def cik_search(self, cik: str) -> pd.DataFrame: 25 | Referance: https://site.financialmodelingprep.com/developer/docs#cik-search-company-search 26 | 27 | def cusip_search(self, cusip: str) -> pd.DataFrame: 28 | Referance: https://site.financialmodelingprep.com/developer/docs#cusip-search-company-search 29 | 30 | def isin_search(self, isin: str) -> pd.DataFrame: 31 | Referance: https://site.financialmodelingprep.com/developer/docs#isin-search-company-search 32 | """ 33 | 34 | 35 | class FmpCompanySearch(FmpBase): 36 | def __init__(self, api_key: str = os.getenv("FMP_API_KEY")): 37 | super().__init__(api_key) 38 | 39 | ############################ 40 | # ISIN Search 41 | ############################ 42 | def isin_search(self, isin: str) -> pd.DataFrame: 43 | """ 44 | Searches for a company using its ISIN (International Securities Identification Number). 45 | 46 | Args: 47 | isin (str): The ISIN of the company to search for. 48 | 49 | Returns: 50 | pd.DataFrame: A DataFrame containing the company information. 51 | 52 | Raises: 53 | ValueError: If no data is found for the specified parameters. 54 | """ 55 | 56 | url = "v4/search/isin" 57 | params = {"isin": isin} 58 | 59 | response = self.get_request(url, params) 60 | 61 | if not response: 62 | raise ValueError("No data found for the specified parameters.") 63 | 64 | return ( 65 | pd.DataFrame(response) 66 | .rename( 67 | columns={ 68 | "symbol": "symbol", 69 | "price": "price", 70 | "beta": "beta", 71 | "volAvg": "vol_avg", 72 | "mktCap": "mkt_cap", 73 | "lastDiv": "last_div", 74 | "range": "range", 75 | "changes": "changes", 76 | "companyName": "company_name", 77 | "currency": "currency", 78 | "cik": "cik", 79 | "isin": "isin", 80 | "cusip": "cusip", 81 | "exchange": "exchange", 82 | "exchangeShortName": "exchange_short_name", 83 | "industry": "industry", 84 | "website": "website", 85 | "description": "description", 86 | "ceo": "ceo", 87 | "sector": "sector", 88 | "fullTimeEmployees": "full_time_employees", 89 | "phone": "phone", 90 | "address": "address", 91 | "city": "city", 92 | "state": "state", 93 | "zip": "zip", 94 | "dcfDiff": "dcf_diff", 95 | "dcf": "dcf", 96 | "image": "image", 97 | "ipoDate": "ipo_date", 98 | "defaultImage": "default_image", 99 | "isEtf": "is_etf", 100 | "isActivelyTrading": "is_actively_trading", 101 | "isAdr": "is_adr", 102 | "isFund": "is_fund", 103 | } 104 | ) 105 | .astype( 106 | { 107 | "price": "float", 108 | "beta": "float", 109 | "vol_avg": "int", 110 | "mkt_cap": "int", 111 | "last_div": "int", 112 | "changes": "float", 113 | "full_time_employees": "int", 114 | "dcf_diff": "float", 115 | "dcf": "float", 116 | "is_etf": "bool", 117 | "is_actively_trading": "bool", 118 | "is_adr": "bool", 119 | "is_fund": "bool", 120 | "default_image": "bool", 121 | "ipo_date": "datetime64[ns]", 122 | } 123 | ) 124 | ) 125 | 126 | ############################ 127 | # CUSIP Search 128 | ############################ 129 | def cusip_search(self, cusip: str) -> pd.DataFrame: 130 | """ 131 | Search for a company using its CUSIP identifier. 132 | 133 | Parameters: 134 | cusip (str): The CUSIP identifier of the company to search for. 135 | 136 | Returns: 137 | pd.DataFrame: A DataFrame containing the response data. 138 | 139 | Raises: 140 | ValueError: If no data is found for the specified parameters. 141 | """ 142 | url = f"v3/cusip/{cusip}" 143 | response = self.get_request(url) 144 | 145 | if not response: 146 | raise ValueError("No data found for the specified parameters.") 147 | 148 | return pd.DataFrame(response) 149 | 150 | ############################ 151 | # CIK Search 152 | ############################ 153 | def cik_search(self, cik: str) -> pd.DataFrame: 154 | """ 155 | Retrieves company information based on the specified CIK (Central Index Key). 156 | 157 | Args: 158 | cik (str): The CIK of the company. 159 | 160 | Returns: 161 | pd.DataFrame: A DataFrame containing the company information. 162 | 163 | Raises: 164 | ValueError: If no data is found for the specified parameters. 165 | """ 166 | url = f"v3/cik/{cik}" 167 | response = self.get_request(url) 168 | 169 | if not response: 170 | raise ValueError("No data found for the specified parameters.") 171 | 172 | return pd.DataFrame(response) 173 | 174 | ############################ 175 | # CIK Name Search 176 | ############################ 177 | def cik_name_search( 178 | self, 179 | company_name: str, 180 | ) -> pd.DataFrame: 181 | """ 182 | Searches for a company's CIK (Central Index Key) based on its name. 183 | 184 | Args: 185 | company_name (str): The name of the company to search for. 186 | 187 | Returns: 188 | pd.DataFrame: A DataFrame containing the response data. 189 | 190 | Raises: 191 | ValueError: If no data is found for the specified parameters. 192 | """ 193 | url = f"v3/cik-search/{company_name}" 194 | response = self.get_request(url) 195 | 196 | if not response: 197 | raise ValueError("No data found for the specified parameters.") 198 | 199 | return pd.DataFrame(response) 200 | 201 | ############################ 202 | # Name Search 203 | ############################ 204 | def name_search( 205 | self, query: str, limit: int = None, exchange: str = None 206 | ) -> pd.DataFrame: 207 | """ 208 | Search for company names based on the specified query. 209 | 210 | Args: 211 | query (str): The search query. 212 | limit (int, optional): The maximum number of results to return. Defaults to None. 213 | exchange (str, optional): The exchange to search within. Defaults to None. 214 | 215 | Returns: 216 | pd.DataFrame: A DataFrame containing the search results with columns: 217 | - symbol: The company symbol. 218 | - name: The company name. 219 | - currency: The currency in which the company operates. 220 | - stock_exchange: The stock exchange where the company is listed. 221 | - exchange_short_name: The short name of the exchange. 222 | 223 | Raises: 224 | ValueError: If an invalid exchange is provided. 225 | ValueError: If no data is found for the specified parameters. 226 | """ 227 | url = "v3/search-name" 228 | return self._process_search_data(url, query, exchange, limit) 229 | 230 | ############################ 231 | # Ticker Search 232 | ############################ 233 | def ticker_search( 234 | self, query: str, limit: int = None, exchange: str = None 235 | ) -> pd.DataFrame: 236 | """ 237 | Search for tickers based on the specified query, limit, and exchange. 238 | 239 | Args: 240 | query (str): The search query. 241 | limit (int, optional): The maximum number of results to return. Defaults to None. 242 | exchange (str, optional): The exchange to search for tickers. Defaults to None. 243 | 244 | Returns: 245 | pd.DataFrame: A DataFrame containing the search results with columns: 246 | - symbol: The ticker symbol. 247 | - name: The company name. 248 | - currency: The currency in which the stock is traded. 249 | - stock_exchange: The stock exchange where the stock is listed. 250 | - exchange_short_name: The short name of the exchange. 251 | 252 | Raises: 253 | ValueError: If an invalid exchange is provided. 254 | ValueError: If no data is found for the specified parameters. 255 | """ 256 | url = "v3/search" 257 | return self._process_search_data(url, query, exchange, limit) 258 | 259 | ############################ 260 | # General Search 261 | ############################ 262 | def general_search( 263 | self, query: str, exchange: str = None, limit: int = None 264 | ) -> pd.DataFrame: 265 | """ 266 | Perform a general search for companies based on the specified query. 267 | 268 | Args: 269 | query (str): The search query. 270 | exchange (str, optional): The exchange to filter the search results. Defaults to None. 271 | limit (int, optional): The maximum number of search results to return. Defaults to None. 272 | 273 | Returns: 274 | pd.DataFrame: A DataFrame containing the search results with the following columns: 275 | - symbol: The company symbol. 276 | - name: The company name. 277 | - currency: The currency in which the company is traded. 278 | - stock_exchange: The stock exchange where the company is listed. 279 | - exchange_short_name: The short name of the exchange. 280 | 281 | Raises: 282 | ValueError: If an invalid exchange is provided. 283 | ValueError: If no data is found for the specified parameters. 284 | """ 285 | url = "v3/search" 286 | return self._process_search_data(url, query, exchange, limit) 287 | 288 | def _process_search_data( 289 | self, url: str, query: str, exchange: str, limit: int 290 | ) -> pd.DataFrame: 291 | if exchange and exchange not in self._available_exchanges(): 292 | raise ValueError( 293 | f"Invalid exchange: {exchange}. Please choose from {self._available_exchanges()}." 294 | ) 295 | 296 | params = {"query": query, "limit": limit, "exchange": exchange} 297 | 298 | response = self.get_request(url, params) 299 | 300 | if not response: 301 | raise ValueError("No data found for the specified parameters.") 302 | 303 | return ( 304 | pd.DataFrame(response) 305 | .rename( 306 | columns={ 307 | "symbol": "symbol", 308 | "name": "name", 309 | "currency": "currency", 310 | "stockExchange": "stock_exchange", 311 | "exchangeShortName": "exchange_short_name", 312 | } 313 | ) 314 | .sort_values(by="symbol") 315 | .reset_index(drop=True) 316 | ) 317 | 318 | ############################ 319 | # Available Exchanges 320 | ############################ 321 | @staticmethod 322 | def _available_exchanges() -> list: 323 | """ 324 | Returns a list of available exchanges. 325 | 326 | Returns: 327 | list: A list of available exchanges. 328 | """ 329 | return [ 330 | "AMEX", 331 | "AMS", 332 | "AQS", 333 | "ASX", 334 | "ATH", 335 | "BER", 336 | "BME", 337 | "BRU", 338 | "BSE", 339 | "BUD", 340 | "BUE", 341 | "CAI", 342 | "CBOE", 343 | "CNQ", 344 | "CPH", 345 | "DFM", 346 | "DOH", 347 | "DUS", 348 | "DXE", 349 | "ETF", 350 | "EURONEXT", 351 | "HAM", 352 | "HEL", 353 | "HKSE", 354 | "ICE", 355 | "IOB", 356 | "IST", 357 | "JKT", 358 | "JNB", 359 | "JPX", 360 | "KLS", 361 | "KOE", 362 | "KSC", 363 | "KUW", 364 | "LSE", 365 | "MCX", 366 | "MEX", 367 | "MIL", 368 | "MUN", 369 | "NASDAQ", 370 | "NEO", 371 | "NIM", 372 | "NSE", 373 | "NYSE", 374 | "NZE", 375 | "OEM", 376 | "OQB", 377 | "OQX", 378 | "OSL", 379 | "OTC", 380 | "PNK", 381 | "PRA", 382 | "RIS", 383 | "SAO", 384 | "SAU", 385 | "SES", 386 | "SET", 387 | "SGO", 388 | "SHH", 389 | "SHZ", 390 | "SIX", 391 | "STO", 392 | "STU", 393 | "TAI", 394 | "TLV", 395 | "TSX", 396 | "TSXV", 397 | "TWO", 398 | "VIE", 399 | "VSE", 400 | "WSE", 401 | "XETRA", 402 | ] 403 | -------------------------------------------------------------------------------- /src/fmp_py/fmp_dividends.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pendulum 4 | from fmp_py.fmp_base import FmpBase 5 | import pandas as pd 6 | from dotenv import load_dotenv 7 | 8 | load_dotenv() 9 | """ 10 | This class is used to access the FMP dividends endpoints. 11 | Reference: https://site.financialmodelingprep.com/developer/docs#dividends 12 | 13 | def dividends_calendar(self, from_date, to_date): 14 | Reference: https://site.financialmodelingprep.com/developer/docs#dividends-calendar-dividends 15 | 16 | def dividends_historical(self, symbol, from_date, to_date): 17 | Reference: https://site.financialmodelingprep.com/developer/docs#dividends-historical-dividends 18 | """ 19 | 20 | 21 | class FmpDividends(FmpBase): 22 | def __init__(self, api_key: str = os.getenv("FMP_API_KEY")): 23 | super().__init__(api_key) 24 | 25 | ############################# 26 | # Dividends Calendar 27 | ############################# 28 | def dividends_calendar(self, from_date: str, to_date: str) -> pd.DataFrame: 29 | """ 30 | Retrieves the dividends calendar data for a specified date range. 31 | Args: 32 | from_date (str): The starting date of the date range in "YYYY-MM-DD" format. 33 | to_date (str): The ending date of the date range in "YYYY-MM-DD" format. 34 | Returns: 35 | pd.DataFrame: A DataFrame containing the dividends calendar data with the following columns: 36 | - date: The date of the dividend event. 37 | - label: The label of the dividend event. 38 | - adj_dividend: The adjusted dividend amount. 39 | - symbol: The symbol of the stock. 40 | - dividend: The dividend amount. 41 | - record_date: The record date of the dividend event. 42 | - payment_date: The payment date of the dividend event. 43 | - declaration_date: The declaration date of the dividend event. 44 | Raises: 45 | ValueError: If from_date is greater than to_date or if the request to fetch dividends calendar data fails. 46 | """ 47 | 48 | from_date = pendulum.parse(from_date).format("YYYY-MM-DD") 49 | to_date = pendulum.parse(to_date).format("YYYY-MM-DD") 50 | if from_date > to_date: 51 | raise ValueError("from_date must be less than or equal to to_date") 52 | 53 | url = "v3/stock_dividend_calendar" 54 | params = {"from": from_date, "to": to_date} 55 | response = self.get_request(url=url, params=params) 56 | 57 | if not response: 58 | raise ValueError("Failed to fetch dividends calendar data.") 59 | 60 | data_df = ( 61 | pd.DataFrame(response) 62 | .fillna(0) 63 | .rename( 64 | columns={ 65 | "date": "date", 66 | "label": "label", 67 | "adjDividend": "adj_dividend", 68 | "symbol": "symbol", 69 | "dividend": "dividend", 70 | "recordDate": "record_date", 71 | "paymentDate": "payment_date", 72 | "declarationDate": "declaration_date", 73 | } 74 | ) 75 | .astype( 76 | { 77 | "date": "datetime64[ns]", 78 | "label": "str", 79 | "adj_dividend": "float64", 80 | "symbol": "str", 81 | "dividend": "float64", 82 | "record_date": "datetime64[ns]", 83 | "payment_date": "datetime64[ns]", 84 | "declaration_date": "datetime64[ns]", 85 | } 86 | ) 87 | .sort_values(by="date", ascending=True) 88 | .reset_index(drop=True) 89 | ) 90 | 91 | return data_df 92 | 93 | ############################# 94 | # Dividends Historical 95 | ############################# 96 | def dividends_historical(self, symbol: str) -> pd.DataFrame: 97 | """ 98 | Fetches historical dividends data for a given stock symbol. 99 | Args: 100 | symbol (str): The stock symbol. 101 | Returns: 102 | pd.DataFrame: A DataFrame containing the historical dividends data. 103 | Raises: 104 | ValueError: If failed to fetch historical dividends data for the given symbol. 105 | """ 106 | 107 | url = f"v3/historical-price-full/stock_dividend/{symbol}" 108 | 109 | response = self.get_request(url=url)["historical"] 110 | 111 | if not response: 112 | raise ValueError(f"Failed to fetch historical dividends data for {symbol}.") 113 | 114 | data_df = ( 115 | pd.DataFrame(response) 116 | .fillna(0) 117 | .rename( 118 | columns={ 119 | "date": "date", 120 | "label": "label", 121 | "adjDividend": "adj_dividend", 122 | "dividend": "dividend", 123 | "recordDate": "record_date", 124 | "paymentDate": "payment_date", 125 | "declarationDate": "declaration_date", 126 | } 127 | ) 128 | .astype( 129 | { 130 | "date": "datetime64[ns]", 131 | "label": "str", 132 | "adj_dividend": "float64", 133 | "dividend": "float64", 134 | "record_date": "datetime64[ns]", 135 | "payment_date": "datetime64[ns]", 136 | "declaration_date": "datetime64[ns]", 137 | } 138 | ) 139 | .sort_values(by="date", ascending=True) 140 | .reset_index(drop=True) 141 | ) 142 | data_df["symbol"] = symbol 143 | 144 | return data_df 145 | -------------------------------------------------------------------------------- /src/fmp_py/fmp_earnings.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from fmp_py.fmp_base import FmpBase 3 | import os 4 | import pendulum 5 | from dotenv import load_dotenv 6 | 7 | load_dotenv() 8 | 9 | pd.set_option("future.no_silent_downcasting", True) 10 | 11 | 12 | """ 13 | This module provides functions to retrieve earnings data from the Financial Modeling Prep API. 14 | References: 15 | - https://site.financialmodelingprep.com/developer/docs#earnings 16 | 17 | def earnings_surprises(self, symbol: str) -> pd.DataFrame: 18 | References: https://site.financialmodelingprep.com/developer/docs#earnings-surprises-earnings 19 | 20 | def earnings_historical(self, symbol: str) -> pd.DataFrame: 21 | References: https://site.financialmodelingprep.com/developer/docs#earnings-historical-earnings 22 | 23 | def earnings_calendar(self, from_date: str, to_date: str) -> pd.DataFrame: 24 | References: https://site.financialmodelingprep.com/developer/docs#earnings-calendar-earnings 25 | 26 | def earnings_within_weeks(self, symbol: str, weeks_ahead: int = 2) -> bool: 27 | References: This function is not documented in the Financial Modeling Prep API documentation. 28 | It is a custom function that is not documented in the Financial Modeling Prep API documentation. 29 | 30 | def earnings_confirmed(self, symbol: str, date: str) -> pd.DataFrame: 31 | References: https://site.financialmodelingprep.com/developer/docs#earnings-confirmed-earnings 32 | """ 33 | 34 | 35 | class FmpEarnings(FmpBase): 36 | def __init__(self, api_key: str = os.getenv("FMP_API_KEY")): 37 | super().__init__(api_key) 38 | 39 | ############################# 40 | # Earnings Surprises 41 | ############################# 42 | def earnings_surprises(self, symbol: str) -> pd.DataFrame: 43 | """ 44 | Retrieves the earnings surprises for a given symbol. 45 | Args: 46 | symbol (str): The symbol of the stock. 47 | Returns: 48 | pd.DataFrame: A DataFrame containing the earnings surprises data. 49 | """ 50 | url = f"v3/earnings-surprises/{symbol}" 51 | response = self.get_request(url) 52 | 53 | if not response: 54 | raise ValueError("Error fetching earnings surprises data") 55 | 56 | data_df = ( 57 | ( 58 | pd.DataFrame(response) 59 | .fillna(0) 60 | .rename( 61 | columns={ 62 | "symbol": "symbol", 63 | "date": "date", 64 | "actualEarningResult": "actual_earning_result", 65 | "estimatedEarning": "estimated_earning", 66 | } 67 | ) 68 | .astype( 69 | { 70 | "symbol": "string", 71 | "date": "datetime64[ns]", 72 | "actual_earning_result": "float", 73 | "estimated_earning": "float", 74 | } 75 | ) 76 | ) 77 | .sort_values(by="date", ascending=True) 78 | .reset_index(drop=True) 79 | ) 80 | 81 | return data_df 82 | 83 | ############################# 84 | # Earnings Confirmed 85 | ############################# 86 | def earnings_confirmed(self, from_date: str, to_date: str) -> pd.DataFrame: 87 | """ 88 | Fetches the confirmed earnings calendar data from the specified date range. 89 | Args: 90 | from_date (str): The starting date of the range in the format 'YYYY-MM-DD'. 91 | to_date (str): The ending date of the range in the format 'YYYY-MM-DD'. 92 | Returns: 93 | pd.DataFrame: A DataFrame containing the fetched earnings calendar data, with the following columns: 94 | - symbol: The symbol of the company. 95 | - exchange: The exchange where the company is listed. 96 | - time: The time of the earnings release. 97 | - when: The time period of the earnings release (e.g., 'Before Market Open', 'After Market Close'). 98 | - date: The date of the earnings release. 99 | - publication_date: The publication date of the earnings release. 100 | - title: The title of the earnings release. 101 | - url: The URL of the earnings release. 102 | Raises: 103 | ValueError: If there is an error fetching the earnings calendar data. 104 | """ 105 | from_date = pendulum.parse(from_date).format("YYYY-MM-DD") 106 | to_date = pendulum.parse(to_date).format("YYYY-MM-DD") 107 | if from_date > to_date: 108 | raise ValueError("from_date must be less than or equal to to_date") 109 | 110 | url = "v4/earning-calendar-confirmed" 111 | params = {"from": from_date, "to": to_date} 112 | response = self.get_request(url, params) 113 | 114 | if not response: 115 | raise ValueError("Error fetching earnings calendar data") 116 | 117 | data_df = ( 118 | ( 119 | pd.DataFrame(response) 120 | .fillna(0) 121 | .rename( 122 | columns={ 123 | "symbol": "symbol", 124 | "exchange": "exchange", 125 | "time": "time", 126 | "when": "when", 127 | "date": "date", 128 | "publicationDate": "publication_date", 129 | "title": "title", 130 | "url": "url", 131 | } 132 | ) 133 | .astype( 134 | { 135 | "symbol": "string", 136 | "exchange": "string", 137 | "time": "string", 138 | "when": "string", 139 | "date": "datetime64[ns]", 140 | "publication_date": "datetime64[ns]", 141 | "title": "string", 142 | "url": "string", 143 | } 144 | ) 145 | ) 146 | .sort_values(by="date", ascending=True) 147 | .reset_index(drop=True) 148 | ) 149 | 150 | return data_df 151 | 152 | ############################# 153 | # Earnings Calendar 154 | ############################# 155 | def earnings_calendar(self, from_date: str, to_date: str) -> pd.DataFrame: 156 | """ 157 | Retrieves the earnings calendar data from the specified date range. 158 | Args: 159 | from_date (str): The starting date of the earnings calendar data in "YYYY-MM-DD" format. 160 | to_date (str): The ending date of the earnings calendar data in "YYYY-MM-DD" format. 161 | Returns: 162 | pd.DataFrame: A DataFrame containing the earnings calendar data with the following columns: 163 | - date: The date of the earnings release. 164 | - symbol: The symbol of the company. 165 | - eps: The earnings per share. 166 | - eps_estimated: The estimated earnings per share. 167 | - time: The time of the earnings release. 168 | - revenue: The revenue. 169 | - revenue_estimated: The estimated revenue. 170 | - fiscal_date_ending: The fiscal date ending. 171 | - updated_from_date: The updated from date. 172 | Raises: 173 | ValueError: If from_date is greater than to_date or if there is an error fetching the earnings calendar data. 174 | """ 175 | 176 | from_date = pendulum.parse(from_date).format("YYYY-MM-DD") 177 | to_date = pendulum.parse(to_date).format("YYYY-MM-DD") 178 | if from_date > to_date: 179 | raise ValueError("from_date must be less than or equal to to_date") 180 | 181 | url = "v3/earning_calendar" 182 | params = {"from": from_date, "to": to_date} 183 | response = self.get_request(url, params) 184 | 185 | if not response: 186 | raise ValueError("Error fetching earnings calendar data") 187 | 188 | data_df = ( 189 | pd.DataFrame(response) 190 | .fillna(0) 191 | .rename( 192 | columns={ 193 | "date": "date", 194 | "symbol": "symbol", 195 | "eps": "eps", 196 | "epsEstimated": "eps_estimated", 197 | "time": "time", 198 | "revenue": "revenue", 199 | "revenueEstimated": "revenue_estimated", 200 | "fiscalDateEnding": "fiscal_date_ending", 201 | "updatedFromDate": "updated_from_date", 202 | } 203 | ) 204 | .astype( 205 | { 206 | "date": "datetime64[ns]", 207 | "symbol": "string", 208 | "eps": "float", 209 | "eps_estimated": "float", 210 | "time": "string", 211 | "revenue": "int", 212 | "revenue_estimated": "int", 213 | "fiscal_date_ending": "datetime64[ns]", 214 | "updated_from_date": "datetime64[ns]", 215 | } 216 | ) 217 | .sort_values(by="date", ascending=True) 218 | .reset_index(drop=True) 219 | ) 220 | 221 | return data_df 222 | 223 | ############################# 224 | # Earnings Historical 225 | ############################# 226 | def earnings_historical(self, symbol: str) -> pd.DataFrame: 227 | """ 228 | Fetches historical earnings data for a given symbol. 229 | Args: 230 | symbol (str): The symbol for which to fetch earnings data. 231 | Returns: 232 | pd.DataFrame: A DataFrame containing the historical earnings data, with the following columns: 233 | - date (datetime64[ns]): The date of the earnings release. 234 | - symbol (str): The symbol of the company. 235 | - eps (float): The earnings per share. 236 | - revenue (int): The revenue. 237 | - eps_estimated (float): The estimated earnings per share. 238 | - revenue_estimated (int): The estimated revenue. 239 | - time (str): The time of the earnings release. 240 | - updated_from_date (datetime64[ns]): The date from which the data was last updated. 241 | - fiscal_date_ending (datetime64[ns]): The fiscal date ending. 242 | Raises: 243 | ValueError: If there is an error fetching the earnings historical data. 244 | """ 245 | 246 | url = f"v3/historical/earning_calendar/{symbol}" 247 | response = self.get_request(url) 248 | 249 | if not response: 250 | raise ValueError("Error fetching earnings historical data") 251 | 252 | data_df = ( 253 | pd.DataFrame(response) 254 | .fillna(0) 255 | .rename( 256 | columns={ 257 | "date": "date", 258 | "symbol": "symbol", 259 | "eps": "eps", 260 | "revenue": "revenue", 261 | "epsEstimated": "eps_estimated", 262 | "revenueEstimated": "revenue_estimated", 263 | "time": "time", 264 | "updatedFromDate": "updated_from_date", 265 | "fiscalDateEnding": "fiscal_date_ending", 266 | } 267 | ) 268 | .astype( 269 | { 270 | "date": "datetime64[ns]", 271 | "time": "str", 272 | "updated_from_date": "datetime64[ns]", 273 | "fiscal_date_ending": "datetime64[ns]", 274 | "eps": "float", 275 | "revenue": "int", 276 | "eps_estimated": "float", 277 | "revenue_estimated": "int", 278 | }, 279 | errors="ignore", 280 | ) 281 | .sort_values(by="date", ascending=True) 282 | ) 283 | 284 | return data_df 285 | 286 | ############################# 287 | # Earnings Within Weeks 288 | ############################# 289 | def earnings_within_weeks(self, symbol: str, weeks_ahead: int = 2) -> bool: 290 | """ 291 | Checks if there are earnings within a specified number of weeks ahead. 292 | Args: 293 | symbol (str): The symbol of the stock. 294 | weeks_ahead (int, optional): The number of weeks ahead to check for earnings. Defaults to 2. 295 | Returns: 296 | bool: True if there are earnings within the specified number of weeks ahead, False otherwise. 297 | """ 298 | 299 | try: 300 | earnings_history = self.earnings_historical(symbol) 301 | 302 | todays_date = pd.to_datetime(pendulum.today().to_date_string()) 303 | future_date = pd.to_datetime( 304 | pendulum.today().add(weeks=weeks_ahead).to_date_string() 305 | ) 306 | 307 | earnings_history = earnings_history[earnings_history["date"] >= todays_date] 308 | earnings_history = earnings_history[earnings_history["date"] <= future_date] 309 | 310 | if earnings_history.empty: 311 | return False 312 | 313 | except ValueError: 314 | return False 315 | 316 | return True 317 | -------------------------------------------------------------------------------- /src/fmp_py/fmp_forex.py: -------------------------------------------------------------------------------- 1 | import pendulum 2 | from fmp_py.fmp_base import FmpBase 3 | import pandas as pd 4 | import os 5 | from dotenv import load_dotenv 6 | 7 | load_dotenv() 8 | 9 | 10 | class FmpForex(FmpBase): 11 | def __init__(self, api_key: str = os.getenv("FMP_API_KEY")) -> None: 12 | super().__init__(api_key) 13 | 14 | #################### 15 | # Forex Daily 16 | #################### 17 | def forex_daily(self, symbol: str) -> pd.DataFrame: 18 | """ 19 | Retrieves daily forex data for a given symbol. 20 | Args: 21 | symbol (str): The symbol for the forex pair. 22 | Returns: 23 | pd.DataFrame: A DataFrame containing the historical daily forex data. 24 | Raises: 25 | ValueError: If no data is found for the given symbol. 26 | """ 27 | 28 | clean_symbol = symbol.replace("/", "") 29 | url = f"v3/historical-price-full/{clean_symbol}" 30 | 31 | try: 32 | response = self.get_request(url)["historical"] 33 | except KeyError: 34 | raise ValueError(f"No data found for {symbol}") 35 | 36 | if not response: 37 | raise ValueError(f"No data found for {symbol}") 38 | 39 | data_df = ( 40 | pd.DataFrame(response) 41 | .fillna(0) 42 | .rename( 43 | columns={ 44 | "date": "date", 45 | "open": "open", 46 | "high": "high", 47 | "low": "low", 48 | "close": "close", 49 | "adjClose": "adj_close", 50 | "volume": "volume", 51 | "unadjustedVolume": "unadjusted_volume", 52 | "change": "change", 53 | "changePercent": "change_percent", 54 | "vwap": "vwap", 55 | "label": "label", 56 | "changeOverTime": "change_over_time", 57 | } 58 | ) 59 | .astype( 60 | { 61 | "date": "datetime64[ns]", 62 | "open": "float", 63 | "high": "float", 64 | "low": "float", 65 | "close": "float", 66 | "adj_close": "float", 67 | "volume": "int", 68 | "unadjusted_volume": "int", 69 | "change": "float", 70 | "change_percent": "float", 71 | "vwap": "float", 72 | "label": "str", 73 | "change_over_time": "float", 74 | } 75 | ) 76 | ) 77 | 78 | return data_df 79 | 80 | #################### 81 | # Intraday Forex Quote 82 | #################### 83 | def intraday_forex_quote( 84 | self, symbol: str, interval: str, from_date: str, to_date: str 85 | ) -> pd.DataFrame: 86 | """ 87 | Retrieves intraday forex quotes for a given symbol within a specified time interval. 88 | Args: 89 | symbol (str): The symbol of the forex pair. 90 | interval (str): The time interval for the quotes. Must be one of: 1min, 5min, 15min, 30min, 1hour, 4hour, 1day, 1week, 1month. 91 | from_date (str): The starting date for the quotes in the format "YYYY-MM-DD". 92 | to_date (str): The ending date for the quotes in the format "YYYY-MM-DD". 93 | Returns: 94 | pd.DataFrame: A DataFrame containing the intraday forex quotes with the following columns: 95 | - date: The date and time of the quote. 96 | - open: The opening price of the forex pair. 97 | - high: The highest price of the forex pair during the interval. 98 | - low: The lowest price of the forex pair during the interval. 99 | - close: The closing price of the forex pair. 100 | - volume: The trading volume during the interval. 101 | Raises: 102 | ValueError: If the from_date is greater than the to_date. 103 | ValueError: If an invalid interval is provided. 104 | ValueError: If no data is found for the given symbol. 105 | """ 106 | 107 | from_date = pendulum.parse(from_date).format("YYYY-MM-DD") 108 | to_date = pendulum.parse(to_date).format("YYYY-MM-DD") 109 | if from_date > to_date: 110 | raise ValueError("from_date must be less than or equal to to_date") 111 | 112 | clean_symbol = symbol.replace("/", "") 113 | 114 | if interval not in [ 115 | "1min", 116 | "5min", 117 | "15min", 118 | "30min", 119 | "1hour", 120 | "4hour", 121 | "1day", 122 | "1week", 123 | "1month", 124 | ]: 125 | raise ValueError( 126 | "Invalid interval. Please choose from: 1min, 5min, 15min, 30min, 1hour, 4hour, 1day, 1week, 1month" 127 | ) 128 | 129 | url = f"v3/historical-chart/{interval}/{clean_symbol}" 130 | params = {"from": from_date, "to": to_date} 131 | 132 | response = self.get_request(url, params=params) 133 | 134 | if not response: 135 | raise ValueError(f"No data found for {symbol}") 136 | 137 | data_df = ( 138 | pd.DataFrame(response) 139 | .fillna(0) 140 | .astype( 141 | { 142 | "date": "datetime64[ns]", 143 | "open": "float", 144 | "high": "float", 145 | "low": "float", 146 | "close": "float", 147 | "volume": "int", 148 | } 149 | ) 150 | ) 151 | 152 | return data_df 153 | 154 | #################### 155 | # Full Forex Quote 156 | #################### 157 | def full_forex_quote(self, symbol: str) -> pd.DataFrame: 158 | """ 159 | Retrieves the full forex quote for a given symbol. 160 | Args: 161 | symbol (str): The symbol for the forex quote. 162 | Returns: 163 | pd.DataFrame: A DataFrame containing the full forex quote data. 164 | Raises: 165 | ValueError: If no data is found for the given symbol. 166 | """ 167 | 168 | clean_symbol = symbol.replace("/", "") 169 | url = f"v3/quote/{clean_symbol}" 170 | 171 | response = self.get_request(url) 172 | 173 | if not response: 174 | raise ValueError(f"No data found for {symbol}") 175 | 176 | data_df = ( 177 | pd.DataFrame(response) 178 | .fillna(0) 179 | .rename( 180 | columns={ 181 | "symbol": "symbol", 182 | "name": "name", 183 | "price": "price", 184 | "changesPercentage": "changes_percentage", 185 | "change": "change", 186 | "dayLow": "day_low", 187 | "dayHigh": "day_high", 188 | "yearHigh": "year_high", 189 | "yearLow": "year_low", 190 | "marketCap": "market_cap", 191 | "priceAvg50": "price_avg_50", 192 | "priceAvg200": "price_avg_200", 193 | "exhange": "exchange", 194 | "volume": "volume", 195 | "avgVolume": "avg_volume", 196 | "open": "open", 197 | "previousClose": "previous_close", 198 | "eps": "eps", 199 | "pe": "pe", 200 | "earningsAnnouncement": "earnings_announcement", 201 | "sharesOutstanding": "shares_outstanding", 202 | "timestamp": "timestamp", 203 | } 204 | ) 205 | .astype( 206 | { 207 | "symbol": "str", 208 | "name": "str", 209 | "price": "float", 210 | "changes_percentage": "float", 211 | "change": "float", 212 | "day_low": "float", 213 | "day_high": "float", 214 | "year_high": "float", 215 | "year_low": "float", 216 | "market_cap": "int", 217 | "price_avg_50": "float", 218 | "price_avg_200": "float", 219 | "exchange": "str", 220 | "volume": "int", 221 | "avg_volume": "int", 222 | "open": "float", 223 | "previous_close": "float", 224 | "eps": "float", 225 | "pe": "float", 226 | "earnings_announcement": "str", 227 | "shares_outstanding": "int", 228 | } 229 | ) 230 | ) 231 | 232 | data_df["timestamp"] = pd.to_datetime(data_df["timestamp"], unit="s") 233 | 234 | return data_df 235 | 236 | #################### 237 | # Forex List 238 | #################### 239 | def forex_list(self) -> pd.DataFrame: 240 | """ 241 | Retrieves a list of available forex currency pairs. 242 | Returns: 243 | pd.DataFrame: A DataFrame containing the following columns: 244 | - symbol: The symbol of the currency pair. 245 | - name: The name of the currency pair. 246 | - currency: The currency of the currency pair. 247 | - stock_exchange: The stock exchange of the currency pair. 248 | - exchange_short_name: The short name of the stock exchange. 249 | Raises: 250 | ValueError: If no data is found. 251 | """ 252 | 253 | url = "v3/symbol/available-forex-currency-pairs" 254 | response = self.get_request(url) 255 | 256 | if not response: 257 | raise ValueError("No data found") 258 | 259 | data_df = ( 260 | pd.DataFrame(response) 261 | .fillna("") 262 | .rename( 263 | columns={ 264 | "symbol": "symbol", 265 | "name": "name", 266 | "currency": "currency", 267 | "stockExchange": "stock_exchange", 268 | "exchangeShortName": "exchange_short_name", 269 | } 270 | ) 271 | .astype( 272 | { 273 | "symbol": "str", 274 | "name": "str", 275 | "currency": "str", 276 | "stock_exchange": "str", 277 | "exchange_short_name": "str", 278 | } 279 | ) 280 | ) 281 | 282 | return data_df 283 | 284 | #################### 285 | # Full Forex Quote List 286 | #################### 287 | def full_forex_quote_list(self) -> pd.DataFrame: 288 | """ 289 | Retrieves a full list of forex quotes. 290 | Returns: 291 | pd.DataFrame: A DataFrame containing the forex quotes with the following columns: 292 | - symbol (str): The symbol of the forex. 293 | - name (str): The name of the forex. 294 | - price (float): The current price of the forex. 295 | - changes_percentage (float): The percentage change in price. 296 | - change (float): The change in price. 297 | - day_low (float): The lowest price of the day. 298 | - day_high (float): The highest price of the day. 299 | - year_high (float): The highest price in the past year. 300 | - year_low (float): The lowest price in the past year. 301 | - market_cap (int): The market capitalization of the forex. 302 | - price_avg_50 (float): The 50-day average price. 303 | - price_avg_200 (float): The 200-day average price. 304 | - exchange (str): The exchange where the forex is traded. 305 | - volume (int): The trading volume of the forex. 306 | - avg_volume (int): The average trading volume of the forex. 307 | - open (float): The opening price of the forex. 308 | - previous_close (float): The previous closing price of the forex. 309 | - eps (float): The earnings per share of the forex. 310 | - pe (float): The price-to-earnings ratio of the forex. 311 | - earnings_announcement (str): The announcement date of the earnings. 312 | - shares_outstanding (int): The number of shares outstanding. 313 | - timestamp (datetime): The timestamp of the data. 314 | Raises: 315 | ValueError: If no data is found. 316 | """ 317 | 318 | url = "v3/quotes/forex" 319 | response = self.get_request(url) 320 | 321 | if not response: 322 | raise ValueError("No data found") 323 | 324 | data_df = ( 325 | pd.DataFrame(response) 326 | .fillna(0) 327 | .rename( 328 | columns={ 329 | "symbol": "symbol", 330 | "name": "name", 331 | "price": "price", 332 | "changesPercentage": "changes_percentage", 333 | "change": "change", 334 | "dayLow": "day_low", 335 | "dayHigh": "day_high", 336 | "yearHigh": "year_high", 337 | "yearLow": "year_low", 338 | "marketCap": "market_cap", 339 | "priceAvg50": "price_avg_50", 340 | "priceAvg200": "price_avg_200", 341 | "exhange": "exchange", 342 | "volume": "volume", 343 | "avgVolume": "avg_volume", 344 | "open": "open", 345 | "previousClose": "previous_close", 346 | "eps": "eps", 347 | "pe": "pe", 348 | "earningsAnnouncement": "earnings_announcement", 349 | "sharesOutstanding": "shares_outstanding", 350 | "timestamp": "timestamp", 351 | } 352 | ) 353 | .astype( 354 | { 355 | "symbol": "str", 356 | "name": "str", 357 | "price": "float", 358 | "changes_percentage": "float", 359 | "change": "float", 360 | "day_low": "float", 361 | "day_high": "float", 362 | "year_high": "float", 363 | "year_low": "float", 364 | "market_cap": "int", 365 | "price_avg_50": "float", 366 | "price_avg_200": "float", 367 | "exchange": "str", 368 | "volume": "int", 369 | "avg_volume": "int", 370 | "open": "float", 371 | "previous_close": "float", 372 | "eps": "float", 373 | "pe": "float", 374 | "earnings_announcement": "str", 375 | "shares_outstanding": "int", 376 | } 377 | ) 378 | ) 379 | 380 | data_df["timestamp"] = pd.to_datetime(data_df["timestamp"], unit="s") 381 | 382 | return data_df 383 | -------------------------------------------------------------------------------- /src/fmp_py/fmp_historical_data.py: -------------------------------------------------------------------------------- 1 | # src/fmp_py/fmp_historical_data.py 2 | # Define the FmpHistoricalData class that inherits from FmpBase. 3 | import pandas as pd 4 | from fmp_py.fmp_base import FmpBase 5 | 6 | # from typing import Dict, Any 7 | import os 8 | from dotenv import load_dotenv 9 | 10 | load_dotenv() 11 | 12 | 13 | class FmpHistoricalData(FmpBase): 14 | def __init__(self, api_key: str = os.getenv("FMP_API_KEY")) -> None: 15 | """ 16 | Initialize the FmpHistoricalData class. 17 | 18 | Args: 19 | api_key (str): The API key for Financial Modeling Prep. 20 | """ 21 | super().__init__(api_key) 22 | 23 | ############################ 24 | # Historical Daily Prices 25 | ############################ 26 | def daily_history(self, symbol: str, from_date: str, to_date: str) -> pd.DataFrame: 27 | """ 28 | Retrieves daily historical data for a given symbol within a specified date range. 29 | 30 | Args: 31 | symbol (str): The symbol of the stock or asset. 32 | from_date (str): The starting date of the historical data in the format 'YYYY-MM-DD'. 33 | to_date (str): The ending date of the historical data in the format 'YYYY-MM-DD'. 34 | 35 | Returns: 36 | pd.DataFrame: A DataFrame containing the daily historical data for the specified symbol. 37 | """ 38 | url = f"v3/historical-price-full/{symbol}" 39 | params = {"from": from_date, "to": to_date} 40 | response = self.get_request(url, params) 41 | 42 | data = response.get("historical", []) 43 | 44 | if not data: 45 | raise ValueError("No data found for the specified parameters.") 46 | 47 | data_df = self._prepare_data(pd.DataFrame(data)) 48 | return data_df.sort_values(by="date").set_index("date")[ 49 | ["open", "high", "low", "close", "volume", "vwap"] 50 | ] 51 | 52 | ############################ 53 | # Intraday Historical Prices 54 | ############################ 55 | def intraday_history( 56 | self, symbol: str, interval: str, from_date: str, to_date: str 57 | ) -> pd.DataFrame: 58 | """ 59 | Retrieves intraday historical data for a given symbol within a specified time interval. 60 | 61 | Args: 62 | symbol (str): The stock or asset symbol. 63 | interval (str): The time interval for the data. Must be one of: ['1min', '5min', '15min', '30min', '1hour', '4hour']. 64 | from_date (str): The starting date for the data in the format 'YYYY-MM-DD'. 65 | to_date (str): The ending date for the data in the format 'YYYY-MM-DD'. 66 | 67 | Returns: 68 | pd.DataFrame: A DataFrame containing the intraday historical data for the specified symbol and time interval. 69 | """ 70 | interval_options = ["1min", "5min", "15min", "30min", "1hour", "4hour", "1day"] 71 | if interval not in interval_options: 72 | raise ValueError(f"Interval must be one of: {interval_options}") 73 | 74 | url = f"v3/historical-chart/{interval}/{symbol}" 75 | params = {"from": from_date, "to": to_date} 76 | response = self.get_request(url, params) 77 | 78 | if not response: 79 | raise ValueError("No data found for the specified parameters.") 80 | 81 | data_df = self._prepare_data(pd.DataFrame(response)) 82 | return data_df.sort_values(by="date").set_index("date") 83 | 84 | ############################ 85 | # Prepare Data 86 | ############################ 87 | def _prepare_data(self, data_df: pd.DataFrame) -> pd.DataFrame: 88 | """ 89 | Prepare data by calculating VWAP and converting data types. 90 | 91 | Args: 92 | data_df (pd.DataFrame): Raw data. 93 | 94 | Returns: 95 | pd.DataFrame: Prepared data. 96 | """ 97 | # data_df["vwap"] = self._calc_vwap(data_df) 98 | data_df = data_df.astype( 99 | { 100 | "date": "datetime64[ns]", 101 | "open": "float", 102 | "high": "float", 103 | "low": "float", 104 | "close": "float", 105 | "volume": "int64", 106 | # "vwap": "float", 107 | } 108 | ) 109 | return self._round_prices(data_df) 110 | 111 | ############################ 112 | # Round Prices 113 | ############################ 114 | def _round_prices(self, data_df: pd.DataFrame) -> pd.DataFrame: 115 | """ 116 | Round prices to 2 decimal places. 117 | 118 | Args: 119 | data_df (pd.DataFrame): DataFrame with price data. 120 | 121 | Returns: 122 | pd.DataFrame: DataFrame with rounded price data. 123 | """ 124 | price_columns = ["open", "high", "low", "close"] 125 | data_df[price_columns] = data_df[price_columns].round(2) 126 | return data_df 127 | 128 | ############################ 129 | # VWAP Calculation 130 | ############################ 131 | def _calc_vwap(self, data_df: pd.DataFrame) -> pd.Series: 132 | """ 133 | Calculate the Volume Weighted Average Price (VWAP). 134 | 135 | Args: 136 | data_df (pd.DataFrame): DataFrame with price and volume data. 137 | 138 | Returns: 139 | pd.Series: VWAP values. 140 | """ 141 | vwap = ( 142 | ((data_df["high"] + data_df["low"] + data_df["close"]) / 3) 143 | * data_df["volume"] 144 | ).cumsum() / data_df["volume"].cumsum() 145 | return vwap.round(2) 146 | -------------------------------------------------------------------------------- /src/fmp_py/fmp_ipo_calendar.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from fmp_py.fmp_base import FmpBase 3 | import os 4 | import pendulum 5 | from dotenv import load_dotenv 6 | 7 | load_dotenv() 8 | 9 | 10 | """ 11 | This class provides methods for retrieving IPO calendar data from the Financial Modeling Prep API. 12 | Ref: https://site.financialmodelingprep.com/developer/docs#ipo-calendar 13 | 14 | def ipo_calendar_by_symbol(self, from_date: str, to_date: str) -> pd.DataFrame: 15 | Reference: https://site.financialmodelingprep.com/developer/docs#ipo-confirmed-ipo-calendar 16 | 17 | def ipo_prospectus(self, from_date: str, to_date: str) -> pd.DataFrame: 18 | Reference: https://site.financialmodelingprep.com/developer/docs#ipo-prospectus-ipo-calendar 19 | 20 | def ipo_confirmed(self, from_date: str, to_date: str) -> pd.DataFrame: 21 | Reference: https://site.financialmodelingprep.com/developer/docs#ipo-calender-by-ipo-calendar 22 | """ 23 | 24 | 25 | class FmpIpoCalendar(FmpBase): 26 | def __init__(self, api_key: str = os.getenv("FMP_API_KEY")): 27 | super().__init__(api_key) 28 | 29 | ############################# 30 | # IPO Calendar by Symbol 31 | ############################# 32 | def ipo_calendar_by_symbol(self, from_date: str, to_date: str) -> pd.DataFrame: 33 | """ 34 | Retrieves IPO calendar data for a specific symbol within a given date range. 35 | Args: 36 | from_date (str): The starting date of the date range in "YYYY-MM-DD" format. 37 | to_date (str): The ending date of the date range in "YYYY-MM-DD" format. 38 | Returns: 39 | pd.DataFrame: A DataFrame containing IPO calendar data for the specified symbol within the given date range. 40 | """ 41 | 42 | from_date = pendulum.parse(from_date).format("YYYY-MM-DD") 43 | to_date = pendulum.parse(to_date).format("YYYY-MM-DD") 44 | if from_date > to_date: 45 | raise ValueError("from_date must be less than or equal to to_date") 46 | 47 | url = "v3/ipo_calendar" 48 | params = {"from": from_date, "to": to_date} 49 | 50 | response = self.get_request(url, params) 51 | 52 | if not response: 53 | raise ValueError("Error fetching IPO calendar data") 54 | 55 | data_df = ( 56 | pd.DataFrame(response) 57 | .fillna(0) 58 | .rename( 59 | columns={ 60 | "date": "date", 61 | "company": "company", 62 | "symbol": "symbol", 63 | "exchange": "exchange", 64 | "actions": "actions", 65 | "shares": "shares", 66 | "priceRange": "price_range", 67 | "marketCap": "market_cap", 68 | } 69 | ) 70 | .astype( 71 | { 72 | "date": "datetime64[ns]", 73 | "company": "str", 74 | "symbol": "str", 75 | "exchange": "str", 76 | "actions": "str", 77 | "shares": "int", 78 | "price_range": "string", 79 | "market_cap": "int", 80 | } 81 | ) 82 | .sort_values(by="date", ascending=True) 83 | .reset_index(drop=True) 84 | ) 85 | 86 | return data_df 87 | 88 | ############################# 89 | # IPO Prspectus 90 | ############################# 91 | def ipo_prospectus(self, from_date: str, to_date: str) -> pd.DataFrame: 92 | """ 93 | Retrieves IPO prospectus data from the specified date range. 94 | Args: 95 | from_date (str): The starting date of the date range in "YYYY-MM-DD" format. 96 | to_date (str): The ending date of the date range in "YYYY-MM-DD" format. 97 | Returns: 98 | pd.DataFrame: A DataFrame containing IPO prospectus data with the following columns: 99 | - symbol (str): The symbol of the IPO. 100 | - cik (str): The CIK (Central Index Key) of the IPO. 101 | - form (str): The form type of the IPO. 102 | - filing_date (datetime64[ns]): The filing date of the IPO. 103 | - accepted_date (datetime64[ns]): The accepted date of the IPO. 104 | - ipo_date (datetime64[ns]): The IPO date. 105 | - price_public_per_share (float): The price per share for the public offering. 106 | - price_public_total (float): The total price for the public offering. 107 | - discounts_and_commissions_per_share (float): The discounts and commissions per share. 108 | - discounts_and_commissions_total (float): The total discounts and commissions. 109 | - proceeds_before_expenses_per_share (float): The proceeds per share before expenses. 110 | - proceeds_before_expenses_total (float): The total proceeds before expenses. 111 | - url (str): The URL of the IPO prospectus. 112 | Raises: 113 | ValueError: If from_date is greater than to_date or if there is an error fetching the IPO calendar data. 114 | """ 115 | 116 | from_date = pendulum.parse(from_date).format("YYYY-MM-DD") 117 | to_date = pendulum.parse(to_date).format("YYYY-MM-DD") 118 | if from_date > to_date: 119 | raise ValueError("from_date must be less than or equal to to_date") 120 | 121 | url = "v4/ipo-calendar-prospectus" 122 | params = {"from": from_date, "to": to_date} 123 | 124 | response = self.get_request(url, params) 125 | 126 | if not response: 127 | raise ValueError("Error fetching IPO calendar data") 128 | 129 | data_df = ( 130 | pd.DataFrame(response) 131 | .fillna("") 132 | .rename( 133 | columns={ 134 | "symbol": "symbol", 135 | "cik": "cik", 136 | "form": "form", 137 | "filingDate": "filing_date", 138 | "acceptedDate": "accepted_date", 139 | "ipoDate": "ipo_date", 140 | "pricePublicPerShare": "price_public_per_share", 141 | "pricePublicTotal": "price_public_total", 142 | "discountsAndCommissionsPerShare": "discounts_and_commissions_per_share", 143 | "discountsAndCommissionsTotal": "discounts_and_commissions_total", 144 | "proceedsBeforeExpensesPerShare": "proceeds_before_expenses_per_share", 145 | "proceedsBeforeExpensesTotal": "proceeds_before_expenses_total", 146 | "url": "url", 147 | } 148 | ) 149 | .astype( 150 | { 151 | "symbol": "str", 152 | "cik": "str", 153 | "form": "str", 154 | "filing_date": "datetime64[ns]", 155 | "accepted_date": "datetime64[ns]", 156 | "ipo_date": "datetime64[ns]", 157 | "price_public_per_share": "float", 158 | "price_public_total": "float", 159 | "discounts_and_commissions_per_share": "float", 160 | "discounts_and_commissions_total": "float", 161 | "proceeds_before_expenses_per_share": "float", 162 | "proceeds_before_expenses_total": "float", 163 | "url": "str", 164 | } 165 | ) 166 | .sort_values(by="filing_date", ascending=True) 167 | .reset_index(drop=True) 168 | ) 169 | 170 | return data_df 171 | 172 | ############################# 173 | # IPO Confirmed 174 | ############################# 175 | def ipo_confirmed(self, from_date: str, to_date: str) -> pd.DataFrame: 176 | """ 177 | Retrieves the IPO calendar data for confirmed IPOs within the specified date range. 178 | Args: 179 | from_date (str): The start date of the date range in the format "YYYY-MM-DD". 180 | to_date (str): The end date of the date range in the format "YYYY-MM-DD". 181 | Returns: 182 | pd.DataFrame: A DataFrame containing the IPO calendar data for confirmed IPOs, sorted by filing date. 183 | Raises: 184 | ValueError: If from_date is greater than to_date or if there is an error fetching the IPO calendar data. 185 | """ 186 | 187 | from_date = pendulum.parse(from_date).format("YYYY-MM-DD") 188 | to_date = pendulum.parse(to_date).format("YYYY-MM-DD") 189 | if from_date > to_date: 190 | raise ValueError("from_date must be less than or equal to to_date") 191 | 192 | url = "v4/ipo-calendar-confirmed" 193 | params = {"from": from_date, "to": to_date} 194 | 195 | response = self.get_request(url, params) 196 | 197 | if not response: 198 | raise ValueError("Error fetching IPO calendar data") 199 | 200 | data_df = ( 201 | pd.DataFrame(response) 202 | .fillna("") 203 | .rename( 204 | columns={ 205 | "symbol": "symbol", 206 | "cik": "cik", 207 | "form": "form", 208 | "filingDate": "filing_date", 209 | "acceptedDate": "accepted_date", 210 | "effectivenessDate": "effectiveness_date", 211 | "url": "url", 212 | } 213 | ) 214 | .astype( 215 | { 216 | "symbol": "string", 217 | "cik": "string", 218 | "form": "string", 219 | "filing_date": "datetime64[ns]", 220 | "accepted_date": "datetime64[ns]", 221 | "effectiveness_date": "datetime64[ns]", 222 | "url": "string", 223 | } 224 | ) 225 | .sort_values(by="filing_date", ascending=True) 226 | .reset_index(drop=True) 227 | ) 228 | 229 | return data_df 230 | -------------------------------------------------------------------------------- /src/fmp_py/fmp_mergers_and_aquisitions.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | from fmp_py.fmp_base import FmpBase 3 | import os 4 | from dotenv import load_dotenv 5 | 6 | load_dotenv() 7 | 8 | 9 | """ 10 | The FmpMergersAndAquisitions class provides methods for retrieving mergers and acquisitions data from the Financial Modeling Prep API. 11 | Refer to the official documentation (https://site.financialmodelingprep.com/developer/docs#mergers-&-acquisitions) for more information. 12 | 13 | def ma_rss_feed(self, page: int = 0) -> pd.DataFrame: 14 | Reference: https://site.financialmodelingprep.com/developer/docs#m&a-rss-feed-mergers-&-acquisitions 15 | 16 | def search_ma(self, query: str) -> pd.DataFrame: 17 | Reference: https://site.financialmodelingprep.com/developer/docs#search-m&a-mergers-&-acquisitions 18 | """ 19 | 20 | 21 | class FmpMergersAndAquisitions(FmpBase): 22 | def __init__(self, api_key: str = os.getenv("FMP_API_KEY")) -> None: 23 | super().__init__(api_key) 24 | 25 | ##################################### 26 | # Mergers and Acquisitions Search 27 | ##################################### 28 | def search_ma(self, name: str) -> pd.DataFrame: 29 | """ 30 | Retrieves mergers and acquisitions data based on a search name. 31 | 32 | Args: 33 | name (str): The search name of company. 34 | 35 | Returns: 36 | pd.DataFrame: A DataFrame containing the mergers and acquisitions data. 37 | """ 38 | url = "v4/mergers-acquisitions/search" 39 | params = {"name": name} 40 | response = self.get_request(url, params) 41 | 42 | if not response: 43 | raise ValueError("No data found for the specified parameters.") 44 | 45 | data_df = ( 46 | pd.DataFrame(response) 47 | .fillna("") 48 | .rename( 49 | columns={ 50 | "companyName": "company_name", 51 | "symbol": "symbol", 52 | "targetedCompanyName": "targeted_company_name", 53 | "targetedCik": "targeted_cik", 54 | "targetedSymbol": "targeted_symbol", 55 | "transactionDate": "transaction_date", 56 | "acceptanceTime": "acceptance_time", 57 | "url": "url", 58 | } 59 | ) 60 | .astype( 61 | { 62 | "company_name": "str", 63 | "symbol": "str", 64 | "targeted_company_name": "str", 65 | "targeted_cik": "str", 66 | "targeted_symbol": "str", 67 | "transaction_date": "datetime64[ns]", 68 | "acceptance_time": "datetime64[ns]", 69 | "url": "str", 70 | } 71 | ) 72 | ) 73 | return data_df 74 | 75 | ##################################### 76 | # Mergers and Acquisitions RSS Feed 77 | ##################################### 78 | def ma_rss_feed(self, page: int = 0) -> pd.DataFrame: 79 | """ 80 | Retrieves mergers and acquisitions data from the RSS feed. 81 | 82 | Args: 83 | page (int): The page number for the RSS feed. 84 | 85 | Returns: 86 | pd.DataFrame: A DataFrame containing the mergers and acquisitions data. 87 | """ 88 | url = "v4/mergers-acquisitions-rss-feed" 89 | params = {"page": page} 90 | response = self.get_request(url, params) 91 | 92 | if not response: 93 | raise ValueError("No data found for the specified parameters.") 94 | 95 | data_df = ( 96 | pd.DataFrame(response) 97 | .fillna("") 98 | .rename( 99 | columns={ 100 | "companyName": "company_name", 101 | "symbol": "symbol", 102 | "targetedCompanyName": "targeted_company_name", 103 | "targetedCik": "targeted_cik", 104 | "targetedSymbol": "targeted_symbol", 105 | "transactionDate": "transaction_date", 106 | "acceptanceTime": "acceptance_time", 107 | "url": "url", 108 | } 109 | ) 110 | .astype( 111 | { 112 | "company_name": "str", 113 | "symbol": "str", 114 | "targeted_company_name": "str", 115 | "targeted_cik": "str", 116 | "targeted_symbol": "str", 117 | "transaction_date": "datetime64[ns]", 118 | "acceptance_time": "datetime64[ns]", 119 | "url": "str", 120 | } 121 | ) 122 | ) 123 | return data_df 124 | -------------------------------------------------------------------------------- /src/fmp_py/fmp_price_targets.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pendulum 3 | from fmp_py.fmp_base import FmpBase 4 | from dotenv import load_dotenv 5 | import os 6 | import pandas as pd 7 | 8 | from fmp_py.models.price_targets import PriceTargetConsensus, PriceTargetSummary 9 | 10 | load_dotenv() 11 | 12 | """ 13 | The FmpPriceTargets class provides methods for retrieving price targets data from the Financial Modeling Prep API. 14 | Reference: https://site.financialmodelingprep.com/developer/docs#price-targets 15 | 16 | def price_target(self, symbol: str) -> pd.DataFrame: 17 | Reference: https://site.financialmodelingprep.com/developer/docs#price-target 18 | 19 | def price_target_summary(self, symbol: str) -> pd.DataFrame: 20 | Reference: https://site.financialmodelingprep.com/developer/docs#price-target-summary 21 | 22 | def price_target_consensus(self, symbol: str) -> pd.DataFrame: 23 | Reference: https://site.financialmodelingprep.com/developer/docs#price-target-consensus 24 | """ 25 | 26 | 27 | class FmpPriceTargets(FmpBase): 28 | def __init__(self, api_key: str = os.getenv("FMP_API_KEY")) -> None: 29 | super().__init__(api_key) 30 | 31 | ############################ 32 | # Price Target Consensus 33 | ############################ 34 | def price_target_consensus(self, symbol: str) -> PriceTargetConsensus: 35 | """ 36 | Retrieves the price target consensus for a given symbol. 37 | 38 | Args: 39 | symbol (str): The symbol of the stock. 40 | 41 | Returns: 42 | PriceTargetConsensus: An object containing the price target consensus information. 43 | 44 | Raises: 45 | ValueError: If no data is found for the specified parameters. 46 | """ 47 | url = "v4/price-target-consensus" 48 | params = {"symbol": symbol, "apikey": self.api_key} 49 | 50 | try: 51 | response: dict = self.get_request(url, params)[0] 52 | except IndexError: 53 | raise ValueError("No data found for the specified parameters.") 54 | 55 | return PriceTargetConsensus( 56 | symbol=self.clean_value(response.get("symbol", ""), str), 57 | target_high=self.clean_value(response.get("targetHigh", 0.0), float), 58 | target_low=self.clean_value(response.get("targetLow", 0.0), float), 59 | target_consensus=self.clean_value( 60 | response.get("targetConsensus", 0.0), float 61 | ), 62 | target_median=self.clean_value(response.get("targetMedian", 0.0), float), 63 | ) 64 | 65 | ############################ 66 | # Price Target Summary 67 | ############################ 68 | def price_target_summary(self, symbol: str) -> PriceTargetSummary: 69 | url = "v4/price-target-summary" 70 | params = {"symbol": symbol, "apikey": self.api_key} 71 | 72 | try: 73 | response: dict = self.get_request(url, params)[0] 74 | except IndexError: 75 | raise ValueError("No data found for the specified parameters.") 76 | 77 | return PriceTargetSummary( 78 | symbol=self.clean_value(response.get("symbol", ""), str), 79 | last_month=self.clean_value(response.get("lastMonth", 0), int), 80 | last_month_avg_price_target=self.clean_value( 81 | response.get("lastMonthAvgPriceTarget", 0.0), float 82 | ), 83 | last_quarter=self.clean_value(response.get("lastQuarter", 0), int), 84 | last_quarter_avg_price_target=self.clean_value( 85 | response.get("lastQuarterAvgPriceTarget", 0.0), float 86 | ), 87 | last_year=self.clean_value(response.get("lastYear", 0), int), 88 | last_year_avg_price_target=self.clean_value( 89 | response.get("lastYearAvgPriceTarget", 0.0), float 90 | ), 91 | all_time=self.clean_value(response.get("allTime", 0), int), 92 | all_time_avg_price_target=self.clean_value( 93 | response.get("allTimeAvgPriceTarget", 0.0), float 94 | ), 95 | publishers=json.loads(response["publishers"]), 96 | ) 97 | 98 | ############################ 99 | # Price Target 100 | ############################ 101 | def price_target(self, symbol: str) -> pd.DataFrame: 102 | """ 103 | Retrieves the price target data for a given symbol. 104 | 105 | Args: 106 | symbol (str): The symbol for which to retrieve the price target data. 107 | 108 | Returns: 109 | pd.DataFrame: A DataFrame containing the price target data, sorted by published date. 110 | 111 | Raises: 112 | ValueError: If no data is found for the given symbol. 113 | """ 114 | url = "v4/price-target" 115 | params = {"symbol": symbol, "apikey": self.api_key} 116 | response = self.get_request(url, params) 117 | 118 | if not response: 119 | raise ValueError("No data found for the given symbol.") 120 | 121 | data_df = ( 122 | pd.DataFrame(response) 123 | .fillna(0) 124 | .rename( 125 | columns={ 126 | "symbol": "symbol", 127 | "publishedDate": "published_date", 128 | "newsURL": "news_url", 129 | "newsTitle": "news_title", 130 | "analystName": "analyst_name", 131 | "priceTarget": "price_target", 132 | "adjPriceTarget": "adj_price_target", 133 | "priceWhenPosted": "price_when_posted", 134 | "newsPublisher": "news_publisher", 135 | "newsBaseURL": "news_base_url", 136 | "analystCompany": "analyst_company", 137 | } 138 | ) 139 | ) 140 | 141 | data_df["published_date"] = data_df["published_date"].apply( 142 | lambda x: pendulum.parse(x).to_datetime_string() 143 | ) 144 | 145 | return ( 146 | data_df.astype( 147 | { 148 | "symbol": "str", 149 | "published_date": "datetime64[ns]", 150 | "news_url": "str", 151 | "news_title": "str", 152 | "analyst_name": "str", 153 | "price_target": "float", 154 | "adj_price_target": "float", 155 | "price_when_posted": "float", 156 | "news_publisher": "str", 157 | "news_base_url": "str", 158 | "analyst_company": "str", 159 | } 160 | ) 161 | .sort_values(by="published_date", ascending=True) 162 | .reset_index(drop=True) 163 | ) 164 | -------------------------------------------------------------------------------- /src/fmp_py/fmp_splits.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pendulum 3 | from fmp_py.fmp_base import FmpBase 4 | import os 5 | from dotenv import load_dotenv 6 | 7 | load_dotenv() 8 | 9 | """ 10 | Retrieves Stock Spilts Data from Financial Modeling Prep API 11 | References: 12 | - https://site.financialmodelingprep.com/developer/docs#splits 13 | 14 | def stock_splits_calendar(self, from_date: str, to_date: str) -> pd.DataFrame: 15 | Reference: https://site.financialmodelingprep.com/developer/docs#splits-calendar-splits 16 | 17 | def stock_splits_historical(self, symbol: str) -> pd.DataFrame: 18 | Reference: https://site.financialmodelingprep.com/developer/docs#splits-historical-splits 19 | """ 20 | 21 | 22 | class FmpSplits(FmpBase): 23 | def __init__(self, api_key: str = os.getenv("FMP_API_KEY")) -> None: 24 | super().__init__(api_key) 25 | 26 | ########################################## 27 | # Stock Splits Histrorical 28 | ########################################## 29 | def stock_splits_historical(self, symbol: str) -> pd.DataFrame: 30 | """ 31 | Retrieves historical stock splits data for a given symbol. 32 | Args: 33 | symbol (str): The stock symbol. 34 | Returns: 35 | pd.DataFrame: A DataFrame containing the historical stock splits data. 36 | """ 37 | 38 | url = f"v3/historical-price-full/stock_split/{symbol}" 39 | response = self.get_request(url)["historical"] 40 | 41 | if not response: 42 | raise ValueError( 43 | f"Error fetching stock splits historical data for {symbol}" 44 | ) 45 | 46 | data_df = ( 47 | pd.DataFrame(response) 48 | .fillna(0) 49 | .rename( 50 | columns={ 51 | "date": "date", 52 | "label": "label", 53 | "numerator": "numerator", 54 | "denominator": "denominator", 55 | } 56 | ) 57 | .astype( 58 | { 59 | "date": "datetime64[ns]", 60 | "label": "str", 61 | "numerator": "int", 62 | "denominator": "int", 63 | } 64 | ) 65 | .sort_values(by="date", ascending=True) 66 | .reset_index(drop=True) 67 | ) 68 | data_df["symbol"] = symbol 69 | 70 | return data_df 71 | 72 | ######################################## 73 | # Stock Splits Calendar 74 | ######################################## 75 | def stock_splits_calendar(self, from_date: str, to_date: str) -> pd.DataFrame: 76 | """ 77 | Retrieves the stock splits calendar for a given date range. 78 | Args: 79 | from_date (str): The start date of the date range in "YYYY-MM-DD" format. 80 | to_date (str): The end date of the date range in "YYYY-MM-DD" format. 81 | Returns: 82 | pd.DataFrame: A DataFrame containing the stock splits calendar data with the following columns: 83 | - date: The date of the stock split. 84 | - label: The label of the stock split. 85 | - symbol: The symbol of the stock. 86 | - numerator: The numerator of the stock split ratio. 87 | - denominator: The denominator of the stock split ratio. 88 | Raises: 89 | ValueError: If from_date is greater than to_date or if no data is found for the given date range. 90 | """ 91 | 92 | from_date = pendulum.parse(from_date).format("YYYY-MM-DD") 93 | to_date = pendulum.parse(to_date).format("YYYY-MM-DD") 94 | if from_date > to_date: 95 | raise ValueError("from_date must be less than or equal to to_date") 96 | 97 | url = "v3/stock_split_calendar" 98 | params = {"from": from_date, "to": to_date} 99 | response = self.get_request(url=url, params=params) 100 | 101 | if not response: 102 | raise ValueError("No data found for the given date range") 103 | 104 | data_df = ( 105 | pd.DataFrame(response) 106 | .fillna(0) 107 | .rename( 108 | columns={ 109 | "date": "date", 110 | "label": "label", 111 | "symbol": "symbol", 112 | "numerator": "numerator", 113 | "denominator": "denominator", 114 | } 115 | ) 116 | .astype( 117 | { 118 | "date": "datetime64[ns]", 119 | "label": "str", 120 | "symbol": "str", 121 | "numerator": "int", 122 | "denominator": "int", 123 | } 124 | ) 125 | .sort_values(by="date", ascending=True) 126 | .reset_index(drop=True) 127 | ) 128 | 129 | return data_df 130 | -------------------------------------------------------------------------------- /src/fmp_py/fmp_upgrades_downgrades.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pendulum 3 | from fmp_py.fmp_base import FmpBase 4 | from fmp_py.models.upgrades_downgrades import UpgradesDowngrades 5 | 6 | import os 7 | from dotenv import load_dotenv 8 | 9 | load_dotenv() 10 | 11 | 12 | """ 13 | The FmpUpgradesDowngrades class provides methods for retrieving upgrade/downgrade data from the Financial Modeling Prep API. 14 | Reference: https://site.financialmodelingprep.com/developer/docs#upgrades-downgrades 15 | 16 | def upgrades_downgrades(self, symbol: str) -> pd.DataFrame: 17 | Reference: https://site.financialmodelingprep.com/developer/docs#upgrades-downgrades-search 18 | 19 | def upgrades_downgrades_rss_feed(self, page: int = 0) -> pd.DataFrame: 20 | Reference: https://site.financialmodelingprep.com/developer/docs#up-down-grades-rss-feed 21 | 22 | def upgrades_downgrades_by_company(self, company: str) -> pd.DataFrame: 23 | Reference: https://site.financialmodelingprep.com/developer/docs#up-down-grades-by-company 24 | 25 | def upgrades_downgrades_consensus(self, symbol: str) -> UpgradesDowngrades: 26 | Reference: https://site.financialmodelingprep.com/developer/docs#up-down-grades-consensus 27 | """ 28 | 29 | 30 | class FMPUpgradesDowngrades(FmpBase): 31 | def __init__(self, api_key: str = os.getenv("FMP_API_KEY")) -> None: 32 | super().__init__(api_key) 33 | 34 | ############################ 35 | # Upgrades Downgrades Consensus 36 | ############################ 37 | def upgrades_downgrades_consensus(self, symbol: str) -> UpgradesDowngrades: 38 | """ 39 | Retrieves the upgrades and downgrades consensus data for a given symbol. 40 | 41 | Args: 42 | symbol (str): The symbol of the stock. 43 | 44 | Returns: 45 | UpgradesDowngrades: An instance of the UpgradesDowngrades class containing the consensus data. 46 | 47 | Raises: 48 | ValueError: If no data is found for the specified parameters. 49 | """ 50 | url = "v4/upgrades-downgrades-consensus" 51 | params = {"symbol": symbol, "apikey": self.api_key} 52 | 53 | try: 54 | response = self.get_request(url, params)[0] 55 | except IndexError: 56 | raise ValueError("No data found for the specified parameters.") 57 | 58 | data_dict = { 59 | "symbol": self.clean_value(response.get("symbol", ""), str), 60 | "strong_buy": self.clean_value(response.get("strongBuy", 0), int), 61 | "strong_sell": self.clean_value(response.get("strongSell", 0), int), 62 | "buy": self.clean_value(response.get("buy", 0), int), 63 | "sell": self.clean_value(response.get("sell", 0), int), 64 | "hold": self.clean_value(response.get("hold", 0), int), 65 | "consensus": self.clean_value(response.get("consensus", ""), str), 66 | } 67 | 68 | return UpgradesDowngrades(**data_dict) 69 | 70 | ############################ 71 | # Upgrades Downgrades by Company 72 | ############################ 73 | def upgrades_downgrades_by_company(self, company: str) -> pd.DataFrame: 74 | """ 75 | Retrieves upgrades and downgrades grading data for a specific company. 76 | 77 | Args: 78 | company (str): The name of the company. 79 | 80 | Returns: 81 | pd.DataFrame: A DataFrame containing the upgrades and downgrades grading data for the specified company. 82 | 83 | Raises: 84 | ValueError: If no data is found for the specified company. 85 | """ 86 | url = "v4/upgrades-downgrades-grading-company" 87 | params = {"company": company, "apikey": self.api_key} 88 | 89 | try: 90 | return self._process_data(url=url, params=params) 91 | except ValueError: 92 | raise ValueError(f"No data found for {company}.") 93 | 94 | ############################ 95 | # Upgrades Downgrades RSS Feed 96 | ############################ 97 | def upgrades_downgrades_rss_feed(self, page: int = 0) -> pd.DataFrame: 98 | """ 99 | Retrieves the upgrades and downgrades RSS feed data from the Financial Modeling Prep API. 100 | 101 | Args: 102 | page (int): The page number of the RSS feed to retrieve. Default is 0. 103 | 104 | Returns: 105 | pd.DataFrame: A pandas DataFrame containing the upgrades and downgrades data. 106 | 107 | Raises: 108 | ValueError: If no data is found for the specified parameters. 109 | """ 110 | url = "v4/upgrades-downgrades-rss-feed" 111 | params = {"page": page, "apikey": self.api_key} 112 | 113 | try: 114 | return self._process_data(url=url, params=params) 115 | except ValueError: 116 | raise ValueError("No data found for the specified parameters.") 117 | 118 | ############################ 119 | # Upgrades Downgrades 120 | ############################ 121 | def upgrades_downgrades(self, symbol: str) -> pd.DataFrame: 122 | """ 123 | Retrieves upgrades and downgrades data for a given symbol. 124 | 125 | Args: 126 | symbol (str): The symbol for which to retrieve upgrades and downgrades data. 127 | 128 | Returns: 129 | pd.DataFrame: A DataFrame containing the upgrades and downgrades data, sorted by published date. 130 | 131 | Raises: 132 | ValueError: If no data is found for the given symbol. 133 | """ 134 | url = "v4/upgrades-downgrades" 135 | params = {"symbol": symbol, "apikey": self.api_key} 136 | 137 | try: 138 | return self._process_data(url=url, params=params) 139 | except ValueError: 140 | raise ValueError("No data found for the given symbol.") 141 | 142 | ############################ 143 | # Private Methods 144 | ############################ 145 | def _process_data(self, url: str, params: dict) -> pd.DataFrame: 146 | """ 147 | Process the data obtained from the API response. 148 | 149 | Args: 150 | url (str): The URL for the API request. 151 | params (dict): The parameters for the API request. 152 | 153 | Returns: 154 | pd.DataFrame: A DataFrame containing the processed data. 155 | 156 | Raises: 157 | ValueError: If no data is found for the given symbol. 158 | """ 159 | 160 | response = self.get_request(url=url, params=params) 161 | 162 | if not response: 163 | raise ValueError("No data found for the given symbol.") 164 | 165 | data_df = ( 166 | pd.DataFrame(response) 167 | .fillna(0) 168 | .rename( 169 | columns={ 170 | "symbol": "symbol", 171 | "publishedDate": "published_date", 172 | "newsURL": "news_url", 173 | "newsTitle": "news_title", 174 | "newsBaseURL": "news_base_url", 175 | "newsPublisher": "news_publisher", 176 | "newGrade": "new_grade", 177 | "previousGrade": "previous_grade", 178 | "gradingCompany": "grading_company", 179 | "action": "action", 180 | "priceWhenPosted": "price_when_posted", 181 | } 182 | ) 183 | ) 184 | 185 | data_df["published_date"] = data_df["published_date"].apply( 186 | lambda x: pendulum.parse(x).to_datetime_string() 187 | ) 188 | 189 | return ( 190 | data_df.sort_values("published_date", ascending=True) 191 | .reset_index(drop=True) 192 | .astype( 193 | { 194 | "symbol": "str", 195 | "published_date": "datetime64[ns]", 196 | "news_url": "str", 197 | "news_title": "str", 198 | "news_base_url": "str", 199 | "news_publisher": "str", 200 | "new_grade": "str", 201 | "previous_grade": "str", 202 | "grading_company": "str", 203 | "action": "str", 204 | "price_when_posted": "float", 205 | } 206 | ) 207 | ) 208 | -------------------------------------------------------------------------------- /src/fmp_py/models/company_information.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class StockPeers: 6 | symbol: str 7 | peers_list: list[str] 8 | 9 | 10 | @dataclass 11 | class CompanyCoreInfo: 12 | cik: str 13 | symbol: str 14 | exchange: str 15 | sic_code: str 16 | sic_group: str 17 | sic_description: str 18 | state_location: str 19 | state_of_incorporation: str 20 | fiscal_year_end: str 21 | business_address: str 22 | mailing_address: str 23 | tax_idenfication_number: str 24 | registrant_name: str 25 | 26 | 27 | @dataclass 28 | class CompanyMarketCap: 29 | symbol: str 30 | market_cap: int 31 | date: str 32 | 33 | 34 | @dataclass 35 | class ExecutiveCompensation: 36 | symbol: str 37 | cik: str 38 | company_name: str 39 | industry: str 40 | accepted_date: str 41 | filing_date: str 42 | name_and_position: str 43 | year: str 44 | salary: float 45 | bonus: float 46 | stock_award: float 47 | incentive_plan_compensation: float 48 | all_other_compensation: float 49 | total: float 50 | url: str 51 | 52 | 53 | @dataclass 54 | class CompanyProfile: 55 | symbol: str 56 | price: float 57 | beta: float 58 | vol_avg: int 59 | mkt_cap: int 60 | last_div: int 61 | range: str 62 | changes: float 63 | company_name: str 64 | currency: str 65 | cik: str 66 | isin: str 67 | cusip: str 68 | exchange: str 69 | exchange_short_name: str 70 | industry: str 71 | website: str 72 | description: str 73 | ceo: str 74 | sector: str 75 | country: str 76 | full_time_employees: int 77 | phone: str 78 | address: str 79 | city: str 80 | state: str 81 | zip: str 82 | dcf_diff: float 83 | dcf: float 84 | image: str 85 | ipo_date: str 86 | default_image: bool 87 | is_etf: bool 88 | is_actively_trading: bool 89 | is_adr: bool 90 | is_fund: bool 91 | -------------------------------------------------------------------------------- /src/fmp_py/models/price_targets.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List 3 | 4 | 5 | @dataclass 6 | class PriceTargetConsensus: 7 | symbol: str 8 | target_high: float 9 | target_low: float 10 | target_consensus: float 11 | target_median: float 12 | 13 | 14 | @dataclass 15 | class PriceTargetSummary: 16 | symbol: str 17 | last_month: int 18 | last_month_avg_price_target: float 19 | last_quarter: int 20 | last_quarter_avg_price_target: float 21 | last_year: int 22 | last_year_avg_price_target: float 23 | all_time: int 24 | all_time_avg_price_target: float 25 | publishers: List[str] 26 | -------------------------------------------------------------------------------- /src/fmp_py/models/quote.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class FxPrice: 6 | ticker: str 7 | bid: float 8 | ask: float 9 | open: float 10 | low: float 11 | high: float 12 | changes: float 13 | date: str 14 | 15 | 16 | @dataclass 17 | class RealtimeFullPrice: 18 | symbol: str 19 | volume: int 20 | ask_price: float 21 | ask_size: int 22 | bid_price: float 23 | bid_size: int 24 | last_sale_price: float 25 | last_sale_size: int 26 | last_sale_time: int 27 | fmp_last: float 28 | last_updated: int 29 | 30 | 31 | @dataclass 32 | class CryptoQuote: 33 | symbol: str 34 | price: float 35 | size: float 36 | timestamp: str 37 | 38 | 39 | @dataclass 40 | class ForexQuote: 41 | symbol: str 42 | ask: float 43 | bid: int 44 | timestamp: str 45 | 46 | 47 | @dataclass 48 | class AftermarketQuote: 49 | symbol: str 50 | ask: float 51 | bid: float 52 | asize: float 53 | bsize: int 54 | timestamp: str 55 | 56 | 57 | @dataclass 58 | class AftermarketTrade: 59 | symbol: str 60 | price: float 61 | size: int 62 | timestamp: str 63 | 64 | 65 | @dataclass 66 | class PriceChange: 67 | symbol: str 68 | day_1: float 69 | day_5: float 70 | month_1: float 71 | month_3: float 72 | month_6: float 73 | ytd: float 74 | year_1: float 75 | year_3: float 76 | year_5: float 77 | year_10: float 78 | max: float 79 | 80 | 81 | @dataclass 82 | class OtcQuote: 83 | prev_close: float 84 | high: float 85 | low: float 86 | open: float 87 | volume: int 88 | last_sale_price: float 89 | fmp_last: float 90 | last_updated: str 91 | symbol: str 92 | 93 | 94 | @dataclass 95 | class SimpleQuote: 96 | symbol: str 97 | price: float 98 | volume: int 99 | 100 | 101 | @dataclass 102 | class Quote: 103 | symbol: str 104 | name: str 105 | price: float 106 | change_percentage: float 107 | change: float 108 | day_low: float 109 | day_high: float 110 | year_low: float 111 | year_high: float 112 | market_cap: int 113 | price_avg_50: float 114 | price_avg_200: float 115 | volume: int 116 | avg_volume: int 117 | exchange: str 118 | open: float 119 | previous_close: float 120 | eps: float 121 | pe: float 122 | earnings_date: str 123 | shares_outstanding: int 124 | timestamp: str 125 | -------------------------------------------------------------------------------- /src/fmp_py/models/statement_analysis.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class FinancialScore: 6 | symbol: str 7 | altman_z_score: float 8 | piotroski_score: int 9 | working_capital: int 10 | total_assets: int 11 | retained_earnings: int 12 | ebit: int 13 | market_cap: int 14 | total_liabilities: int 15 | revenue: int 16 | 17 | 18 | @dataclass 19 | class Ratios: 20 | dividend_yield_ttm: float 21 | dividend_yield_percentage_ttm: float 22 | pe_ratio_ttm: float 23 | peg_ratio_ttm: float 24 | payout_ratio_ttm: float 25 | current_ratio_ttm: float 26 | quick_ratio_ttm: float 27 | cash_ratio_ttm: float 28 | days_of_sales_outstanding_ttm: float 29 | days_of_inventory_outstanding_ttm: float 30 | operating_cycle_ttm: float 31 | days_of_payables_outstanding_ttm: float 32 | cash_conversion_cycle_ttm: float 33 | gross_profit_margin_ttm: float 34 | operating_profit_margin_ttm: float 35 | pretax_profit_margin_ttm: float 36 | net_profit_margin_ttm: float 37 | effective_tax_rate_ttm: float 38 | return_on_assets_ttm: float 39 | return_on_equity_ttm: float 40 | return_on_capital_employed_ttm: float 41 | net_income_per_ebt_ttm: float 42 | ebt_per_ebit_ttm: float 43 | ebit_per_revenue_ttm: float 44 | debt_ratio_ttm: float 45 | debt_equity_ratio_ttm: float 46 | longterm_debt_to_capitalization_ttm: float 47 | total_debt_to_capitalization_ttm: float 48 | interest_coverage_ttm: float 49 | cash_flow_to_debt_ratio_ttm: float 50 | company_equity_multiplier_ttm: float 51 | receivables_turnover_ttm: float 52 | payables_turnover_ttm: float 53 | inventory_turnover_ttm: float 54 | fixed_asset_turnover_ttm: float 55 | asset_turnover_ttm: float 56 | operating_cash_flow_per_share_ttm: float 57 | cash_per_share_ttm: float 58 | operating_cash_flow_sales_ratio_ttm: float 59 | free_cash_flow_operating_cash_flow_ratio_ttm: float 60 | free_cash_flow_per_share_ttm: float 61 | cash_flow_coverage_ratios_ttm: float 62 | short_term_coverage_ratios_ttm: float 63 | capital_expenditure_coverage_ratio_ttm: float 64 | dividend_paid_and_capex_coverage_ratio_ttm: float 65 | price_book_value_ratio_ttm: float 66 | price_to_book_ratio_ttm: float 67 | price_to_sales_ratio_ttm: float 68 | price_earnings_ratio_ttm: float 69 | price_to_free_cash_flows_ratio_ttm: float 70 | price_to_operating_cash_flows_ratio_ttm: float 71 | price_cash_flow_ratio_ttm: float 72 | price_earnings_to_growth_ratio_ttm: float 73 | price_sales_ratio_ttm: float 74 | enterprise_value_multiple_ttm: float 75 | price_fair_value_ttm: float 76 | dividend_per_share_ttm: float 77 | 78 | 79 | @dataclass 80 | class KeyMetrics: 81 | revenue_per_share_ttm: float 82 | net_income_per_share_ttm: float 83 | operating_cash_flow_per_share_ttm: float 84 | free_cash_flow_per_share_ttm: float 85 | cash_per_share_ttm: float 86 | book_value_per_share_ttm: float 87 | tangible_book_value_per_share_ttm: float 88 | shareholders_equity_per_share_ttm: float 89 | interest_debt_per_share_ttm: float 90 | market_cap_ttm: int 91 | enterprise_value_ttm: float 92 | pe_ratio_ttm: float 93 | price_to_sales_ratio_ttm: float 94 | pocf_ratio_ttm: float 95 | pfcf_ratio_ttm: float 96 | pb_ratio_ttm: float 97 | ptb_ratio_ttm: float 98 | ev_to_sales_ttm: float 99 | enterprise_value_over_ebitda_ttm: float 100 | debt_to_market_cap_ttm: float 101 | ev_to_operating_cash_flow_ttm: float 102 | dividend_per_share_ttm: float 103 | ev_to_free_cash_flow_ttm: float 104 | earnings_yield_ttm: float 105 | free_cash_flow_yield_ttm: float 106 | debt_to_equity_ttm: float 107 | debt_to_assets_ttm: float 108 | net_debt_to_ebitda_ttm: float 109 | graham_net_net_ttm: float 110 | return_on_tangible_assets_ttm: float 111 | current_ratio_ttm: float 112 | interest_coverage_ttm: float 113 | income_quality_ttm: float 114 | dividend_yield_ttm: float 115 | dividend_yield_percentage_ttm: float 116 | payout_ratio_ttm: float 117 | sales_general_and_administrative_to_revenue_ttm: float 118 | research_and_developement_to_revenue_ttm: float 119 | intangibles_to_total_assets_ttm: float 120 | capex_to_operating_cash_flow_ttm: float 121 | capex_to_revenue_ttm: float 122 | capex_to_depreciation_ttm: float 123 | stock_based_compensation_to_revenue_ttm: float 124 | graham_number_ttm: float 125 | roic_ttm: float 126 | working_capital_ttm: float 127 | tangible_asset_value_ttm: float 128 | net_current_asset_value_ttm: float 129 | invested_capital_ttm: float 130 | average_receivables_ttm: int 131 | average_payables_ttm: int 132 | average_inventory_ttm: int 133 | days_sales_outstanding_ttm: float 134 | days_payables_outstanding_ttm: float 135 | days_of_inventory_on_hand_ttm: float 136 | receivables_turnover_ttm: float 137 | payables_turnover_ttm: float 138 | inventory_turnover_ttm: float 139 | capex_per_share_ttm: float 140 | roe_ttm: float 141 | -------------------------------------------------------------------------------- /src/fmp_py/models/upgrades_downgrades.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class UpgradesDowngrades: 6 | symbol: str 7 | strong_buy: int 8 | buy: int 9 | hold: int 10 | sell: int 11 | strong_sell: int 12 | consensus: str 13 | -------------------------------------------------------------------------------- /src/fmp_py/models/valuation.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | 4 | @dataclass 5 | class DiscountedCashFlow: 6 | symbol: str 7 | date: str 8 | dcf: float 9 | stock_price: float 10 | 11 | 12 | @dataclass 13 | class CompanyRating: 14 | symbol: str 15 | date: str 16 | rating: str 17 | rating_score: int 18 | rating_recommendation: str 19 | rating_details_dcf_score: int 20 | rating_details_dcf_recommendation: str 21 | rating_details_roe_score: int 22 | rating_details_roe_recommendation: str 23 | rating_details_roa_score: int 24 | rating_details_roa_recommendation: str 25 | rating_details_de_score: int 26 | rating_details_de_recommendation: str 27 | rating_details_pe_score: int 28 | rating_details_pe_recommendation: str 29 | rating_details_pb_score: int 30 | rating_details_pb_recommendation: str 31 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TexasCoding/fmp-py/3770dcc3254ce59c2ba7fc5578aacafea54a19df/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_fmp_chart_data.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | from fmp_py.fmp_chart_data import FmpChartData 4 | 5 | 6 | @pytest.fixture 7 | def fmp(): 8 | return FmpChartData(symbol="AAPL", from_date="2021-01-01", to_date="2021-01-10") 9 | 10 | 11 | def test_fmp_chart_data_init(fmp): 12 | assert isinstance(fmp, FmpChartData) 13 | 14 | 15 | def test_fmp_chart_data_return_chart(fmp): 16 | fmp_chart = fmp.return_chart() 17 | assert isinstance(fmp_chart, pd.DataFrame) 18 | assert "close" in fmp_chart.columns 19 | assert "volume" in fmp_chart.columns 20 | assert "open" in fmp_chart.columns 21 | assert "high" in fmp_chart.columns 22 | assert "low" in fmp_chart.columns 23 | 24 | 25 | def test_fmp_chart_data_nvi(fmp): 26 | fmp.nvi() 27 | fmp_chart = fmp.return_chart() 28 | assert isinstance(fmp_chart, pd.DataFrame) 29 | assert "nvi" in fmp_chart.columns 30 | 31 | 32 | def test_fmp_chart_data_sma(fmp): 33 | fmp.sma(14) 34 | fmp_chart = fmp.return_chart() 35 | assert isinstance(fmp_chart, pd.DataFrame) 36 | assert "sma14" in fmp_chart.columns 37 | 38 | 39 | def test_fmp_chart_data_ema(fmp): 40 | fmp.ema(14) 41 | fmp_chart = fmp.return_chart() 42 | assert isinstance(fmp_chart, pd.DataFrame) 43 | assert "ema14" in fmp_chart.columns 44 | 45 | 46 | def test_fmp_chart_data_rsi(fmp): 47 | fmp.rsi(14) 48 | fmp_chart = fmp.return_chart() 49 | assert isinstance(fmp_chart, pd.DataFrame) 50 | assert "rsi14" in fmp_chart.columns 51 | 52 | 53 | def test_fmp_chart_data_vwap(fmp): 54 | fmp.vwap() 55 | fmp_chart = fmp.return_chart() 56 | assert isinstance(fmp_chart, pd.DataFrame) 57 | assert "vwap" in fmp_chart.columns 58 | 59 | 60 | def test_fmp_chart_data_bb(fmp): 61 | fmp.bb(20, 2) 62 | fmp_chart = fmp.return_chart() 63 | assert isinstance(fmp_chart, pd.DataFrame) 64 | assert "bb_hband" in fmp_chart.columns 65 | assert "bb_lband" in fmp_chart.columns 66 | assert "bb_mband" in fmp_chart.columns 67 | assert "bb_wband" in fmp_chart.columns 68 | assert "bb_pband" in fmp_chart.columns 69 | assert "bb_hband_ind" in fmp_chart.columns 70 | assert "bb_lband_ind" in fmp_chart.columns 71 | 72 | 73 | def test_fmp_chart_data_mfi(fmp): 74 | fmp.mfi(14) 75 | fmp_chart = fmp.return_chart() 76 | assert isinstance(fmp_chart, pd.DataFrame) 77 | assert "mfi14" in fmp_chart.columns 78 | 79 | 80 | def test_fmp_chart_data_ao(fmp): 81 | fmp.ao() 82 | fmp_chart = fmp.return_chart() 83 | assert isinstance(fmp_chart, pd.DataFrame) 84 | assert "ao" in fmp_chart.columns 85 | 86 | 87 | def test_fmp_chart_data_wr(fmp): 88 | fmp.wr(14) 89 | fmp_chart = fmp.return_chart() 90 | assert isinstance(fmp_chart, pd.DataFrame) 91 | assert "wr14" in fmp_chart.columns 92 | 93 | 94 | def test_fmp_chart_data_uo(fmp): 95 | fmp.uo() 96 | fmp_chart = fmp.return_chart() 97 | assert isinstance(fmp_chart, pd.DataFrame) 98 | assert "uo" in fmp_chart.columns 99 | 100 | 101 | def test_fmp_chart_data_tsi(fmp): 102 | fmp.tsi(25, 13) 103 | fmp_chart = fmp.return_chart() 104 | assert isinstance(fmp_chart, pd.DataFrame) 105 | assert "tsi" in fmp_chart.columns 106 | 107 | 108 | def test_fmp_chart_data_stoch(fmp): 109 | fmp.stoch(14) 110 | fmp_chart = fmp.return_chart() 111 | assert isinstance(fmp_chart, pd.DataFrame) 112 | assert "stoch14" in fmp_chart.columns 113 | assert "stoch14_sig" in fmp_chart.columns 114 | 115 | 116 | def test_fmp_chart_data_srsi(fmp): 117 | fmp.srsi(14) 118 | fmp_chart = fmp.return_chart() 119 | assert isinstance(fmp_chart, pd.DataFrame) 120 | assert "srsi14" in fmp_chart.columns 121 | assert "srsi14_d" in fmp_chart.columns 122 | assert "srsi14_k" in fmp_chart.columns 123 | 124 | 125 | def test_fmp_chart_data_roc(fmp): 126 | fmp.roc(12) 127 | fmp_chart = fmp.return_chart() 128 | assert isinstance(fmp_chart, pd.DataFrame) 129 | assert "roc12" in fmp_chart.columns 130 | 131 | 132 | def test_fmp_chart_data_kama(fmp): 133 | fmp.kama(10) 134 | fmp_chart = fmp.return_chart() 135 | assert isinstance(fmp_chart, pd.DataFrame) 136 | assert "kama10" in fmp_chart.columns 137 | -------------------------------------------------------------------------------- /tests/test_fmp_company_search.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import pytest 4 | 5 | from fmp_py.fmp_company_search import FmpCompanySearch 6 | 7 | 8 | @pytest.fixture 9 | def fmp_company_search(): 10 | return FmpCompanySearch() 11 | 12 | 13 | def test_fmp_company_search_isin_search(fmp_company_search): 14 | search_results = fmp_company_search.isin_search("US0378331005") 15 | assert isinstance(search_results, pd.DataFrame) 16 | assert len(search_results) > 0 17 | assert "symbol" in search_results.columns 18 | assert "company_name" in search_results.columns 19 | assert isinstance(search_results["symbol"].iloc[0], str) 20 | assert isinstance(search_results["company_name"].iloc[0], str) 21 | assert isinstance(search_results["currency"].iloc[0], str) 22 | assert isinstance(search_results["exchange"].iloc[0], str) 23 | assert isinstance(search_results["exchange_short_name"].iloc[0], str) 24 | assert isinstance(search_results["city"].iloc[0], str) 25 | assert isinstance(search_results["country"].iloc[0], str) 26 | assert isinstance(search_results["website"].iloc[0], str) 27 | assert isinstance(search_results["industry"].iloc[0], str) 28 | assert isinstance(search_results["description"].iloc[0], str) 29 | assert isinstance(search_results["sector"].iloc[0], str) 30 | assert isinstance(search_results["full_time_employees"].iloc[0], np.int64) 31 | assert isinstance(search_results["phone"].iloc[0], str) 32 | assert isinstance(search_results["address"].iloc[0], str) 33 | assert isinstance(search_results["state"].iloc[0], str) 34 | assert isinstance(search_results["zip"].iloc[0], str) 35 | assert isinstance(search_results["price"].iloc[0], np.float64) 36 | assert isinstance(search_results["beta"].iloc[0], np.float64) 37 | assert isinstance(search_results["default_image"].iloc[0], np.bool) 38 | assert isinstance(search_results["is_actively_trading"].iloc[0], np.bool) 39 | assert isinstance(search_results["is_adr"].iloc[0], np.bool) 40 | assert isinstance(search_results["is_etf"].iloc[0], np.bool) 41 | assert isinstance(search_results["is_fund"].iloc[0], np.bool) 42 | 43 | 44 | def test_fmp_company_search_isin_search_invalid_isin(fmp_company_search): 45 | with pytest.raises(ValueError): 46 | fmp_company_search.isin_search("invalid_isin") 47 | 48 | 49 | def test_fmp_company_search_cik_name_search(fmp_company_search): 50 | search_results = fmp_company_search.cik_name_search("apple") 51 | assert isinstance(search_results, pd.DataFrame) 52 | assert len(search_results) > 0 53 | assert "cik" in search_results.columns 54 | assert "name" in search_results.columns 55 | assert isinstance(search_results["cik"].iloc[0], str) 56 | assert isinstance(search_results["name"].iloc[0], str) 57 | 58 | 59 | def test_fmp_company_search_cik_name_search_invalid_company_name(fmp_company_search): 60 | with pytest.raises(ValueError): 61 | fmp_company_search.cik_name_search("invalid_company_name") 62 | 63 | 64 | def test_fmp_company_search_cik_search(fmp_company_search): 65 | search_results = fmp_company_search.cik_search("0001067983") 66 | assert isinstance(search_results, pd.DataFrame) 67 | assert len(search_results) > 0 68 | assert "cik" in search_results.columns 69 | assert "name" in search_results.columns 70 | assert isinstance(search_results["cik"].iloc[0], str) 71 | assert isinstance(search_results["name"].iloc[0], str) 72 | 73 | 74 | def test_fmp_company_search_cik_search_invalid_cik(fmp_company_search): 75 | with pytest.raises(ValueError): 76 | fmp_company_search.cik_search("invalid_cik") 77 | 78 | 79 | def test_fmp_company_search_name_search(fmp_company_search): 80 | search_results = fmp_company_search.name_search("apple") 81 | assert isinstance(search_results, pd.DataFrame) 82 | assert len(search_results) > 0 83 | assert "symbol" in search_results.columns 84 | assert "name" in search_results.columns 85 | assert "currency" in search_results.columns 86 | assert "stock_exchange" in search_results.columns 87 | assert "exchange_short_name" in search_results.columns 88 | assert isinstance(search_results["symbol"].iloc[0], str) 89 | assert isinstance(search_results["name"].iloc[0], str) 90 | assert isinstance(search_results["currency"].iloc[0], str) 91 | assert isinstance(search_results["stock_exchange"].iloc[0], str) 92 | 93 | 94 | def test_fmp_company_search_name_search_with_limit(fmp_company_search): 95 | search_results = fmp_company_search.name_search("apple", limit=10) 96 | assert len(search_results) == 10 97 | 98 | 99 | def test_fmp_company_search_name_search_with_exchange(fmp_company_search): 100 | search_results = fmp_company_search.name_search("apple", exchange="NASDAQ") 101 | assert len(search_results) > 0 102 | assert search_results["stock_exchange"].str.contains("NASDAQ").all() 103 | 104 | 105 | def test_fmp_company_search_name_search_with_invalid_exchange(fmp_company_search): 106 | with pytest.raises(ValueError): 107 | fmp_company_search.name_search("apple", exchange="INVALID_EXCHANGE") 108 | 109 | 110 | def test_fmp_company_search_name_search_with_no_results(fmp_company_search): 111 | with pytest.raises(ValueError): 112 | fmp_company_search.name_search("INVALID_QUERY") 113 | 114 | 115 | def test_fmp_company_search_ticker_search(fmp_company_search): 116 | search_results = fmp_company_search.ticker_search("apple") 117 | assert isinstance(search_results, pd.DataFrame) 118 | assert len(search_results) > 0 119 | assert "symbol" in search_results.columns 120 | assert "name" in search_results.columns 121 | assert "currency" in search_results.columns 122 | assert "stock_exchange" in search_results.columns 123 | assert "exchange_short_name" in search_results.columns 124 | assert isinstance(search_results["symbol"].iloc[0], str) 125 | assert isinstance(search_results["name"].iloc[0], str) 126 | assert isinstance(search_results["currency"].iloc[0], str) 127 | assert isinstance(search_results["stock_exchange"].iloc[0], str) 128 | assert isinstance(search_results["exchange_short_name"].iloc[0], str) 129 | 130 | 131 | def test_fmp_company_search_ticker_search_with_limit(fmp_company_search): 132 | search_results = fmp_company_search.ticker_search("apple", limit=10) 133 | assert len(search_results) == 10 134 | 135 | 136 | def test_fmp_company_search_ticker_search_with_invalid_exchange(fmp_company_search): 137 | with pytest.raises(ValueError): 138 | fmp_company_search.ticker_search("apple", exchange="invalid_exchange") 139 | 140 | 141 | def test_fmp_company_search_ticker_search_with_no_data(fmp_company_search): 142 | with pytest.raises(ValueError): 143 | fmp_company_search.ticker_search("invalid_query") 144 | 145 | 146 | def test_fmp_company_search_general_search(fmp_company_search): 147 | search_results = fmp_company_search.general_search("apple") 148 | assert isinstance(search_results, pd.DataFrame) 149 | assert len(search_results) > 0 150 | assert "symbol" in search_results.columns 151 | assert "name" in search_results.columns 152 | assert "currency" in search_results.columns 153 | assert "stock_exchange" in search_results.columns 154 | assert "exchange_short_name" in search_results.columns 155 | assert isinstance(search_results["symbol"].iloc[0], str) 156 | assert isinstance(search_results["name"].iloc[0], str) 157 | assert isinstance(search_results["currency"].iloc[0], str) 158 | assert isinstance(search_results["stock_exchange"].iloc[0], str) 159 | assert isinstance(search_results["exchange_short_name"].iloc[0], str) 160 | 161 | 162 | def test_fmp_company_search_general_search_invalid_exchange(fmp_company_search): 163 | with pytest.raises(ValueError): 164 | fmp_company_search.general_search("apple", exchange="invalid_exchange") 165 | 166 | 167 | def test_fmp_company_search_general_search_check_limit(fmp_company_search): 168 | search_results = fmp_company_search.general_search("apple", limit=1) 169 | assert len(search_results) == 1 170 | 171 | 172 | def test_fmp_company_search_general_search_no_results(fmp_company_search): 173 | with pytest.raises(ValueError): 174 | fmp_company_search.general_search("invalid_query") 175 | -------------------------------------------------------------------------------- /tests/test_fmp_dividends.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | 4 | from fmp_py.fmp_dividends import FmpDividends 5 | 6 | 7 | @pytest.fixture 8 | def fmp_dividends(): 9 | return FmpDividends() 10 | 11 | 12 | def test_fmp_dividends_init(fmp_dividends): 13 | assert isinstance(fmp_dividends, FmpDividends) 14 | 15 | 16 | def test_fmp_dividends_calendar(fmp_dividends): 17 | result = fmp_dividends.dividends_calendar( 18 | from_date="2023-01-01", to_date="2023-01-31" 19 | ) 20 | assert isinstance(result, pd.DataFrame) 21 | assert "date" in result.columns 22 | assert "label" in result.columns 23 | assert "adj_dividend" in result.columns 24 | assert "symbol" in result.columns 25 | assert "dividend" in result.columns 26 | assert "record_date" in result.columns 27 | assert "payment_date" in result.columns 28 | assert "declaration_date" in result.columns 29 | assert isinstance(result["date"].iloc[0], pd.Timestamp) 30 | assert isinstance(result["label"].iloc[0], str) 31 | assert isinstance(result["adj_dividend"].iloc[0], float) 32 | assert isinstance(result["symbol"].iloc[0], str) 33 | assert isinstance(result["dividend"].iloc[0], float) 34 | assert isinstance(result["record_date"].iloc[0], pd.Timestamp) 35 | assert isinstance(result["payment_date"].iloc[0], pd.Timestamp) 36 | assert isinstance(result["declaration_date"].iloc[0], pd.Timestamp) 37 | 38 | 39 | def test_fmp_dividends_calendar_invalid_date(fmp_dividends): 40 | with pytest.raises(Exception): 41 | fmp_dividends.dividends_calendar(from_date="2023-01-01", to_date="2022-01-01") 42 | 43 | 44 | def test_fmp_dividends_historical(fmp_dividends): 45 | result = fmp_dividends.dividends_historical("AAPL") 46 | assert isinstance(result, pd.DataFrame) 47 | assert "date" in result.columns 48 | assert "label" in result.columns 49 | assert "adj_dividend" in result.columns 50 | assert "symbol" in result.columns 51 | assert "dividend" in result.columns 52 | assert "record_date" in result.columns 53 | assert "payment_date" in result.columns 54 | assert "declaration_date" in result.columns 55 | assert isinstance(result["date"].iloc[0], pd.Timestamp) 56 | assert isinstance(result["label"].iloc[0], str) 57 | assert isinstance(result["adj_dividend"].iloc[0], float) 58 | assert isinstance(result["symbol"].iloc[0], str) 59 | assert isinstance(result["dividend"].iloc[0], float) 60 | assert isinstance(result["record_date"].iloc[0], pd.Timestamp) 61 | assert isinstance(result["payment_date"].iloc[0], pd.Timestamp) 62 | assert isinstance(result["declaration_date"].iloc[0], pd.Timestamp) 63 | 64 | 65 | def test_fmp_dividends_historical_invalid_symbol(fmp_dividends): 66 | with pytest.raises(Exception): 67 | fmp_dividends.dividends_historical("INVALID_SYMBOL") 68 | -------------------------------------------------------------------------------- /tests/test_fmp_earnings.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import pandas as pd 3 | import numpy as np 4 | from fmp_py.fmp_earnings import FmpEarnings 5 | 6 | 7 | @pytest.fixture 8 | def fmp_earnings(): 9 | return FmpEarnings() 10 | 11 | 12 | def test_fmp_earnings_init(fmp_earnings): 13 | assert isinstance(fmp_earnings, FmpEarnings) 14 | 15 | 16 | def test_fmp_earnings_surprises(fmp_earnings): 17 | result = fmp_earnings.earnings_surprises("AAPL") 18 | assert isinstance(result, pd.DataFrame) 19 | assert "symbol" in result.columns 20 | assert "date" in result.columns 21 | assert isinstance(result["symbol"].iloc[0], str) 22 | assert isinstance(result["date"].iloc[0], pd.Timestamp) 23 | assert isinstance(result["estimated_earning"].iloc[0], float) 24 | assert isinstance(result["actual_earning_result"].iloc[0], float) 25 | 26 | 27 | def test_fmp_earnings_surprises_invalid_symbol(fmp_earnings): 28 | with pytest.raises(Exception): 29 | fmp_earnings.earnings_surprises("INVALID_SYMBOL") 30 | 31 | 32 | def test_fmp_earnings_confirmed(fmp_earnings): 33 | result = fmp_earnings.earnings_confirmed("2023-04-18", "2024-04-18") 34 | assert isinstance(result, pd.DataFrame) 35 | assert "symbol" in result.columns 36 | assert "date" in result.columns 37 | assert isinstance(result["symbol"].iloc[0], str) 38 | assert isinstance(result["date"].iloc[0], pd.Timestamp) 39 | assert isinstance(result["publication_date"].iloc[0], pd.Timestamp) 40 | assert isinstance(result["time"].iloc[0], str) 41 | assert isinstance(result["when"].iloc[0], str) 42 | assert isinstance(result["title"].iloc[0], str) 43 | 44 | 45 | def test_fmp_earnings_confirmed_invalid_date(fmp_earnings): 46 | with pytest.raises(Exception): 47 | fmp_earnings.earnings_confirmed("2023-04-18", "2022-04-18") 48 | 49 | 50 | def test_fmp_earnings_calendar(fmp_earnings): 51 | result = fmp_earnings.earnings_calendar("2023-04-18", "2024-04-18") 52 | assert isinstance(result, pd.DataFrame) 53 | assert "symbol" in result.columns 54 | assert "date" in result.columns 55 | assert isinstance(result["symbol"].iloc[0], str) 56 | assert isinstance(result["date"].iloc[0], pd.Timestamp) 57 | assert isinstance(result["fiscal_date_ending"].iloc[0], pd.Timestamp) 58 | assert isinstance(result["updated_from_date"].iloc[0], pd.Timestamp) 59 | assert isinstance(result["time"].iloc[0], str) 60 | assert isinstance(result["revenue_estimated"].iloc[0], np.int64) 61 | assert isinstance(result["revenue"].iloc[0], np.int64) 62 | assert isinstance(result["eps"].iloc[0], np.float64) 63 | assert isinstance(result["eps_estimated"].iloc[0], np.float64) 64 | 65 | 66 | def test_fmp_earnings_calendar_invalid_date(fmp_earnings): 67 | with pytest.raises(Exception): 68 | fmp_earnings.earnings_calendar("2023-04-18", "2022-04-18") 69 | 70 | 71 | def test_fmp_earnings_historical(fmp_earnings): 72 | result = fmp_earnings.earnings_historical("AAPL") 73 | assert isinstance(result, pd.DataFrame) 74 | assert "symbol" in result.columns 75 | assert "date" in result.columns 76 | assert isinstance(result["symbol"].iloc[0], str) 77 | assert isinstance(result["date"].iloc[0], pd.Timestamp) 78 | assert isinstance(result["fiscal_date_ending"].iloc[0], pd.Timestamp) 79 | assert isinstance(result["updated_from_date"].iloc[0], pd.Timestamp) 80 | assert isinstance(result["revenue"].iloc[0], np.int64) 81 | assert isinstance(result["eps"].iloc[0], np.float64) 82 | assert isinstance(result["eps_estimated"].iloc[0], np.float64) 83 | 84 | 85 | def test_fmp_earnings_historical_invalid_symbol(fmp_earnings): 86 | with pytest.raises(Exception): 87 | fmp_earnings.earnings_historical("INVALID_SYMBOL") 88 | 89 | 90 | def test_fmp_earning_within_weeks(fmp_earnings): 91 | result = fmp_earnings.earnings_within_weeks("AAPL", 2) 92 | assert isinstance(result, bool) 93 | 94 | 95 | def test_fmp_earning_within_weeks_invalid_symbol(fmp_earnings): 96 | result = fmp_earnings.earnings_within_weeks("INVALID_SYMBOL", 2) 97 | assert not result 98 | -------------------------------------------------------------------------------- /tests/test_fmp_historical_data.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | 4 | # from unittest.mock import patch 5 | from fmp_py.fmp_historical_data import FmpHistoricalData 6 | import pandas as pd 7 | 8 | 9 | @pytest.fixture 10 | def fmp_historical_data(): 11 | return FmpHistoricalData() 12 | 13 | 14 | def test_fmp_historical_data_init(fmp_historical_data): 15 | assert isinstance(fmp_historical_data, FmpHistoricalData) 16 | 17 | 18 | def test_fmp_historical_data_daily_history(fmp_historical_data): 19 | symbol = "AAPL" 20 | from_date = "2023-01-01" 21 | to_date = "2023-01-10" 22 | data = fmp_historical_data.daily_history(symbol, from_date, to_date) 23 | assert isinstance(data, pd.DataFrame) 24 | assert not data.empty 25 | assert "date" in data.index.names 26 | assert isinstance(data.index, pd.DatetimeIndex) 27 | assert isinstance(data.iloc[0]["open"], np.float64) 28 | assert isinstance(data.iloc[0]["high"], np.float64) 29 | assert isinstance(data.iloc[0]["low"], np.float64) 30 | assert isinstance(data.iloc[0]["close"], np.float64) 31 | assert isinstance(data.iloc[0]["volume"], np.float64) 32 | 33 | 34 | def test_fmp_historical_data_daily_history_with_invalid_symbol(fmp_historical_data): 35 | symbol = "INVALID_SYMBOL" 36 | from_date = "2023-01-01" 37 | to_date = "2023-01-10" 38 | with pytest.raises(ValueError): 39 | fmp_historical_data.daily_history(symbol, from_date, to_date) 40 | 41 | 42 | def test_fmp_historical_data_intraday_history(fmp_historical_data): 43 | symbol = "AAPL" 44 | interval = "1min" 45 | from_date = "2023-01-01" 46 | to_date = "2023-01-10" 47 | data = fmp_historical_data.intraday_history(symbol, interval, from_date, to_date) 48 | assert isinstance(data, pd.DataFrame) 49 | assert not data.empty 50 | assert "date" in data.index.names 51 | assert isinstance(data.index, pd.DatetimeIndex) 52 | assert isinstance(data.iloc[0]["open"], np.float64) 53 | assert isinstance(data.iloc[0]["high"], np.float64) 54 | assert isinstance(data.iloc[0]["low"], np.float64) 55 | assert isinstance(data.iloc[0]["close"], np.float64) 56 | assert isinstance(data.iloc[0]["volume"], np.float64) 57 | 58 | 59 | def test_fmp_historical_data_intraday_history_with_invalid_symbol(fmp_historical_data): 60 | symbol = "INVALID_SYMBOL" 61 | interval = "1min" 62 | from_date = "2023-01-01" 63 | to_date = "2023-01-10" 64 | with pytest.raises(ValueError): 65 | fmp_historical_data.intraday_history(symbol, interval, from_date, to_date) 66 | 67 | 68 | # @pytest.fixture 69 | # def mock_response_daily(): 70 | # return { 71 | # "historical": [ 72 | # { 73 | # "date": "2023-01-01", 74 | # "open": 100, 75 | # "high": 110, 76 | # "low": 90, 77 | # "close": 105, 78 | # "volume": 1000, 79 | # }, 80 | # { 81 | # "date": "2023-01-02", 82 | # "open": 106, 83 | # "high": 115, 84 | # "low": 105, 85 | # "close": 110, 86 | # "volume": 1500, 87 | # }, 88 | # ] 89 | # } 90 | 91 | 92 | # @pytest.fixture 93 | # def mock_response_intraday(): 94 | # return [ 95 | # { 96 | # "date": "2023-01-01 09:30:00", 97 | # "open": 100, 98 | # "high": 110, 99 | # "low": 90, 100 | # "close": 105, 101 | # "volume": 1000, 102 | # }, 103 | # { 104 | # "date": "2023-01-01 09:31:00", 105 | # "open": 106, 106 | # "high": 115, 107 | # "low": 105, 108 | # "close": 110, 109 | # "volume": 1500, 110 | # }, 111 | # ] 112 | 113 | 114 | # def test_fmp_historical_data_initialization(): 115 | # fmp = FmpHistoricalData() 116 | # assert hasattr( 117 | # fmp, "api_key" 118 | # ), "FmpHistoricalData class should have an attribute 'api_key'" 119 | 120 | 121 | # @patch.object(FmpHistoricalData, "get_request") 122 | # def test_daily_history(mock_get_request, mock_response_daily): 123 | # mock_get_request.return_value = mock_response_daily 124 | 125 | # fmp = FmpHistoricalData() 126 | # symbol = "AAPL" 127 | # from_date = "2023-01-01" 128 | # to_date = "2023-01-02" 129 | # data = fmp.daily_history(symbol, from_date, to_date) 130 | 131 | # assert isinstance(data, pd.DataFrame) 132 | # assert not data.empty 133 | # assert "vwap" in data.columns 134 | 135 | 136 | # @patch.object(FmpHistoricalData, "get_request") 137 | # def test_intraday_history(mock_get_request, mock_response_intraday): 138 | # mock_get_request.return_value = mock_response_intraday 139 | 140 | # fmp = FmpHistoricalData() 141 | # symbol = "AAPL" 142 | # interval = "1min" 143 | # from_date = "2023-01-01" 144 | # to_date = "2023-01-01" 145 | # data = fmp.intraday_history(symbol, interval, from_date, to_date) 146 | 147 | # assert isinstance(data, pd.DataFrame) 148 | # assert not data.empty 149 | # assert "vwap" in data.columns 150 | 151 | 152 | # def test_prepare_data(): 153 | # fmp = FmpHistoricalData() 154 | # data_df = pd.DataFrame( 155 | # { 156 | # "date": ["2023-01-01", "2023-01-02"], 157 | # "open": [100, 106], 158 | # "high": [110, 115], 159 | # "low": [90, 105], 160 | # "close": [105, 110], 161 | # "volume": [1000, 1500], 162 | # } 163 | # ) 164 | 165 | # prepared_data = fmp._prepare_data(data_df.copy()) 166 | # assert "vwap" in prepared_data.columns 167 | # assert prepared_data["vwap"].dtype == "float" 168 | # assert prepared_data["date"].dtype == "datetime64[ns]" 169 | 170 | 171 | # def test_calc_vwap(): 172 | # fmp = FmpHistoricalData() 173 | # data_df = pd.DataFrame( 174 | # { 175 | # "date": ["2023-01-01", "2023-01-02"], 176 | # "open": [100, 106], 177 | # "high": [110, 115], 178 | # "low": [90, 105], 179 | # "close": [105, 110], 180 | # "volume": [1000, 1500], 181 | # } 182 | # ) 183 | 184 | # vwap = fmp._calc_vwap(data_df) 185 | # expected_vwap = pd.Series([101.67, 106.67]) 186 | # pd.testing.assert_series_equal(vwap.round(2), expected_vwap) 187 | 188 | 189 | # def test_round_prices(): 190 | # fmp = FmpHistoricalData() 191 | # data_df = pd.DataFrame( 192 | # { 193 | # "date": ["2023-01-01", "2023-01-02"], 194 | # "open": [100.123, 106.456], 195 | # "high": [110.789, 115.101], 196 | # "low": [90.112, 105.991], 197 | # "close": [105.553, 110.234], 198 | # "volume": [1000, 1500], 199 | # } 200 | # ) 201 | 202 | # rounded_data = fmp._round_prices(data_df) 203 | # assert rounded_data["open"].iloc[0] == 100.12 204 | # assert rounded_data["high"].iloc[1] == 115.10 205 | # assert rounded_data["low"].iloc[0] == 90.11 206 | # assert rounded_data["close"].iloc[1] == 110.23 207 | -------------------------------------------------------------------------------- /tests/test_fmp_ipo_calendar.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import pytest 4 | 5 | from fmp_py.fmp_ipo_calendar import FmpIpoCalendar 6 | 7 | 8 | @pytest.fixture 9 | def fmp_ipo_calendar(): 10 | return FmpIpoCalendar() 11 | 12 | 13 | def test_fmp_ipo_calendar_init(fmp_ipo_calendar): 14 | assert isinstance(fmp_ipo_calendar, FmpIpoCalendar) 15 | 16 | 17 | def test_fmp_ipo_calendar_ipo_calendar_by_symbol(fmp_ipo_calendar): 18 | from_date = "2023-01-01" 19 | to_date = "2023-01-31" 20 | result = fmp_ipo_calendar.ipo_calendar_by_symbol(from_date, to_date) 21 | assert isinstance(result, pd.DataFrame) 22 | assert not result.empty 23 | assert "date" in result.columns 24 | assert "company" in result.columns 25 | assert "symbol" in result.columns 26 | assert "exchange" in result.columns 27 | assert "actions" in result.columns 28 | assert "shares" in result.columns 29 | assert "price_range" in result.columns 30 | assert "market_cap" in result.columns 31 | assert isinstance(result["date"].iloc[0], pd.Timestamp) 32 | assert isinstance(result["company"].iloc[0], str) 33 | assert isinstance(result["symbol"].iloc[0], str) 34 | assert isinstance(result["exchange"].iloc[0], str) 35 | assert isinstance(result["actions"].iloc[0], str) 36 | assert isinstance(result["shares"].iloc[0], np.int64) 37 | assert isinstance(result["price_range"].iloc[0], str) 38 | assert isinstance(result["market_cap"].iloc[0], np.int64) 39 | 40 | 41 | def test_fmp_ipo_calendar_ipo_calendar_by_symbol_invalid_date_range(fmp_ipo_calendar): 42 | from_date = "2023-01-31" 43 | to_date = "2023-01-01" 44 | with pytest.raises(ValueError): 45 | fmp_ipo_calendar.ipo_calendar_by_symbol(from_date, to_date) 46 | 47 | 48 | def test_fmp_ipo_calendar_ipo_prospectus(fmp_ipo_calendar): 49 | result = fmp_ipo_calendar.ipo_prospectus( 50 | from_date="2023-01-01", to_date="2023-01-31" 51 | ) 52 | assert isinstance(result, pd.DataFrame) 53 | assert "symbol" in result.columns 54 | assert "cik" in result.columns 55 | assert "form" in result.columns 56 | assert "filing_date" in result.columns 57 | assert "accepted_date" in result.columns 58 | assert "ipo_date" in result.columns 59 | assert "price_public_per_share" in result.columns 60 | assert "price_public_total" in result.columns 61 | assert "discounts_and_commissions_per_share" in result.columns 62 | assert "discounts_and_commissions_total" in result.columns 63 | assert "proceeds_before_expenses_per_share" in result.columns 64 | assert "proceeds_before_expenses_total" in result.columns 65 | assert "url" in result.columns 66 | assert isinstance(result["symbol"].iloc[0], str) 67 | assert isinstance(result["cik"].iloc[0], str) 68 | assert isinstance(result["form"].iloc[0], str) 69 | assert isinstance(result["filing_date"].iloc[0], pd.Timestamp) 70 | assert isinstance(result["accepted_date"].iloc[0], pd.Timestamp) 71 | assert isinstance(result["ipo_date"].iloc[0], pd.Timestamp) 72 | assert isinstance(result["price_public_per_share"].iloc[0], float) 73 | assert isinstance(result["price_public_total"].iloc[0], float) 74 | assert isinstance(result["discounts_and_commissions_per_share"].iloc[0], float) 75 | assert isinstance(result["discounts_and_commissions_total"].iloc[0], float) 76 | assert isinstance(result["proceeds_before_expenses_per_share"].iloc[0], float) 77 | assert isinstance(result["proceeds_before_expenses_total"].iloc[0], float) 78 | assert isinstance(result["url"].iloc[0], str) 79 | 80 | 81 | def test_fmp_ipo_calendar_ipo_prspectus_invalid_date_range(fmp_ipo_calendar): 82 | with pytest.raises(ValueError): 83 | fmp_ipo_calendar.ipo_prospectus(from_date="2023-01-31", to_date="2023-01-01") 84 | 85 | 86 | def test_fmp_ipo_calendar_ipo_confirmed(fmp_ipo_calendar): 87 | result = fmp_ipo_calendar.ipo_confirmed( 88 | from_date="2023-01-01", to_date="2023-01-31" 89 | ) 90 | assert isinstance(result, pd.DataFrame) 91 | assert "symbol" in result.columns 92 | assert "cik" in result.columns 93 | assert "form" in result.columns 94 | assert "filing_date" in result.columns 95 | assert "accepted_date" in result.columns 96 | assert "effectiveness_date" in result.columns 97 | assert "url" in result.columns 98 | assert isinstance(result["symbol"].iloc[0], str) 99 | assert isinstance(result["cik"].iloc[0], str) 100 | assert isinstance(result["form"].iloc[0], str) 101 | assert isinstance(result["filing_date"].iloc[0], pd.Timestamp) 102 | assert isinstance(result["accepted_date"].iloc[0], pd.Timestamp) 103 | assert isinstance(result["effectiveness_date"].iloc[0], pd.Timestamp) 104 | assert isinstance(result["url"].iloc[0], str) 105 | 106 | 107 | def test_fmp_ipo_calendar_ipo_confirmed_invalid_date_range(fmp_ipo_calendar): 108 | with pytest.raises(ValueError): 109 | fmp_ipo_calendar.ipo_confirmed(from_date="2023-01-31", to_date="2023-01-01") 110 | -------------------------------------------------------------------------------- /tests/test_fmp_mergers_and_aquisitions.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import pandas as pd 3 | 4 | from fmp_py.fmp_mergers_and_aquisitions import FmpMergersAndAquisitions 5 | 6 | 7 | @pytest.fixture 8 | def fmp_mergers_and_aquisitions(): 9 | return FmpMergersAndAquisitions() 10 | 11 | 12 | def test_fmp_mergers_and_aquisitions_init(fmp_mergers_and_aquisitions): 13 | assert isinstance(fmp_mergers_and_aquisitions, FmpMergersAndAquisitions) 14 | 15 | 16 | def test_fmp_mergers_and_aquisitions_ma_rss_feed(fmp_mergers_and_aquisitions): 17 | result = fmp_mergers_and_aquisitions.ma_rss_feed() 18 | assert isinstance(result, pd.DataFrame) 19 | assert not result.empty 20 | assert result.columns.tolist() == [ 21 | "company_name", 22 | "cik", 23 | "symbol", 24 | "targeted_company_name", 25 | "targeted_cik", 26 | "targeted_symbol", 27 | "transaction_date", 28 | "acceptance_time", 29 | "url", 30 | ] 31 | assert isinstance(result["company_name"].iloc[0], str) 32 | assert isinstance(result["cik"].iloc[0], str) 33 | assert isinstance(result["symbol"].iloc[0], str) 34 | assert isinstance(result["targeted_company_name"].iloc[0], str) 35 | assert isinstance(result["targeted_cik"].iloc[0], str) 36 | assert isinstance(result["targeted_symbol"].iloc[0], str) 37 | assert isinstance(result["transaction_date"].iloc[0], pd.Timestamp) 38 | assert isinstance(result["acceptance_time"].iloc[0], pd.Timestamp) 39 | assert isinstance(result["url"].iloc[0], str) 40 | 41 | 42 | def test_fmp_mergers_and_aquisitions_ma_rss_feed_by_page(fmp_mergers_and_aquisitions): 43 | result = fmp_mergers_and_aquisitions.ma_rss_feed(page=1) 44 | assert isinstance(result, pd.DataFrame) 45 | assert not result.empty 46 | assert result.columns.tolist() == [ 47 | "company_name", 48 | "cik", 49 | "symbol", 50 | "targeted_company_name", 51 | "targeted_cik", 52 | "targeted_symbol", 53 | "transaction_date", 54 | "acceptance_time", 55 | "url", 56 | ] 57 | assert isinstance(result["company_name"].iloc[0], str) 58 | assert isinstance(result["cik"].iloc[0], str) 59 | assert isinstance(result["symbol"].iloc[0], str) 60 | assert isinstance(result["targeted_company_name"].iloc[0], str) 61 | assert isinstance(result["targeted_cik"].iloc[0], str) 62 | assert isinstance(result["targeted_symbol"].iloc[0], str) 63 | assert isinstance(result["transaction_date"].iloc[0], pd.Timestamp) 64 | assert isinstance(result["acceptance_time"].iloc[0], pd.Timestamp) 65 | assert isinstance(result["url"].iloc[0], str) 66 | 67 | 68 | def test_fmp_mergers_and_aquisitions_search_ma(fmp_mergers_and_aquisitions): 69 | result = fmp_mergers_and_aquisitions.search_ma(name="test") 70 | assert isinstance(result, pd.DataFrame) 71 | assert not result.empty 72 | assert result.columns.tolist() == [ 73 | "company_name", 74 | "cik", 75 | "symbol", 76 | "targeted_company_name", 77 | "targeted_cik", 78 | "targeted_symbol", 79 | "transaction_date", 80 | "acceptance_time", 81 | "url", 82 | ] 83 | assert isinstance(result["company_name"].iloc[0], str) 84 | assert isinstance(result["symbol"].iloc[0], str) 85 | assert isinstance(result["targeted_company_name"].iloc[0], str) 86 | assert isinstance(result["targeted_cik"].iloc[0], str) 87 | assert isinstance(result["targeted_symbol"].iloc[0], str) 88 | assert isinstance(result["transaction_date"].iloc[0], pd.Timestamp) 89 | assert isinstance(result["acceptance_time"].iloc[0], pd.Timestamp) 90 | assert isinstance(result["url"].iloc[0], str) 91 | -------------------------------------------------------------------------------- /tests/test_fmp_price_targets.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import pytest 4 | 5 | from fmp_py.fmp_price_targets import FmpPriceTargets 6 | from fmp_py.models.price_targets import PriceTargetConsensus, PriceTargetSummary 7 | 8 | 9 | @pytest.fixture 10 | def fmp_price_targets(): 11 | return FmpPriceTargets() 12 | 13 | 14 | def test_fmp_price_targets_init(fmp_price_targets): 15 | assert isinstance(fmp_price_targets, FmpPriceTargets) 16 | 17 | 18 | def test_fmp_price_targets_price_target_consensus(fmp_price_targets): 19 | data = fmp_price_targets.price_target_consensus("AAPL") 20 | assert isinstance(data, PriceTargetConsensus) 21 | assert isinstance(data.symbol, str) 22 | assert isinstance(data.target_high, float) 23 | assert isinstance(data.target_low, float) 24 | assert isinstance(data.target_consensus, float) 25 | assert isinstance(data.target_median, float) 26 | 27 | 28 | def test_fmp_price_targets_price_target_consensus_invalid_symbol(fmp_price_targets): 29 | with pytest.raises(ValueError): 30 | fmp_price_targets.price_target_consensus("INVALID_SYMBOL") 31 | 32 | 33 | def test_fmp_price_targets_price_target_summary(fmp_price_targets): 34 | data = fmp_price_targets.price_target_summary("AAPL") 35 | assert isinstance(data, PriceTargetSummary) 36 | assert isinstance(data.symbol, str) 37 | assert isinstance(data.last_month, int) 38 | assert isinstance(data.last_month_avg_price_target, float) 39 | assert isinstance(data.last_quarter, int) 40 | assert isinstance(data.last_quarter_avg_price_target, float) 41 | assert isinstance(data.last_year, int) 42 | assert isinstance(data.last_year_avg_price_target, float) 43 | assert isinstance(data.all_time, int) 44 | assert isinstance(data.all_time_avg_price_target, float) 45 | assert isinstance(data.publishers, list) 46 | 47 | 48 | def test_fmp_price_targets_price_target_summary_invalid_symbol(fmp_price_targets): 49 | with pytest.raises(ValueError): 50 | fmp_price_targets.price_target_summary("INVALID_SYMBOL") 51 | 52 | 53 | def test_fmp_price_targets_price_target(fmp_price_targets): 54 | data = fmp_price_targets.price_target("AAPL") 55 | assert isinstance(data, pd.DataFrame) 56 | assert not data.empty 57 | assert "symbol" in data.columns 58 | assert isinstance(data["symbol"][0], str) 59 | assert isinstance(data["published_date"][0], pd.Timestamp) 60 | assert isinstance(data["news_url"][0], str) 61 | assert isinstance(data["news_title"][0], str) 62 | assert isinstance(data["analyst_name"][0], str) 63 | assert isinstance(data["price_target"][0], np.float64) 64 | assert isinstance(data["adj_price_target"][0], np.float64) 65 | assert isinstance(data["price_when_posted"][0], np.float64) 66 | assert isinstance(data["news_publisher"][0], str) 67 | assert isinstance(data["news_base_url"][0], str) 68 | assert isinstance(data["analyst_company"][0], str) 69 | 70 | 71 | def test_fmp_price_targets_price_target_invalid_symbol(fmp_price_targets): 72 | with pytest.raises(ValueError): 73 | fmp_price_targets.price_target("INVALID_SYMBOL") 74 | -------------------------------------------------------------------------------- /tests/test_fmp_quote.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pytest 3 | import pandas as pd 4 | from fmp_py.fmp_quote import FmpQuote 5 | from fmp_py.models.quote import ( 6 | AftermarketTrade, 7 | AftermarketQuote, 8 | CryptoQuote, 9 | ForexQuote, 10 | FxPrice, 11 | RealtimeFullPrice, 12 | Quote, 13 | SimpleQuote, 14 | OtcQuote, 15 | PriceChange, 16 | ) 17 | 18 | 19 | @pytest.fixture 20 | def fmp_quote(): 21 | return FmpQuote() 22 | 23 | 24 | def test_fmp_quote_init(fmp_quote): 25 | assert isinstance(fmp_quote, FmpQuote) 26 | 27 | 28 | def test_fmp_quote_fx_prices(fmp_quote): 29 | df = fmp_quote.fx_prices() 30 | assert isinstance(df, pd.DataFrame) 31 | assert isinstance(df["ticker"][0], str) 32 | assert isinstance(df["bid"][0], np.float64) 33 | assert isinstance(df["ask"][0], np.float64) 34 | assert isinstance(df["open"][0], np.float64) 35 | assert isinstance(df["low"][0], np.float64) 36 | assert isinstance(df["high"][0], np.float64) 37 | assert isinstance(df["changes"][0], np.float64) 38 | assert isinstance(df["date"][0], pd.Timestamp) 39 | 40 | 41 | def test_fmp_quote_fx_prices_no_results(fmp_quote): 42 | with pytest.raises(ValueError): 43 | fmp_quote.fx_prices() == [] 44 | 45 | 46 | def test_fmp_quote_fx_price(fmp_quote): 47 | fx_price = fmp_quote.fx_price("EURUSD") 48 | assert isinstance(fx_price, FxPrice) 49 | assert fx_price.ticker == "EUR/USD" 50 | assert isinstance(fx_price.ticker, str) 51 | assert isinstance(fx_price.bid, float) 52 | assert isinstance(fx_price.ask, float) 53 | assert isinstance(fx_price.open, float) 54 | assert isinstance(fx_price.low, float) 55 | assert isinstance(fx_price.high, float) 56 | assert isinstance(fx_price.changes, float) 57 | assert isinstance(fx_price.date, str) 58 | 59 | 60 | def test_fmp_quote_fx_price_invalid_symbol(fmp_quote): 61 | with pytest.raises(ValueError): 62 | fmp_quote.fx_price("INVALID_SYMBOL") 63 | 64 | 65 | def test_fmp_quote_all_live_full_stock_prices(fmp_quote): 66 | df = fmp_quote.all_live_full_stock_prices() 67 | assert isinstance(df, pd.DataFrame) 68 | assert isinstance(df["symbol"][0], str) 69 | assert isinstance(df["ask_price"][0], np.float64) 70 | assert isinstance(df["ask_size"][0], np.int64) 71 | assert isinstance(df["bid_price"][0], np.float64) 72 | assert isinstance(df["bid_size"][0], np.int64) 73 | assert isinstance(df["last_sale_price"][0], np.float64) 74 | assert isinstance(df["last_sale_size"][0], np.int64) 75 | assert isinstance(df["last_sale_time"][0], pd.Timestamp) 76 | assert isinstance(df["fmp_last"][0], np.float64) 77 | assert isinstance(df["last_updated"][0], pd.Timestamp) 78 | 79 | 80 | def test_fmp_quote_all_live_full_stock_prices_no_results(fmp_quote): 81 | with pytest.raises(ValueError): 82 | fmp_quote.all_live_full_stock_prices() == [] 83 | 84 | 85 | def test_fmp_quote_live_full_stock_price(fmp_quote): 86 | quote = fmp_quote.live_full_stock_price("AAPL") 87 | assert isinstance(quote, RealtimeFullPrice) 88 | assert quote.symbol == "AAPL" 89 | assert isinstance(quote.symbol, str) 90 | assert isinstance(quote.ask_price, float) 91 | assert isinstance(quote.ask_size, int) 92 | assert isinstance(quote.bid_price, float) 93 | assert isinstance(quote.bid_size, int) 94 | assert isinstance(quote.last_sale_price, float) 95 | assert isinstance(quote.last_sale_size, int) 96 | assert isinstance(quote.last_sale_time, str) 97 | assert isinstance(quote.fmp_last, float) 98 | assert isinstance(quote.last_updated, str) 99 | 100 | 101 | def test_fmp_quote_live_full_stock_price_invalid_symbol(fmp_quote): 102 | with pytest.raises(ValueError): 103 | fmp_quote.live_full_stock_price("INVALID_SYMBOL") 104 | 105 | 106 | def test_fmp_quote_last_crypto(fmp_quote): 107 | quote = fmp_quote.last_crypto("BTCUSD") 108 | assert isinstance(quote, CryptoQuote) 109 | assert quote.symbol == "BTCUSD" 110 | assert isinstance(quote.symbol, str) 111 | assert isinstance(quote.price, float) 112 | assert isinstance(quote.size, float) 113 | assert isinstance(quote.timestamp, str) 114 | 115 | 116 | def test_fmp_quote_last_crypto_invalid_symbol(fmp_quote): 117 | with pytest.raises(ValueError): 118 | fmp_quote.last_crypto("INVALID_SYMBOL") 119 | 120 | 121 | def test_fmp_quote_last_forex(fmp_quote): 122 | quote = fmp_quote.last_forex("EURUSD") 123 | assert isinstance(quote, ForexQuote) 124 | assert quote.symbol == "EURUSD" 125 | assert isinstance(quote.symbol, str) 126 | assert isinstance(quote.bid, float) 127 | assert isinstance(quote.ask, float) 128 | assert isinstance(quote.timestamp, str) 129 | 130 | 131 | def test_fmp_quote_last_forex_invalid_symbol(fmp_quote): 132 | with pytest.raises(ValueError): 133 | fmp_quote.last_forex("INVALID_SYMBOL") 134 | 135 | 136 | def test_fmp_quote_batch_trade(fmp_quote): 137 | df = fmp_quote.batch_trade(["AAPL", "MSFT"]) 138 | assert isinstance(df, pd.DataFrame) 139 | assert isinstance(df["symbol"][0], str) 140 | assert isinstance(df["price"][0], np.float64) 141 | assert isinstance(df["size"][0], np.int64) 142 | assert isinstance(df["timestamp"][0], pd.Timestamp) 143 | 144 | 145 | def test_fmp_quote_batch_trade_not_list(fmp_quote): 146 | with pytest.raises(ValueError): 147 | fmp_quote.batch_trade("AAPL") 148 | 149 | 150 | def test_fmp_quote_batch_trade_invalid_symbol(fmp_quote): 151 | with pytest.raises(ValueError): 152 | fmp_quote.batch_trade(["INVAILD_SYMBOL"]) 153 | 154 | 155 | def test_fmp_quote_batch_quote(fmp_quote): 156 | df = fmp_quote.batch_quote(["AAPL", "MSFT"]) 157 | assert isinstance(df, pd.DataFrame) 158 | assert isinstance(df["symbol"][0], str) 159 | assert isinstance(df["ask"][0], np.float64) 160 | assert isinstance(df["bid"][0], np.float64) 161 | assert isinstance(df["asize"][0], np.int64) 162 | assert isinstance(df["bsize"][0], np.int64) 163 | assert isinstance(df["timestamp"][0], pd.Timestamp) 164 | 165 | 166 | def test_fmp_quote_batch_quote_not_list(fmp_quote): 167 | with pytest.raises(ValueError): 168 | fmp_quote.batch_quote("AAPL") 169 | 170 | 171 | def test_fmp_quote_batch_quote_invalid_symbol(fmp_quote): 172 | with pytest.raises(ValueError): 173 | fmp_quote.batch_quote(["INVALID_SYMBOL"]) 174 | 175 | 176 | def test_fmp_quote_aftermarket_quote(fmp_quote): 177 | aftermarket_quote = fmp_quote.aftermarket_quote("AAPL") 178 | assert isinstance(aftermarket_quote, AftermarketQuote) 179 | assert isinstance(aftermarket_quote.symbol, str) 180 | assert isinstance(aftermarket_quote.ask, float) 181 | assert isinstance(aftermarket_quote.asize, int) 182 | assert isinstance(aftermarket_quote.bid, float) 183 | assert isinstance(aftermarket_quote.bsize, int) 184 | assert isinstance(aftermarket_quote.timestamp, str) 185 | 186 | 187 | def test_fmp_quote_aftermarket_quote_invalid_symbol(fmp_quote): 188 | with pytest.raises(ValueError): 189 | fmp_quote.aftermarket_quote("INVALID_SYMBOL") 190 | 191 | 192 | def test_fmp_quote_aftermarket_trade(fmp_quote): 193 | aftermarket_trade = fmp_quote.aftermarket_trade("AAPL") 194 | assert isinstance(aftermarket_trade, AftermarketTrade) 195 | assert isinstance(aftermarket_trade.symbol, str) 196 | assert isinstance(aftermarket_trade.price, float) 197 | assert isinstance(aftermarket_trade.size, int) 198 | assert isinstance(aftermarket_trade.timestamp, str) 199 | 200 | 201 | def test_fmp_quote_aftermarket_trade_invalid_symbol(fmp_quote): 202 | with pytest.raises(ValueError): 203 | fmp_quote.aftermarket_trade("INVALID_SYMBOL") 204 | 205 | 206 | def test_fmp_quote_stock_price_change(fmp_quote): 207 | price_change = fmp_quote.stock_price_change("AAPL") 208 | assert isinstance(price_change, PriceChange) 209 | assert isinstance(price_change.symbol, str) 210 | assert isinstance(price_change.day_1, float) 211 | assert isinstance(price_change.day_5, float) 212 | assert isinstance(price_change.month_1, float) 213 | assert isinstance(price_change.month_3, float) 214 | assert isinstance(price_change.month_6, float) 215 | assert isinstance(price_change.ytd, float) 216 | assert isinstance(price_change.year_1, float) 217 | assert isinstance(price_change.year_3, float) 218 | assert isinstance(price_change.year_5, float) 219 | assert isinstance(price_change.year_10, float) 220 | assert isinstance(price_change.max, float) 221 | 222 | 223 | def test_fmp_quote_price_change_invalid_symbol(fmp_quote): 224 | with pytest.raises(ValueError): 225 | fmp_quote.stock_price_change("INVALID_SYMBOL") 226 | 227 | 228 | def test_fmp_quote_exchange_prices(fmp_quote): 229 | df = fmp_quote.exchange_prices("NASDAQ") 230 | assert isinstance(df, pd.DataFrame) 231 | assert "symbol" in df.columns 232 | assert "price" in df.columns 233 | assert "volume" in df.columns 234 | assert isinstance(df["symbol"][0], str) 235 | assert isinstance(df["price"][0], np.float64) 236 | assert isinstance(df["volume"][0], np.int64) 237 | assert isinstance(df["market_cap"][0], np.int64) 238 | assert isinstance(df["avg_volume"][0], np.int64) 239 | assert isinstance(df["shares_outstanding"][0], np.int64) 240 | assert isinstance(df["pe"][0], np.float64) 241 | assert isinstance(df["eps"][0], np.float64) 242 | assert isinstance(df["earnings_date"][0], pd.Timestamp) 243 | assert isinstance(df["datetime"][0], pd.Timestamp) 244 | assert isinstance(df["change"][0], np.float64) 245 | assert isinstance(df["change_percentage"][0], np.float64) 246 | assert isinstance(df["day_high"][0], np.float64) 247 | assert isinstance(df["day_low"][0], np.float64) 248 | assert isinstance(df["year_high"][0], np.float64) 249 | assert isinstance(df["year_low"][0], np.float64) 250 | assert isinstance(df["price_avg_50"][0], np.float64) 251 | assert isinstance(df["price_avg_200"][0], np.float64) 252 | assert isinstance(df["exchange"][0], str) 253 | assert isinstance(df["open"][0], np.float64) 254 | assert isinstance(df["previous_close"][0], np.float64) 255 | assert isinstance(df["name"][0], str) 256 | 257 | 258 | def test_fmp_quote_exchange_prices_invalid_exchange(fmp_quote): 259 | with pytest.raises(ValueError): 260 | fmp_quote.exchange_prices("INVALID_EXCHANGE") 261 | 262 | 263 | def test_fmp_quote_otc_quote(fmp_quote): 264 | quote = fmp_quote.otc_quote("AAPL") 265 | assert isinstance(quote, OtcQuote) 266 | assert quote.symbol == "AAPL" 267 | assert isinstance(quote.symbol, str) 268 | assert isinstance(quote.prev_close, float) 269 | assert isinstance(quote.volume, int) 270 | assert isinstance(quote.high, float) 271 | assert isinstance(quote.low, float) 272 | assert isinstance(quote.open, float) 273 | assert isinstance(quote.last_sale_price, float) 274 | assert isinstance(quote.fmp_last, float) 275 | assert isinstance(quote.last_updated, str) 276 | 277 | 278 | def test_fmp_quote_otc_quote_invalid_symbol(fmp_quote): 279 | with pytest.raises(ValueError): 280 | fmp_quote.otc_quote("INVALID_SYMBOL") 281 | 282 | 283 | def test_fmp_quote_simple_quote(fmp_quote): 284 | quote = fmp_quote.simple_quote("AAPL") 285 | assert isinstance(quote, SimpleQuote) 286 | assert quote.symbol == "AAPL" 287 | assert isinstance(quote.symbol, str) 288 | assert isinstance(quote.price, float) 289 | assert isinstance(quote.volume, int) 290 | 291 | 292 | def test_fmp_quote_simple_quote_invalid_symbol(fmp_quote): 293 | with pytest.raises(ValueError): 294 | fmp_quote.simple_quote("INVALID_SYMBOL") 295 | 296 | 297 | def test_fmp_quote_quote_order(fmp_quote): 298 | quote = fmp_quote.quote_order("AAPL") 299 | assert isinstance(quote, Quote) 300 | assert quote.symbol == "AAPL" 301 | assert isinstance(quote.name, str) 302 | assert isinstance(quote.price, float) 303 | assert isinstance(quote.change_percentage, float) 304 | assert isinstance(quote.change, float) 305 | assert isinstance(quote.day_low, float) 306 | assert isinstance(quote.day_high, float) 307 | assert isinstance(quote.year_low, float) 308 | assert isinstance(quote.year_high, float) 309 | assert isinstance(quote.market_cap, int) 310 | assert isinstance(quote.price_avg_50, float) 311 | assert isinstance(quote.price_avg_200, float) 312 | assert isinstance(quote.volume, int) 313 | assert isinstance(quote.avg_volume, int) 314 | assert isinstance(quote.exchange, str) 315 | assert isinstance(quote.open, float) 316 | assert isinstance(quote.previous_close, float) 317 | assert isinstance(quote.eps, float) 318 | assert isinstance(quote.pe, float) 319 | assert isinstance(quote.earnings_date, str) 320 | assert isinstance(quote.shares_outstanding, int) 321 | assert isinstance(quote.timestamp, str) 322 | 323 | 324 | def test_fmp_quote_quote_order_invalid_symbol(fmp_quote): 325 | with pytest.raises(ValueError): 326 | fmp_quote.quote_order("INVALID_SYMBOL") 327 | 328 | 329 | def test_fmp_quote_full_quote(fmp_quote): 330 | quote = fmp_quote.full_quote("AAPL") 331 | assert isinstance(quote, Quote) 332 | assert quote.symbol == "AAPL" 333 | assert isinstance(quote.name, str) 334 | assert isinstance(quote.price, float) 335 | assert isinstance(quote.change_percentage, float) 336 | assert isinstance(quote.change, float) 337 | assert isinstance(quote.day_low, float) 338 | assert isinstance(quote.day_high, float) 339 | assert isinstance(quote.year_low, float) 340 | assert isinstance(quote.year_high, float) 341 | assert isinstance(quote.market_cap, int) 342 | assert isinstance(quote.price_avg_50, float) 343 | assert isinstance(quote.price_avg_200, float) 344 | assert isinstance(quote.volume, int) 345 | assert isinstance(quote.avg_volume, int) 346 | assert isinstance(quote.exchange, str) 347 | assert isinstance(quote.open, float) 348 | assert isinstance(quote.previous_close, float) 349 | assert isinstance(quote.eps, float) 350 | assert isinstance(quote.pe, float) 351 | assert isinstance(quote.earnings_date, str) 352 | assert isinstance(quote.shares_outstanding, int) 353 | assert isinstance(quote.timestamp, str) 354 | 355 | 356 | def test_fmp_quote_full_quote_invalid_symbol(fmp_quote): 357 | with pytest.raises(ValueError): 358 | fmp_quote.full_quote("INVALID_SYMBOL") 359 | -------------------------------------------------------------------------------- /tests/test_fmp_splits.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import pytest 4 | 5 | from fmp_py.fmp_splits import FmpSplits 6 | 7 | 8 | @pytest.fixture 9 | def fmp_splits(): 10 | return FmpSplits() 11 | 12 | 13 | def test_fmp_splits_init(fmp_splits): 14 | assert isinstance(fmp_splits, FmpSplits) 15 | 16 | 17 | def test_fmp_splits_stock_splits_calendar(fmp_splits): 18 | result = fmp_splits.stock_splits_calendar("2023-01-01", "2023-01-31") 19 | assert isinstance(result, pd.DataFrame) 20 | assert "date" in result.columns 21 | assert "label" in result.columns 22 | assert "symbol" in result.columns 23 | assert "numerator" in result.columns 24 | assert "denominator" in result.columns 25 | assert isinstance(result["date"].iloc[0], pd.Timestamp) 26 | assert isinstance(result["label"].iloc[0], str) 27 | assert isinstance(result["symbol"].iloc[0], str) 28 | assert isinstance(result["numerator"].iloc[0], np.int64) 29 | assert isinstance(result["denominator"].iloc[0], np.int64) 30 | 31 | 32 | def test_fmp_splits_stock_splits_calendar_invalid_date_range(fmp_splits): 33 | with pytest.raises(ValueError): 34 | fmp_splits.stock_splits_calendar("2023-01-31", "2023-01-01") 35 | 36 | 37 | def test_fmp_splits_stock_splits_historical(fmp_splits): 38 | result = fmp_splits.stock_splits_historical("AAPL") 39 | assert isinstance(result, pd.DataFrame) 40 | assert "date" in result.columns 41 | assert "label" in result.columns 42 | assert "symbol" in result.columns 43 | assert "numerator" in result.columns 44 | assert "denominator" in result.columns 45 | assert isinstance(result["date"].iloc[0], pd.Timestamp) 46 | assert isinstance(result["label"].iloc[0], str) 47 | assert isinstance(result["symbol"].iloc[0], str) 48 | assert isinstance(result["numerator"].iloc[0], np.int64) 49 | assert isinstance(result["denominator"].iloc[0], np.int64) 50 | 51 | 52 | def test_fmp_splits_stock_splits_historical_invalid_symbol(fmp_splits): 53 | with pytest.raises(ValueError): 54 | fmp_splits.stock_splits_historical("INVALID_SYMBOL") 55 | -------------------------------------------------------------------------------- /tests/test_fmp_stock_list.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import pytest 4 | 5 | from fmp_py.fmp_stock_list import FmpStockList 6 | 7 | 8 | @pytest.fixture 9 | def fmp_stock_list(): 10 | return FmpStockList() 11 | 12 | 13 | def test_fmp_stock_list_init(fmp_stock_list): 14 | assert isinstance(fmp_stock_list, FmpStockList) 15 | 16 | 17 | def test_fmp_stock_list_available_indexes(fmp_stock_list): 18 | available_indexes = fmp_stock_list.available_indexes() 19 | assert isinstance(available_indexes, pd.DataFrame) 20 | assert isinstance(available_indexes.iloc[0]["symbol"], str) 21 | assert isinstance(available_indexes.iloc[0]["name"], str) 22 | assert isinstance(available_indexes.iloc[0]["currency"], str) 23 | assert isinstance(available_indexes.iloc[0]["stock_exchange"], str) 24 | assert isinstance(available_indexes.iloc[0]["exchange_short_name"], str) 25 | 26 | 27 | def test_fmp_stock_list_available_indexes_no_data(fmp_stock_list): 28 | with pytest.raises(ValueError): 29 | fmp_stock_list.available_indexes() == [] 30 | 31 | 32 | def test_fmp_stock_list_exchange_symbols(fmp_stock_list): 33 | exchange_symbols = fmp_stock_list.exchange_symbols("NASDAQ") 34 | assert isinstance(exchange_symbols, pd.DataFrame) 35 | assert isinstance(exchange_symbols.iloc[0]["symbol"], str) 36 | assert isinstance(exchange_symbols.iloc[0]["name"], str) 37 | assert isinstance(exchange_symbols.iloc[0]["exchange"], str) 38 | assert isinstance(exchange_symbols.iloc[0]["price"], np.float64) 39 | assert isinstance(exchange_symbols.iloc[0]["change_percentage"], np.float64) 40 | assert isinstance(exchange_symbols.iloc[0]["change"], np.float64) 41 | assert isinstance(exchange_symbols.iloc[0]["day_low"], np.float64) 42 | assert isinstance(exchange_symbols.iloc[0]["day_high"], np.float64) 43 | assert isinstance(exchange_symbols.iloc[0]["year_high"], np.float64) 44 | assert isinstance(exchange_symbols.iloc[0]["year_low"], np.float64) 45 | assert isinstance(exchange_symbols.iloc[0]["market_cap"], np.int64) 46 | assert isinstance(exchange_symbols.iloc[0]["price_avg_50"], np.float64) 47 | assert isinstance(exchange_symbols.iloc[0]["price_avg_200"], np.float64) 48 | assert isinstance(exchange_symbols.iloc[0]["volume"], np.int64) 49 | assert isinstance(exchange_symbols.iloc[0]["avg_volume"], np.int64) 50 | assert isinstance(exchange_symbols.iloc[0]["open"], np.float64) 51 | assert isinstance(exchange_symbols.iloc[0]["previous_close"], np.float64) 52 | assert isinstance(exchange_symbols.iloc[0]["eps"], np.float64) 53 | assert isinstance(exchange_symbols.iloc[0]["pe"], np.float64) 54 | assert isinstance(exchange_symbols.iloc[0]["earnings_announcement"], pd.Timestamp) 55 | assert isinstance(exchange_symbols.iloc[0]["shares_outstanding"], np.float64) 56 | assert isinstance(exchange_symbols.iloc[0]["datetime"], pd.Timestamp) 57 | 58 | 59 | def test_fmp_stock_list_exchange_symbols_no_data(fmp_stock_list): 60 | with pytest.raises(ValueError): 61 | fmp_stock_list.exchange_symbols("NASDAQ") == [] 62 | 63 | 64 | def test_fmp_stock_list_symbol_changes(fmp_stock_list): 65 | symbol_changes = fmp_stock_list.symbol_changes() 66 | assert isinstance(symbol_changes, pd.DataFrame) 67 | assert isinstance(symbol_changes.iloc[0]["date"], pd.Timestamp) 68 | assert isinstance(symbol_changes.iloc[0]["name"], str) 69 | assert isinstance(symbol_changes.iloc[0]["old_symbol"], str) 70 | assert isinstance(symbol_changes.iloc[0]["new_symbol"], str) 71 | 72 | 73 | def test_fmp_stock_list_symbol_changes_no_data(fmp_stock_list): 74 | with pytest.raises(ValueError): 75 | fmp_stock_list.symbol_changes() == [] 76 | 77 | 78 | def test_fmp_stock_list_euronext_symbols(fmp_stock_list): 79 | euronext_symbols = fmp_stock_list.euronext_symbols() 80 | assert isinstance(euronext_symbols, pd.DataFrame) 81 | assert isinstance(euronext_symbols.iloc[0]["symbol"], str) 82 | assert isinstance(euronext_symbols.iloc[0]["name"], str) 83 | assert isinstance(euronext_symbols.iloc[0]["currency"], str) 84 | assert isinstance(euronext_symbols.iloc[0]["stock_exchange"], str) 85 | assert isinstance(euronext_symbols.iloc[0]["exchange_short_name"], str) 86 | 87 | 88 | def test_fmp_stock_list_euronext_symbols_no_data(fmp_stock_list): 89 | with pytest.raises(ValueError): 90 | fmp_stock_list.euronext_symbols() == [] 91 | 92 | 93 | def test_fmp_stock_list_cik_list(fmp_stock_list): 94 | cik_list = fmp_stock_list.cik_list() 95 | assert isinstance(cik_list, pd.DataFrame) 96 | assert isinstance(cik_list.iloc[0]["cik"], str) 97 | assert isinstance(cik_list.iloc[0]["name"], str) 98 | 99 | 100 | def test_fmp_stock_list_cik_list_no_data(fmp_stock_list): 101 | with pytest.raises(ValueError): 102 | fmp_stock_list.cik_list() == [] 103 | 104 | 105 | def test_fmp_stock_list_commitment_of_traders_report(fmp_stock_list): 106 | cot_list = fmp_stock_list.commitment_of_traders_report() 107 | assert isinstance(cot_list, pd.DataFrame) 108 | assert isinstance(cot_list.iloc[0]["trading_symbol"], str) 109 | assert isinstance(cot_list.iloc[0]["short_name"], str) 110 | 111 | 112 | def test_fmp_stock_list_commitment_of_traders_report_no_data(fmp_stock_list): 113 | with pytest.raises(ValueError): 114 | fmp_stock_list.commitment_of_traders_report() == [] 115 | 116 | 117 | def test_fmp_stock_list_tradable_stocks_search(fmp_stock_list): 118 | tradable_stocks_list = fmp_stock_list.tradable_stocks_search() 119 | assert isinstance(tradable_stocks_list, pd.DataFrame) 120 | assert isinstance(tradable_stocks_list.iloc[0]["symbol"], str) 121 | assert isinstance(tradable_stocks_list.iloc[0]["name"], str) 122 | assert isinstance(tradable_stocks_list.iloc[0]["price"], float) 123 | assert isinstance(tradable_stocks_list.iloc[0]["exchange"], str) 124 | assert isinstance(tradable_stocks_list.iloc[0]["exchange_short_name"], str) 125 | 126 | 127 | def test_fmp_stock_list_tradable_stocks_search_no_data(fmp_stock_list): 128 | with pytest.raises(ValueError): 129 | fmp_stock_list.tradable_stocks_search() == [] 130 | 131 | 132 | def test_fmp_stock_list_statement_symbols_list(fmp_stock_list): 133 | statement_symbols_list = fmp_stock_list.statement_symbols_list() 134 | assert isinstance(statement_symbols_list, list) 135 | assert isinstance(statement_symbols_list[0], str) 136 | 137 | 138 | def test_fmp_stock_list_exchange_traded_fund_search(fmp_stock_list): 139 | etf_list = fmp_stock_list.exchange_traded_fund_search() 140 | assert isinstance(etf_list, pd.DataFrame) 141 | assert isinstance(etf_list.iloc[0]["symbol"], str) 142 | assert isinstance(etf_list.iloc[0]["name"], str) 143 | assert isinstance(etf_list.iloc[0]["price"], float) 144 | assert isinstance(etf_list.iloc[0]["exchange"], str) 145 | assert isinstance(etf_list.iloc[0]["exchange_short_name"], str) 146 | assert isinstance(etf_list.iloc[0]["type"], str) 147 | 148 | 149 | def test_fmp_stock_list_exchange_traded_fund_search_no_data(fmp_stock_list): 150 | with pytest.raises(ValueError): 151 | fmp_stock_list.exchange_traded_fund_search() == [] 152 | 153 | 154 | def test_fmp_stock_list_stock_list(fmp_stock_list): 155 | stock_list = fmp_stock_list.stock_list() 156 | assert isinstance(stock_list, pd.DataFrame) 157 | assert isinstance(stock_list.iloc[0]["symbol"], str) 158 | assert isinstance(stock_list.iloc[0]["name"], str) 159 | assert isinstance(stock_list.iloc[0]["exchange"], str) 160 | assert isinstance(stock_list.iloc[0]["price"], float) 161 | assert isinstance(stock_list.iloc[0]["exchange_short_name"], str) 162 | assert isinstance(stock_list.iloc[0]["type"], str) 163 | 164 | 165 | def test_fmp_stock_list_stock_list_no_data(fmp_stock_list): 166 | with pytest.raises(ValueError): 167 | fmp_stock_list.stock_list() == [] 168 | -------------------------------------------------------------------------------- /tests/test_fmp_upgrades_downgrades.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import pytest 3 | 4 | from fmp_py.fmp_upgrades_downgrades import FMPUpgradesDowngrades 5 | from fmp_py.models.upgrades_downgrades import UpgradesDowngrades 6 | 7 | 8 | @pytest.fixture 9 | def fmp_upgrades_downgrades(): 10 | return FMPUpgradesDowngrades() 11 | 12 | 13 | def test_fmp_upgrades_downgrades_init(fmp_upgrades_downgrades): 14 | assert isinstance(fmp_upgrades_downgrades, FMPUpgradesDowngrades) 15 | 16 | 17 | def test_fmp_upgrades_downgrades_consensus(fmp_upgrades_downgrades): 18 | upgrades_downgrades = fmp_upgrades_downgrades.upgrades_downgrades_consensus("AAPL") 19 | assert isinstance(upgrades_downgrades, UpgradesDowngrades) 20 | assert isinstance(upgrades_downgrades.symbol, str) 21 | assert isinstance(upgrades_downgrades.strong_buy, int) 22 | assert isinstance(upgrades_downgrades.buy, int) 23 | assert isinstance(upgrades_downgrades.hold, int) 24 | assert isinstance(upgrades_downgrades.sell, int) 25 | assert isinstance(upgrades_downgrades.strong_sell, int) 26 | assert isinstance(upgrades_downgrades.consensus, str) 27 | 28 | 29 | def test_fmp_upgrades_downgrades_consensus_no_data(fmp_upgrades_downgrades): 30 | with pytest.raises(ValueError): 31 | fmp_upgrades_downgrades.upgrades_downgrades_consensus("AAPL1234567890") 32 | 33 | 34 | def test_fmp_upgrades_downgrades_upgrades_downgrades_by_company( 35 | fmp_upgrades_downgrades, 36 | ): 37 | upgrades_downgrades = fmp_upgrades_downgrades.upgrades_downgrades_by_company( 38 | "Barclays" 39 | ) 40 | assert isinstance(upgrades_downgrades, pd.DataFrame) 41 | assert len(upgrades_downgrades) > 0 42 | assert "symbol" in upgrades_downgrades.columns 43 | assert isinstance(upgrades_downgrades["symbol"][0], str) 44 | assert isinstance(upgrades_downgrades["published_date"][0], pd.Timestamp) 45 | assert isinstance(upgrades_downgrades["news_url"][0], str) 46 | assert isinstance(upgrades_downgrades["news_title"][0], str) 47 | assert isinstance(upgrades_downgrades["news_base_url"][0], str) 48 | assert isinstance(upgrades_downgrades["news_publisher"][0], str) 49 | assert isinstance(upgrades_downgrades["new_grade"][0], str) 50 | assert isinstance(upgrades_downgrades["previous_grade"][0], str) 51 | assert isinstance(upgrades_downgrades["grading_company"][0], str) 52 | assert isinstance(upgrades_downgrades["action"][0], str) 53 | assert isinstance(upgrades_downgrades["price_when_posted"][0], float) 54 | 55 | 56 | def test_fmp_upgrades_downgrades_upgrades_downgrades_by_company_no_data( 57 | fmp_upgrades_downgrades, 58 | ): 59 | with pytest.raises(ValueError): 60 | fmp_upgrades_downgrades.upgrades_downgrades_by_company("AAPL1234567890") 61 | 62 | 63 | def test_fmp_upgrades_downgrades_upgrades_downgrades(fmp_upgrades_downgrades): 64 | upgrades_downgrades = fmp_upgrades_downgrades.upgrades_downgrades("AAPL") 65 | assert isinstance(upgrades_downgrades, pd.DataFrame) 66 | assert len(upgrades_downgrades) > 0 67 | assert "symbol" in upgrades_downgrades.columns 68 | assert isinstance(upgrades_downgrades["symbol"][0], str) 69 | assert isinstance(upgrades_downgrades["published_date"][0], pd.Timestamp) 70 | assert isinstance(upgrades_downgrades["news_url"][0], str) 71 | assert isinstance(upgrades_downgrades["news_title"][0], str) 72 | assert isinstance(upgrades_downgrades["news_base_url"][0], str) 73 | assert isinstance(upgrades_downgrades["news_publisher"][0], str) 74 | assert isinstance(upgrades_downgrades["new_grade"][0], str) 75 | assert isinstance(upgrades_downgrades["previous_grade"][0], str) 76 | assert isinstance(upgrades_downgrades["grading_company"][0], str) 77 | assert isinstance(upgrades_downgrades["action"][0], str) 78 | assert isinstance(upgrades_downgrades["price_when_posted"][0], float) 79 | 80 | 81 | def test_fmp_upgrades_downgrades_upgrades_downgrades_no_data(fmp_upgrades_downgrades): 82 | with pytest.raises(ValueError): 83 | fmp_upgrades_downgrades.upgrades_downgrades("AAPL1234567890") 84 | 85 | 86 | def test_fmp_upgrades_downgrades_upgrades_downgrades_rss_feed(fmp_upgrades_downgrades): 87 | upgrades_downgrades = fmp_upgrades_downgrades.upgrades_downgrades_rss_feed("AAPL") 88 | assert isinstance(upgrades_downgrades, pd.DataFrame) 89 | assert len(upgrades_downgrades) > 0 90 | assert "symbol" in upgrades_downgrades.columns 91 | assert isinstance(upgrades_downgrades["symbol"][0], str) 92 | assert isinstance(upgrades_downgrades["published_date"][0], pd.Timestamp) 93 | assert isinstance(upgrades_downgrades["news_url"][0], str) 94 | assert isinstance(upgrades_downgrades["news_title"][0], str) 95 | assert isinstance(upgrades_downgrades["news_base_url"][0], str) 96 | assert isinstance(upgrades_downgrades["news_publisher"][0], str) 97 | assert isinstance(upgrades_downgrades["new_grade"][0], str) 98 | assert isinstance(upgrades_downgrades["previous_grade"][0], str) 99 | assert isinstance(upgrades_downgrades["grading_company"][0], str) 100 | assert isinstance(upgrades_downgrades["action"][0], str) 101 | assert isinstance(upgrades_downgrades["price_when_posted"][0], float) 102 | -------------------------------------------------------------------------------- /tests/test_fmp_valuation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import pytest 4 | 5 | from fmp_py.fmp_valuation import FmpValuation 6 | from fmp_py.models.valuation import CompanyRating, DiscountedCashFlow 7 | 8 | 9 | @pytest.fixture 10 | def fmp_valuation(): 11 | return FmpValuation() 12 | 13 | 14 | def test_fmp_valuation_init(fmp_valuation): 15 | assert isinstance(fmp_valuation, FmpValuation) 16 | 17 | 18 | def test_fmp_valuation_historical_rating(fmp_valuation): 19 | historical_rating = fmp_valuation.historical_rating("AAPL") 20 | assert isinstance(historical_rating, pd.DataFrame) 21 | assert isinstance(historical_rating["date"][0], pd.Timestamp) 22 | assert isinstance(historical_rating["symbol"][0], str) 23 | assert isinstance(historical_rating["rating"][0], str) 24 | assert isinstance(historical_rating["rating_score"][0], np.int64) 25 | assert isinstance(historical_rating["rating_recommendation"][0], str) 26 | assert isinstance(historical_rating["rating_details_dcf_recommendation"][0], str) 27 | assert isinstance(historical_rating["rating_details_roe_score"][0], np.int64) 28 | assert isinstance(historical_rating["rating_details_roe_recommendation"][0], str) 29 | assert isinstance(historical_rating["rating_details_roa_score"][0], np.int64) 30 | assert isinstance(historical_rating["rating_details_roa_recommendation"][0], str) 31 | assert isinstance(historical_rating["rating_details_de_score"][0], np.int64) 32 | assert isinstance(historical_rating["rating_details_de_recommendation"][0], str) 33 | assert isinstance(historical_rating["rating_details_pe_score"][0], np.int64) 34 | assert isinstance(historical_rating["rating_details_pe_recommendation"][0], str) 35 | assert isinstance(historical_rating["rating_details_pb_score"][0], np.int64) 36 | 37 | 38 | def test_fmp_valuation_historical_rating_invalid_symbol(fmp_valuation): 39 | with pytest.raises(ValueError): 40 | fmp_valuation.historical_rating("INVALID_SYMBOL") 41 | 42 | 43 | def test_fmp_valuation_levered_dcf(fmp_valuation): 44 | valuation = fmp_valuation.levered_dcf("AAPL") 45 | assert isinstance(valuation, pd.DataFrame) 46 | assert isinstance(valuation["year"][0], np.int64) 47 | assert isinstance(valuation["symbol"][0], str) 48 | assert isinstance(valuation["revenue"][0], np.int64) 49 | assert isinstance(valuation["revenue_percentage"][0], np.float64) 50 | assert isinstance(valuation["capital_expenditure"][0], np.int64) 51 | assert isinstance(valuation["price"][0], np.float64) 52 | assert isinstance(valuation["beta"][0], np.float64) 53 | assert isinstance(valuation["diluted_shares_outstanding"][0], np.int64) 54 | assert isinstance(valuation["cost_of_debt"][0], np.float64) 55 | assert isinstance(valuation["tax_rate"][0], np.float64) 56 | assert isinstance(valuation["after_tax_cost_of_debt"][0], np.float64) 57 | assert isinstance(valuation["risk_free_rate"][0], np.float64) 58 | assert isinstance(valuation["market_risk_premium"][0], np.float64) 59 | assert isinstance(valuation["cost_of_equity"][0], np.float64) 60 | assert isinstance(valuation["total_debt"][0], np.int64) 61 | assert isinstance(valuation["total_equity"][0], np.int64) 62 | assert isinstance(valuation["total_capital"][0], np.int64) 63 | assert isinstance(valuation["debt_weighting"][0], np.float64) 64 | 65 | assert isinstance(valuation["equity_weighting"][0], np.float64) 66 | assert isinstance(valuation["wacc"][0], np.float64) 67 | assert isinstance(valuation["operating_cash_flow"][0], np.int64) 68 | assert isinstance(valuation["pv_lfcf"][0], np.int64) 69 | assert isinstance(valuation["sum_pv_lfcf"][0], np.int64) 70 | assert isinstance(valuation["long_term_growth_rate"][0], np.float64) 71 | assert isinstance(valuation["free_cash_flow"][0], np.int64) 72 | assert isinstance(valuation["terminal_value"][0], np.int64) 73 | assert isinstance(valuation["present_terminal_value"][0], np.int64) 74 | assert isinstance(valuation["enterprise_value"][0], np.int64) 75 | assert isinstance(valuation["net_debt"][0], np.int64) 76 | assert isinstance(valuation["equity_value"][0], np.int64) 77 | assert isinstance(valuation["equity_value_per_share"][0], np.float64) 78 | assert isinstance(valuation["free_cash_flow_t1"][0], np.int64) 79 | assert isinstance(valuation["operating_cash_flow_percentage"][0], np.float64) 80 | 81 | 82 | def test_fmp_valuation_levered_dcf_invalid_symbol(fmp_valuation): 83 | with pytest.raises(ValueError): 84 | fmp_valuation.levered_dcf("INVALID_SYMBOL") 85 | 86 | 87 | def test_fmp_valuation_advanced_dcf(fmp_valuation): 88 | adcf = fmp_valuation.advanced_dcf("AAPL") 89 | assert isinstance(adcf, pd.DataFrame) 90 | assert isinstance(adcf["year"][0], np.int64) 91 | assert isinstance(adcf["symbol"][0], str) 92 | assert isinstance(adcf["revenue"][0], np.int64) 93 | assert isinstance(adcf["revenue_percentage"][0], np.float64) 94 | 95 | assert isinstance(adcf["ebitda"][0], np.int64) 96 | assert isinstance(adcf["ebitda_percentage"][0], np.float64) 97 | assert isinstance(adcf["ebit"][0], np.int64) 98 | assert isinstance(adcf["depreciation"][0], np.int64) 99 | assert isinstance(adcf["depreciation_percentage"][0], np.float64) 100 | assert isinstance(adcf["total_cash"][0], np.int64) 101 | assert isinstance(adcf["total_cash_percentage"][0], np.float64) 102 | assert isinstance(adcf["receivables"][0], np.int64) 103 | assert isinstance(adcf["receivables_percentage"][0], np.float64) 104 | assert isinstance(adcf["inventories"][0], np.int64) 105 | 106 | assert isinstance(adcf["inventories_percentage"][0], np.float64) 107 | assert isinstance(adcf["payable"][0], np.int64) 108 | assert isinstance(adcf["payable_percentage"][0], np.float64) 109 | assert isinstance(adcf["capital_expenditure"][0], np.int64) 110 | assert isinstance(adcf["capital_expenditure_percentage"][0], np.float64) 111 | assert isinstance(adcf["price"][0], np.float64) 112 | assert isinstance(adcf["beta"][0], np.float64) 113 | assert isinstance(adcf["diluted_shares_outstanding"][0], np.int64) 114 | assert isinstance(adcf["cost_of_debt"][0], np.float64) 115 | assert isinstance(adcf["tax_rate"][0], np.float64) 116 | 117 | assert isinstance(adcf["after_tax_cost_of_debt"][0], np.float64) 118 | assert isinstance(adcf["risk_free_rate"][0], np.float64) 119 | assert isinstance(adcf["market_risk_premium"][0], np.float64) 120 | assert isinstance(adcf["cost_of_equity"][0], np.float64) 121 | assert isinstance(adcf["total_debt"][0], np.int64) 122 | assert isinstance(adcf["total_equity"][0], np.int64) 123 | assert isinstance(adcf["total_capital"][0], np.int64) 124 | assert isinstance(adcf["debt_weighting"][0], np.float64) 125 | assert isinstance(adcf["equity_weighting"][0], np.float64) 126 | assert isinstance(adcf["wacc"][0], np.float64) 127 | 128 | assert isinstance(adcf["tax_rate_cash"][0], np.int64) 129 | assert isinstance(adcf["ebiat"][0], np.int64) 130 | assert isinstance(adcf["ufcf"][0], np.int64) 131 | assert isinstance(adcf["sum_pv_ufcf"][0], np.int64) 132 | assert isinstance(adcf["long_term_growth_rate"][0], np.float64) 133 | assert isinstance(adcf["terminal_value"][0], np.int64) 134 | assert isinstance(adcf["present_terminal_value"][0], np.int64) 135 | assert isinstance(adcf["enterprise_value"][0], np.int64) 136 | assert isinstance(adcf["net_debt"][0], np.int64) 137 | assert isinstance(adcf["equity_value"][0], np.int64) 138 | 139 | assert isinstance(adcf["equity_value_per_share"][0], np.float64) 140 | assert isinstance(adcf["free_cash_flow_t1"][0], np.int64) 141 | 142 | 143 | def test_fmp_valuation_advanced_dcf_invalid_symbol(fmp_valuation): 144 | with pytest.raises(ValueError): 145 | fmp_valuation.advanced_dcf("INVALID_SYMBOL") 146 | 147 | 148 | def test_fmp_valuation_discounted_cash_flow(fmp_valuation): 149 | valuation = fmp_valuation.discounted_cash_flow("AAPL") 150 | assert isinstance(valuation, DiscountedCashFlow) 151 | assert valuation.symbol == "AAPL" 152 | assert isinstance(valuation.date, str) 153 | assert isinstance(valuation.dcf, float) 154 | assert isinstance(valuation.stock_price, float) 155 | 156 | 157 | def test_fmp_valuation_discounted_cash_flow_invalid_symbol(fmp_valuation): 158 | with pytest.raises(ValueError): 159 | fmp_valuation.discounted_cash_flow("INVALID_SYMBOL") 160 | 161 | 162 | def test_fmp_valuation_company_rating(fmp_valuation): 163 | valuation = fmp_valuation.company_rating("AAPL") 164 | assert isinstance(valuation, CompanyRating) 165 | assert valuation.symbol == "AAPL" 166 | assert isinstance(valuation.date, str) 167 | assert isinstance(valuation.rating, str) 168 | assert isinstance(valuation.rating_score, int) 169 | assert isinstance(valuation.rating_recommendation, str) 170 | assert isinstance(valuation.rating_details_dcf_score, int) 171 | assert isinstance(valuation.rating_details_dcf_recommendation, str) 172 | assert isinstance(valuation.rating_details_roe_score, int) 173 | assert isinstance(valuation.rating_details_roe_recommendation, str) 174 | assert isinstance(valuation.rating_details_roa_score, int) 175 | assert isinstance(valuation.rating_details_roa_recommendation, str) 176 | assert isinstance(valuation.rating_details_de_score, int) 177 | assert isinstance(valuation.rating_details_de_recommendation, str) 178 | assert isinstance(valuation.rating_details_pe_score, int) 179 | assert isinstance(valuation.rating_details_pe_recommendation, str) 180 | assert isinstance(valuation.rating_details_pb_score, int) 181 | assert isinstance(valuation.rating_details_pb_recommendation, str) 182 | 183 | 184 | def test_fmp_valuation_company_rating_invalid_symbol(fmp_valuation): 185 | with pytest.raises(ValueError): 186 | fmp_valuation.company_rating("INVALID_SYMBOL") 187 | --------------------------------------------------------------------------------