mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-05 08:51:20 +00:00
Merge branch 'develop' into feature/fixed_income
This commit is contained in:
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
@@ -19,10 +19,11 @@
|
||||
|
||||
# Checklist:
|
||||
|
||||
- [ ] Update [our Hugo documentation](https://openbb-finance.github.io/OpenBBTerminal/) following [these guidelines](https://github.com/OpenBB-finance/OpenBBTerminal/tree/main/website).
|
||||
- [ ] I have adhered to the GitFlow naming convention and my branch name is in the format of `feature/feature-name` or `hotfix/hotfix-name`.
|
||||
- [ ] Update [our documentation](https://openbb-finance.github.io/OpenBBTerminal/) following [these guidelines](https://github.com/OpenBB-finance/OpenBBTerminal/tree/main/website). Update any user guides that are affected by the changes.
|
||||
- [ ] Update our tests following [these guidelines](https://github.com/OpenBB-finance/OpenBBTerminal/tree/main/tests).
|
||||
- [ ] Make sure you are following our [CONTRIBUTING guidelines](https://github.com/OpenBB-finance/OpenBBTerminal/blob/main/CONTRIBUTING.md).
|
||||
- [ ] If a feature was added make sure to add it to the corresponding [scripts file](https://github.com/OpenBB-finance/OpenBBTerminal/tree/main/scripts).
|
||||
- [ ] If a feature was added make sure to add it to the corresponding [integration test script](https://github.com/OpenBB-finance/OpenBBTerminal/tree/develop/openbb_terminal/miscellaneous/integration_tests_scripts).
|
||||
|
||||
|
||||
# Others
|
||||
|
||||
244
.github/workflows/README.md
vendored
Normal file
244
.github/workflows/README.md
vendored
Normal file
@@ -0,0 +1,244 @@
|
||||
# OpenBB Workflows
|
||||
This directory contains the workflows for the OpenBB 🦋 Project. The workflows are:
|
||||
|
||||
| Workflows | Summary
|
||||
| :-------------------- |:---
|
||||
| branch-name-check.yml | Checks if the branch name is valid and follows the naming convention.
|
||||
| build-release.yml | Builds the project and runs the tests.
|
||||
| docker.yml | Builds the docker image and pushes it to the docker hub.
|
||||
| draft.yml | Creates a draft release when a new tag is pushed.
|
||||
| gh-pages.yml | Builds the documentation and deploy to github pages.
|
||||
| integration-test.yml | Runs the integration tests.
|
||||
| intel_macos_build.yml | Builds the project on Intel Macs.
|
||||
| issue.yml | Creates an issue when a new bug is reported.
|
||||
| labels.yml | Adds labels to the issues and pull requests.
|
||||
| linting.yml | Runs the linters.
|
||||
| m1_macos_build.yml | Builds the project on M1 Macs.
|
||||
| mac.yml | Builds the project on Mac OS X Full Clean Build with ML.
|
||||
| nightly-build.yml | Builds the project and runs the integration tests every night on the `develop` branch.
|
||||
| pypi.yml | Publishes the package to PyPI.
|
||||
| unit-test.yml | Runs the unit tests.
|
||||
| windows.yml | Builds the project on Windows 10 Full Clean Build with ML.
|
||||
| windows10_build.yml | Builds the project on Windows 10.
|
||||
|
||||
## Branch Name Check Workflow
|
||||
Objective: To check if pull request branch names follow the GitFlow naming convention before merging.
|
||||
|
||||
Triggered by: A pull request event where the target branch is either develop or main.
|
||||
|
||||
Branches checked: The source branch of a pull request and the target branch of a pull request.
|
||||
|
||||
Steps:
|
||||
|
||||
1. Extract branch names: Using the jq tool, the source and target branch names are extracted from the pull request event. The branch names are then stored in environment variables and printed as output.
|
||||
|
||||
2. Show Output result for source-branch and target-branch: The source and target branch names are printed to the console.
|
||||
|
||||
3. Check branch name for develop PRs: If the target branch is develop, then the source branch is checked against a regular expression to ensure that it follows the GitFlow naming convention. If the branch name is invalid, a message is printed to the console and the workflow exits with a status code of 1.
|
||||
|
||||
4. Check branch name for main PRs: If the target branch is main, then the source branch is checked against a regular expression to ensure that it is either a hotfix or a release branch. If the branch name is invalid, a message is printed to the console and the workflow exits with a status code of 1.
|
||||
|
||||
Note: The GitFlow naming convention for branches is as follows:
|
||||
|
||||
- Feature branches: feature/<feature-name>
|
||||
- Hotfix branches: hotfix/<hotfix-name>
|
||||
- Release branches: release/<major.minor.patch>(rc<number>)
|
||||
|
||||
## Build Release Workflow
|
||||
This GitHub Actions workflow is responsible for building and releasing software for multiple platforms (Windows, M1 MacOS, Intel MacOS, and Docker).
|
||||
The workflow has four jobs:
|
||||
|
||||
- `trigger-windows-build`
|
||||
- `trigger-macos-build`
|
||||
- `trigger-intel-build`
|
||||
- `trigger-docker-build`
|
||||
|
||||
Each job uses the `aurelien-baudet/workflow-dispatch` action to trigger another workflow, respectively `windows10_build.yml`, `m1_macos_build.yml`, `intel_macos_build.yml`, and `docker.yml`. The `GITHUB_TOKEN` is passed as a secret so that the triggered workflows have access to the necessary permissions. The `wait-for-completion-timeout` is set to 2 hours, which is the maximum amount of time the job will wait for the triggered workflow to complete.
|
||||
|
||||
## Docker Workflow
|
||||
This GitHub Actions workflow is responsible for building and pushing the docker image to the itHub Container Registry. This workflow is triggered when a new change is pushed to the main branch of the repository, and the Docker image is published to the GitHub Container Registry.
|
||||
|
||||
Steps
|
||||
-----
|
||||
|
||||
1. Checkout Code: This step checks out the code from the GitHub repository.
|
||||
|
||||
2. Login to GitHub Container Registry: This step logs into the GitHub Container Registry using the GitHub Actions token.
|
||||
|
||||
3. Setup Commit Hash: This step sets the commit hash of the code that is being built.
|
||||
|
||||
4. Build Env File: This step builds the environment file for the Docker image.
|
||||
|
||||
5. Building the Docker Image: This step builds the Docker image using the scripts in the `build/docker` directory.
|
||||
|
||||
6. Publishing the Docker Image: This step publishes the Docker image to the GitHub Container Registry. The Docker image is only pushed to the registry if the branch being built is `main`.
|
||||
|
||||
## Draft Workflow
|
||||
This GitHub Actions workflow is designed to automatically generate and update draft releases in a GitHub repository. The workflow is triggered when it is manually dispatched, allowing you to control when the draft releases are updated.
|
||||
|
||||
## GH Pages Workflow
|
||||
This GitHub Actions workflow is responsible for building the documentation and deploying it to GitHub Pages. This workflow is triggered when a new change is pushed to the `main` or `release` branch of the repository, and the documentation is published to GitHub Pages.
|
||||
|
||||
## Integration Test Workflow
|
||||
This GitHub Action is used to run integration tests on your code repository. It is triggered on pushes to the `release/*` or `main` branches, and it runs on the latest version of Ubuntu.
|
||||
|
||||
The workflow consists of the following steps:
|
||||
|
||||
1. Check out the code from the repository
|
||||
2. Set up Python 3.9
|
||||
3. Install Poetry, a package and dependency manager for Python
|
||||
4. Load a cached virtual environment created by Poetry, to speed up the process if possible
|
||||
5. Install dependencies specified in the `poetry.lock` file
|
||||
6. Run the integration tests using the `terminal.py` script
|
||||
7. Upload a summary of the test results to Slack
|
||||
|
||||
The results of the tests are captured in a file called `result.txt`. The summary of the tests, including information about failed tests, is then uploaded to Slack using the `adrey/slack-file-upload-action` GitHub Action.
|
||||
|
||||
## Intel MacOS Build Workflow
|
||||
This GitHub Actions workflow is responsible for building and testing the project on Intel Macs. The workflow is triggered by the workflow_dispatch event, which means it can be manually triggered. The concurrency group for the job is defined based on the Github workflow and the Github ref, and it is set to cancel in-progress runs.
|
||||
|
||||
The job named `Build` has several steps:
|
||||
|
||||
1. Checkout the repository's main branch.
|
||||
2. Git log to see the history of the repository.
|
||||
3. Install `create-dmg` using Homebrew.
|
||||
4. Clean up previous environment variables and set PATH to default.
|
||||
5. Set up conda environment caching.
|
||||
6. Set up Miniconda using the `build/conda/conda-3-9-env-full.yaml` file.
|
||||
7. Use Poetry to install the necessary dependencies.
|
||||
8. Install Pyinstaller.
|
||||
9. Install Portfolio Optimization and Forecasting Toolkits.
|
||||
10. Install a specific version of Papermill using pip.
|
||||
11. Build the bundle using Pyinstaller and do some additional tasks such as setting up icons.
|
||||
12. Create a keychain and decode a certificate using a password stored in Github secrets.
|
||||
13. Code sign specific binaries using the certificate and password from the previous step.
|
||||
|
||||
This workflow builds the OpenBB Terminal application on MacOS and signs it with a certificate to allow it to run on the MacOS system.
|
||||
|
||||
## Linting Workflow
|
||||
This GitHub Actions workflow is responsible for running linting checks on the codebase. This workflow is triggered on pull request events such as `opened`, `synchronize`, and `edited`, and push events on branches with names that start with `feature/`, `hotfix/`, or `release/`. The workflow also sets a number of environment variables and uses Github Actions caching to improve performance.
|
||||
|
||||
It consists of two jobs: `code-linting` and `markdown-link-check`.
|
||||
|
||||
The first job, `code-linting`, runs on an Ubuntu machine and performs several linting tasks on the code in the repository, including:
|
||||
|
||||
- Checking out the code from the repository
|
||||
- Setting up Python 3.9
|
||||
- Installing a number of Python packages necessary for the linting tasks
|
||||
- Running `bandit` to check for security vulnerabilities
|
||||
- Running `black` to check the code formatting
|
||||
- Running `codespell` to check the spelling of comments, strings, and variable names
|
||||
- Running `ruff` to check the use of Python
|
||||
- Running `mypy` to check the type annotations
|
||||
- Running `pyupgrade` to upgrade Python 2 code to Python 3
|
||||
- Running `pylint` to perform static analysis of the code
|
||||
|
||||
The second job, `markdown-link-check`, runs on an Ubuntu machine and performs linting of the markdown files in the repository. It uses a Docker container `avtodev/markdown-lint` to perform the linting.
|
||||
|
||||
## MacOS Build Workflow
|
||||
This GitHub Actions workflow is used to build a version of the OpenBB Terminal for M1 MacOS. The build process includes installing necessary dependencies, building the terminal application using PyInstaller, creating a DMG file for distribution, and running integration tests on the built application.
|
||||
|
||||
Jobs
|
||||
----
|
||||
|
||||
The workflow consists of a single job named `Build` which runs on self-hosted MacOS systems with ARM64 architecture. The job performs the following steps:
|
||||
|
||||
1. Checkout: The main branch of the repository is checked out, allowing for the commit hashes to line up.
|
||||
2. Git Log: The log of the Git repository is displayed.
|
||||
3. Install create-dmg: The `create-dmg` tool is installed using Homebrew.
|
||||
4. Clean Previous Path: The previous PATH environment variable is cleared and restored to its default values.
|
||||
5. Setup Conda Caching: The miniconda environment is set up using a caching mechanism for faster workflow execution after the first run.
|
||||
6. Setup Miniconda: Miniconda is set up using the `conda-3-9-env-full.yaml` environment file, with channels `conda-forge` and `defaults`, and with the `build_env` environment activated.
|
||||
7. Run Poetry: Poetry is used to install the dependencies for the project.
|
||||
8. Install PyInstaller: PyInstaller is installed using Poetry.
|
||||
9. Poetry Install Portfolio Optimization and Forecasting Toolkits: The portfolio optimization and forecasting toolkits are installed using Poetry.
|
||||
10. Install Specific Papermill: A specific version of Papermill is installed using pip.
|
||||
11. Build Bundle: The terminal application is built using PyInstaller, with icons and assets copied to the DMG directory.
|
||||
12. Create DMG: The DMG file is created using the `create-dmg` tool.
|
||||
13. Clean up Build Artifacts: The build artifacts such as the terminal directory and DMG directory are removed.
|
||||
14. Save Build Artifact DMG: The DMG file is saved as a build artifact.
|
||||
15. Convert & Mount DMG: The DMG file is converted and mounted.
|
||||
16. Directory Change: The current directory is changed to the mounted DMG file.
|
||||
17. Unmount DMG: The mounted DMG file is unmounted.
|
||||
18. Run Integration Tests: The built terminal application is run with integration tests, and the results are displayed.
|
||||
|
||||
Finally, the integration tests are run and the results are logged. The workflow is configured to run only when triggered by a workflow dispatch event and runs in a concurrent group, with the ability to cancel in-progress jobs.
|
||||
|
||||
## Nightly Build Workflow
|
||||
This code is a GitHub Actions workflow configuration file that is used to trigger other workflows when certain events occur. The main purpose of this workflow is to trigger builds on different platforms when a release is made or a pull request is made to the main branch.
|
||||
|
||||
This workflow is triggered at UTC+0 daily by the GitHub Action schedule event.
|
||||
|
||||
The job includes the following steps:
|
||||
|
||||
1. Trigger Windows Build: This step uses the `aurelien-baudet/workflow-dispatch` action to trigger the windows10_build.yml workflow.
|
||||
|
||||
2. Trigger macOS Build: This step uses the `aurelien-baudet/workflow-dispatch` action to trigger the m1_macos_build.yml workflow
|
||||
|
||||
3. Trigger Intel Build: This step uses the `aurelien-baudet/workflow-dispatch` action to trigger the intel_macos_build.yml workflow
|
||||
|
||||
4. Trigger Docker Build: This step uses the `aurelien-baudet/workflow-dispatch` action to trigger the docker.yml workflow
|
||||
|
||||
This workflow also uses a concurrency setting that groups the jobs by the workflow and ref, and cancels any in-progress jobs.
|
||||
|
||||
## pypi-publish Workflow
|
||||
The Github Action code `Deploy to PyPI` is used to deploy a Python project to PyPI (Python Package Index) and TestPyPI, which is a separate package index for testing purposes. The code is triggered on two events:
|
||||
|
||||
1. Push event: The code is triggered whenever there is a push to the `release/*` and `main` branches.
|
||||
|
||||
2. Workflow dispatch event: The code can be manually triggered by the workflow dispatch event.
|
||||
|
||||
The code sets the concurrency to the `group` and the option `cancel-in-progress` is set to `true` to ensure that the running jobs in the same `group` are cancelled in case another job is triggered.
|
||||
|
||||
The code contains two jobs, `deploy-test-pypi` and `deploy-pypi`, both of which have the same steps with slight variations.
|
||||
|
||||
The `deploy-test-pypi` job is triggered only if the pushed branch starts with `refs/heads/release/`. This job sets up the Python environment, installs the `build` package using `pip`, builds binary wheel and source tarball using `build`, and finally, publishes the distributions to TestPyPI using the `pypa/gh-action-pypi-publish@release/v1` Github Action. The `password` to access TestPyPI is stored as a secret named `TEST_PYPI_API_TOKEN`.
|
||||
|
||||
Similarly, the `deploy-pypi` job is triggered only if the pushed branch starts with `refs/heads/main`. This job follows the same steps as `deploy-test-pypi`, but the distributions are published to PyPI instead of TestPyPI. The `password` to access PyPI is stored as a secret named `PYPI_API_TOKEN`.
|
||||
|
||||
Note: The code uses the `pypa/build` package for building the binary wheel and source tarball, and the `pypa/gh-action-pypi-publish@release/v1` Github Action for publishing the distributions to PyPI and TestPyPI.
|
||||
|
||||
## Unit Tests Workflow
|
||||
This workflow is used to run unit tests on the OpenBB Terminal. The workflow is triggered on the following events:
|
||||
The events this workflow will respond to are:
|
||||
|
||||
1. Pull requests that are opened, synchronized, edited, or closed. The pull request must be made to the `develop` or `main` branches.
|
||||
|
||||
2. Pushes to the `release/*` branches.
|
||||
|
||||
Each job in the workflow specifies a set of steps that are executed in order.
|
||||
|
||||
The first job, `check-files-changed`, checks whether there are any changes to certain file types in the repository, such as Python files and lockfiles. If there are changes, then the `check-changes` output variable is set to `true`.
|
||||
|
||||
The next job, `base-test`, runs a series of tests if `check-changes` is `true` and the base branch of the pull request is `develop`. This job sets up a Python 3.9 environment, installs Poetry, and then runs tests using `pytest`. Finally, it starts the terminal and exits.
|
||||
|
||||
The next job, `tests-python`, runs tests for different versions of Python (3.8, 3.9, and 3.10) on the `ubuntu-latest` operating system. It sets up the specified Python version, installs Poetry and dependencies, and then runs tests using `pytest`.
|
||||
|
||||
The next job, `full-test`, uses the GitHub Actions `checkout` action to checkout the code, followed by the `setup-python` action to set up the specified version of Python. Then, the `install-poetry` action is used to install the package manager Poetry, and a cache is set up using the `actions/cache` action to avoid re-installing dependencies. After that, the dependencies are installed using Poetry, and a list of installed packages is displayed. Then, the tests are run using `pytest`, and finally, the `terminal.py` script is started and exited.
|
||||
|
||||
The last job, `tests-conda`, sets up a Miniconda environment using the `setup-miniconda` action. The environment is specified using a YAML file and is activated. Then, the tests are run.
|
||||
|
||||
## Windows 10 Build Workflow
|
||||
This is a GitHub Actions workflow file that automates the build and testing process for the OpenBB Terminal on Windows 10. The workflow consists of two jobs:
|
||||
|
||||
1. Windows-Build
|
||||
2. Build-Exe
|
||||
|
||||
- The Windows-Build job does the following:
|
||||
- Sets up the Windows Git configuration for long file paths.
|
||||
- Checks out the repository code.
|
||||
- Sets up Python 3.9 and creates an OpenBB environment using poetry.
|
||||
- Installs necessary packages and builds the terminal using PyInstaller.
|
||||
- Uploads the built artifact to GitHub as an artifact.
|
||||
- The Build-Exe job does the following:
|
||||
- Sets up the Windows Git configuration for long file paths.
|
||||
- Checks out the repository code.
|
||||
- Downloads the built artifact from the previous Windows-Build job.
|
||||
- Copies the files into an app folder for building the EXE file.
|
||||
- Builds the EXE file using NSIS.
|
||||
- Uploads the built EXE as an artifact to GitHub.
|
||||
- Runs integration tests on the terminal and saves the results to a text file.
|
||||
- Uploads the test results summary to Slack.
|
||||
- Cleans up previous build files and artifacts.
|
||||
|
||||
This workflow is triggered by the `workflow_dispatch` event and runs in concurrency with other workflows in the same group, with the ability to cancel in-progress builds. The concurrency group is defined as `${{ github.workflow }}-${{ github.ref }}`.
|
||||
3
.github/workflows/gh-pages.yml
vendored
3
.github/workflows/gh-pages.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: 1.1.13
|
||||
version: 1.3.2
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
|
||||
@@ -76,4 +76,3 @@ jobs:
|
||||
# You can swap them out with your own user credentials.
|
||||
user_name: github-actions[bot]
|
||||
user_email: 41898282+github-actions[bot]@users.noreply.github.com
|
||||
|
||||
34
.github/workflows/integration-test.yml
vendored
34
.github/workflows/integration-test.yml
vendored
@@ -13,7 +13,17 @@ concurrency:
|
||||
jobs:
|
||||
integration-tests:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
steps:
|
||||
- name: Slack Notification - Starting
|
||||
uses: act10ns/slack@v1
|
||||
with:
|
||||
status: starting
|
||||
channel: '#workflows'
|
||||
message: Starting Integration Test...
|
||||
if: always()
|
||||
|
||||
- name: Checkout code
|
||||
if: github.event_name == 'push'
|
||||
uses: actions/checkout@v3
|
||||
@@ -31,7 +41,7 @@ jobs:
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: 1.1.13
|
||||
version: 1.3.2
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
|
||||
@@ -47,6 +57,26 @@ jobs:
|
||||
run: poetry install --no-interaction -E optimization
|
||||
|
||||
- name: Run integration tests
|
||||
id: integration-tests
|
||||
run: |
|
||||
source $VENV
|
||||
python terminal.py /home/runner/work/OpenBBTerminal/OpenBBTerminal/openbb_terminal/miscellaneous/integration_tests_scripts -t
|
||||
python terminal.py -t > result.txt
|
||||
cat result.txt
|
||||
grep "================================ integration test summary ================================" result.txt -A100 | tail -c 2000 > failed_tests.txt
|
||||
|
||||
- name: Upload summary to Slack
|
||||
uses: adrey/slack-file-upload-action@master
|
||||
with:
|
||||
token: ${{ secrets.SLACK_API_TOKEN }}
|
||||
initial_comment: "Integration test summary"
|
||||
title: "Integration test summary"
|
||||
path: failed_tests.txt
|
||||
channel: ${{ secrets.SLACK_CHANNEL_ID }}
|
||||
|
||||
- name: Slack Notification - Success/Failure
|
||||
uses: act10ns/slack@v1
|
||||
with:
|
||||
status: ${{ job.status }}
|
||||
steps: ${{ toJson(steps) }}
|
||||
channel: '#workflows'
|
||||
if: always()
|
||||
|
||||
34
.github/workflows/intel_macos_build.yml
vendored
34
.github/workflows/intel_macos_build.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
steps:
|
||||
# Checkout repository main branch. this allows for the commit hashes to line up
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.4.2
|
||||
uses: actions/checkout@v3
|
||||
- name: Git Log
|
||||
run: git log
|
||||
# Install create-dmg
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
echo $PATH
|
||||
# Set up caching for conda env so that the workflow runs quickly after the first time
|
||||
- name: Setup Conda Caching
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/conda_pkgs_dir
|
||||
key: conda-macos-3-9-${{ hashFiles('build/conda/conda-3-9-env-full.yaml') }}
|
||||
@@ -53,19 +53,19 @@ jobs:
|
||||
activate-environment: build_env
|
||||
use-only-tar-bz2: true # Needed for caching some reason
|
||||
- name: Run Poetry
|
||||
run: poetry install
|
||||
- name: Install Pyinstaller
|
||||
run: poetry install -E installer
|
||||
- name: Install Portfolio Optimization and Forecasting Toolkits
|
||||
run: poetry install -E all
|
||||
run: |
|
||||
pip list
|
||||
python -m pip install -r requirements-full.txt
|
||||
- name: Install Specific Papermill
|
||||
run: |
|
||||
pip list
|
||||
pip uninstall papermill -y
|
||||
pip install git+https://github.com/nteract/papermill.git@main
|
||||
# This is basically the same as build4mac.sh
|
||||
- name: Build Bundle
|
||||
run: |
|
||||
pyinstaller build/pyinstaller/terminal.spec --clean
|
||||
pip list
|
||||
python -m PyInstaller build/pyinstaller/terminal.spec --clean
|
||||
osascript build/pyinstaller/setup_icons.applescript
|
||||
mkdir DMG
|
||||
cp -r build/pyinstaller/macOS_package_assets/* DMG/
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
rm -rf build/terminal
|
||||
rm -rf dist
|
||||
rm -rf DMG
|
||||
- name: Notorize DMG
|
||||
- name: Notarize DMG
|
||||
env:
|
||||
NOTARIZE_APPLE_ID: ${{ secrets.NOTARIZE_APPLE_ID }}
|
||||
NOTARIZE_APPLE_PWD: ${{ secrets.NOTARIZE_APPLE_PWD }}
|
||||
@@ -177,8 +177,22 @@ jobs:
|
||||
cp -R OpenBB\ Terminal ~/Desktop
|
||||
- name: Unmount DMG
|
||||
run: hdiutil detach /dev/disk2
|
||||
|
||||
- name: Run Integration Tests
|
||||
run: /Users/openbb/Desktop/OpenBB\ Terminal/.OpenBB/OpenBBTerminal /Users/openbb/actions-runner/_work/OpenBBTerminal/OpenBBTerminal/openbb_terminal/miscellaneous/integration_tests_scripts -t
|
||||
run: |
|
||||
/Users/openbb/Desktop/OpenBB\ Terminal/.OpenBB/OpenBBTerminal -t -s forecast cryptocurrency reports alternative economy futures econometrics dashboards portfolio stocks/test_stocks_options_screen.openbb stocks/test_stocks_options.openbb forex etf stocks/test_stocks_fa.openbb > result.txt
|
||||
cat result.txt
|
||||
grep "================================ integration test summary ================================" result.txt -A100 | tail --bytes=2000 > failed_tests.txt
|
||||
|
||||
- name: Upload summary to Slack
|
||||
uses: adrey/slack-file-upload-action@master
|
||||
with:
|
||||
token: ${{ secrets.SLACK_API_TOKEN }}
|
||||
initial_comment: "Intel MacOs Build Integration test summary"
|
||||
title: "Intel MacOs Build Integration test summary"
|
||||
path: failed_tests.txt
|
||||
channel: ${{ secrets.SLACK_CHANNEL_ID }}
|
||||
|
||||
- name: Remove OpenBB Folder
|
||||
run: rm -rf /Users/openbb/Desktop/OpenBB\ Terminal
|
||||
- name: Remove OpenBB Exports Folder
|
||||
|
||||
8
.github/workflows/linting.yml
vendored
8
.github/workflows/linting.yml
vendored
@@ -16,6 +16,8 @@ on:
|
||||
- 'feature/*'
|
||||
- 'hotfix/*'
|
||||
- 'release/*'
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
|
||||
# Cancel previous runs that are not yet completed.
|
||||
concurrency:
|
||||
@@ -43,16 +45,14 @@ jobs:
|
||||
restore-keys: ${{ runner.os }}-linting-${{ hashFiles('**/poetry.lock') }}
|
||||
|
||||
- run: |
|
||||
pip install bandit black codespell mypy==0.982 pyupgrade safety pylint==2.15.2 packaging==22 ruff==0.0.236
|
||||
pip install bandit black codespell mypy==0.982 safety pylint==2.15.2 packaging==22 ruff==0.0.243
|
||||
pip install types-pytz types-requests types-termcolor types-tabulate types-PyYAML types-python-dateutil types-setuptools types-six
|
||||
- run: bandit -x ./tests -r . || true
|
||||
- run: black --diff --check .
|
||||
- run: codespell --ignore-words-list=commun,statics,ro,zar,zlot,jewl,ba,buil,coo,ether,hist,hsi,mape,navagation,operatio,pres,ser,yeld,shold,ist,varian,datas,ake,creat,vie,hel,ket,toke,certi,buidl,ot,te --quiet-level=2 --skip=./tests,.git,*.css,*.csv,*.html,*.ini,*.ipynb,*.js,*.json,*.lock,*.scss,*.txt,*.yaml,./build/pyinstaller,./website/config.toml
|
||||
- run: ruff .
|
||||
- run: mypy --ignore-missing-imports openbb_terminal
|
||||
- run: shopt -s globstar && pyupgrade --py38-plus **/*.py
|
||||
- run: |
|
||||
pylint terminal.py openbb_terminal tests
|
||||
- run: pylint terminal.py openbb_terminal tests
|
||||
|
||||
markdown-link-check:
|
||||
name: Markdown Linting
|
||||
|
||||
121
.github/workflows/m1_macos_build.yml
vendored
121
.github/workflows/m1_macos_build.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
steps:
|
||||
# Checkout repository main branch. this allows for the commit hashes to line up
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.4.2
|
||||
uses: actions/checkout@v3
|
||||
- name: Git Log
|
||||
run: git log
|
||||
# Install create-dmg
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
echo $PATH
|
||||
# Set up caching for conda env so that the workflow runs quickly after the first time
|
||||
- name: Setup Conda Caching
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/conda_pkgs_dir
|
||||
key: conda-macos-3-9-${{ hashFiles('build/conda/conda-3-9-env-full.yaml') }}
|
||||
@@ -55,13 +55,9 @@ jobs:
|
||||
use-only-tar-bz2: true # Needed for caching some reason
|
||||
|
||||
- name: Run Poetry
|
||||
run: poetry install -vvv
|
||||
|
||||
- name: Install Pyinstaller
|
||||
run: poetry install -E installer
|
||||
|
||||
- name: Poetry Install Portfolio Optimization and Forecasting Toolkits
|
||||
run: poetry install -E all
|
||||
run: |
|
||||
pip list
|
||||
python -m pip install -r requirements-full.txt
|
||||
|
||||
- name: Install Specific Papermill
|
||||
run: |
|
||||
@@ -71,13 +67,102 @@ jobs:
|
||||
# This is basically the same as build4mac.sh
|
||||
- name: Build Bundle
|
||||
run: |
|
||||
pyinstaller build/pyinstaller/terminal.spec --clean
|
||||
python -m PyInstaller build/pyinstaller/terminal.spec --clean
|
||||
osascript build/pyinstaller/setup_icons.applescript
|
||||
mkdir DMG
|
||||
cp -r build/pyinstaller/macOS_package_assets/* DMG/
|
||||
mv dist/OpenBBTerminal DMG/OpenBB\ Terminal/.OpenBB
|
||||
- name: Creating Keychain
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||
MACOS_KEYCHAIN_PWD: ${{ secrets.MACOS_KEYCHAIN_PWD }}
|
||||
MACOS_CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
|
||||
run: | # when pushing to main, make to generate new cert, and utilize secrets to store new password, and identity
|
||||
echo "Ensuring Keychain with same name does not exist"
|
||||
rm -rf /Users/openbb/Library/Keychains/build.keychain-db
|
||||
echo "Decoding certificate"
|
||||
echo $MACOS_CERTIFICATE | base64 --decode > certificate.p12
|
||||
echo "Creating Keychain"
|
||||
security create-keychain -p $MACOS_KEYCHAIN_PWD build.keychain
|
||||
echo "Setting Default Keychain"
|
||||
security default-keychain -s build.keychain
|
||||
echo "Unlocking Keychain"
|
||||
security unlock-keychain -p $MACOS_KEYCHAIN_PWD build.keychain
|
||||
echo "Importing Keychain"
|
||||
security import certificate.p12 -k build.keychain -P $MACOS_CERTIFICATE_PWD -T /usr/bin/codesign
|
||||
echo "Setting Partition List"
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $MACOS_KEYCHAIN_PWD build.keychain
|
||||
- name: Codesign Specific Binaries
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||
MACOS_KEYCHAIN_PWD: ${{ secrets.MACOS_KEYCHAIN_PWD }}
|
||||
MACOS_CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
|
||||
run: |
|
||||
echo "Code Signing SO Files"
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/*.so
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/*/*.so
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/*/*/*.so
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/*/*/*/*.so
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/*/*/*/*/*.so
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/*/*/*/*/*/*.so
|
||||
echo "Code Signing DYLIB Files"
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/*.dylib
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/*/*.dylib
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/*/*/*.dylib
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/*/*/*/*/*.dylib
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/prophet/.dylibs/*.dylib
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/prophet/stan_model/cmdstan-2.26.1/stan/lib/stan_math/lib/tbb/*.dylib
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/torch/.dylibs/*.dylib
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/sklearn/.dylibs/*.dylib
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/scipy/.dylibs/*.dylib
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/xgboost/.dylibs/*.dylib
|
||||
|
||||
echo "Code Signing Other Files"
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/prophet/stan_model/prophet_model.bin
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/prophet/stan_model/cmdstan-2.26.1/bin/diagnose
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/prophet/stan_model/cmdstan-2.26.1/bin/print
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/prophet/stan_model/cmdstan-2.26.1/bin/stanc
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/prophet/stan_model/cmdstan-2.26.1/bin/stansummary
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/pyarrow/plasma-store-server
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/torch/bin/protoc
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/torch/bin/protoc-3.13.0.0
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/torch/bin/torch_shm_manager
|
||||
echo "Code Sign OpenBB Executable File"
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY DMG/OpenBB\ Terminal/.OpenBB/OpenBBTerminal
|
||||
- name: Create DMG
|
||||
run: create-dmg --volname "OpenBB Terminal" --volicon "images/dmg_volume.icns" --background "images/openbb_dmg_background.png" --icon "OpenBB Terminal" 190 250 --window-pos 190 120 --window-size 800 400 --icon-size 100 --text-size 14 --app-drop-link 600 250 --eula LICENSE --format UDZO --no-internet-enable "OpenBB Terminal".dmg DMG
|
||||
- name: Code Sign DMG Installer
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
|
||||
MACOS_KEYCHAIN_PWD: ${{ secrets.MACOS_KEYCHAIN_PWD }}
|
||||
MACOS_CODESIGN_IDENTITY: ${{ secrets.MACOS_CODESIGN_IDENTITY }}
|
||||
run: |
|
||||
echo "Code Signing DMG Installer"
|
||||
/usr/bin/codesign --deep --force --verify --verbose --options runtime --entitlements "build/pyinstaller/entitlements.plist" -s $MACOS_CODESIGN_IDENTITY OpenBB\ Terminal.dmg
|
||||
echo "Verify Signing"
|
||||
codesign --verify --deep --strict --verbose=2 OpenBB\ Terminal.dmg
|
||||
- name: Deleting Previous Keychain
|
||||
run: |
|
||||
echo "Deleting Previous Keychain to Clean Instance"
|
||||
rm -rf /Users/openbb/Library/Keychains/build.keychain-db
|
||||
- name: Clean up Build Artifacts
|
||||
run: |
|
||||
rm -rf build/terminal
|
||||
rm -rf dist
|
||||
rm -rf DMG
|
||||
- name: Notorize DMG
|
||||
env:
|
||||
NOTARIZE_APPLE_ID: ${{ secrets.NOTARIZE_APPLE_ID }}
|
||||
NOTARIZE_APPLE_PWD: ${{ secrets.NOTARIZE_APPLE_PWD }}
|
||||
NOTARIZE_APPLE_TEAM_ID: ${{ secrets.NOTARIZE_APPLE_TEAM_ID }}
|
||||
run: |
|
||||
xcrun notarytool submit OpenBB\ Terminal.dmg --apple-id "$NOTARIZE_APPLE_ID" --password "$NOTARIZE_APPLE_PWD" --team-id "$NOTARIZE_APPLE_TEAM_ID" --wait
|
||||
- name: Staple
|
||||
run: |
|
||||
xcrun stapler staple "OpenBB Terminal.dmg"
|
||||
- name: Clean up Build Artifacts
|
||||
run: |
|
||||
rm -rf build/terminal
|
||||
@@ -101,8 +186,22 @@ jobs:
|
||||
cp -R OpenBB\ Terminal ~/Desktop
|
||||
- name: Unmount DMG
|
||||
run: hdiutil detach /dev/disk4
|
||||
|
||||
- name: Run Integration Tests
|
||||
run: /Users/openbb/Desktop/OpenBB\ Terminal/.OpenBB/OpenBBTerminal /Users/openbb/actions-runner/_work/OpenBBTerminal/OpenBBTerminal/openbb_terminal/miscellaneous/integration_tests_scripts -t
|
||||
run: |
|
||||
/Users/openbb/Desktop/OpenBB\ Terminal/.OpenBB/OpenBBTerminal -t -s forecast cryptocurrency reports alternative economy futures econometrics dashboards portfolio stocks/test_stocks_options_screen.openbb stocks/test_stocks_options.openbb forex etf stocks/test_stocks_fa.openbb > result.txt
|
||||
cat result.txt
|
||||
grep "================================ integration test summary ================================" result.txt -A100 | tail --bytes=2000 > failed_tests.txt
|
||||
|
||||
- name: Upload summary to Slack
|
||||
uses: adrey/slack-file-upload-action@master
|
||||
with:
|
||||
token: ${{ secrets.SLACK_API_TOKEN }}
|
||||
initial_comment: "M1 MacOs Build Integration test summary"
|
||||
title: "M1 MacOs Build Integration test summary"
|
||||
path: failed_tests.txt
|
||||
channel: ${{ secrets.SLACK_CHANNEL_ID }}
|
||||
|
||||
- name: Remove OpenBB Folder
|
||||
run: rm -rf /Users/openbb/Desktop/OpenBB\ Terminal
|
||||
- name: Remove OpenBB Exports Folder
|
||||
|
||||
10
.github/workflows/nightly-build.yml
vendored
10
.github/workflows/nightly-build.yml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
permissions:
|
||||
permissions:
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
@@ -25,8 +25,8 @@ jobs:
|
||||
with:
|
||||
workflow: windows10_build.yml
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-for-completion-timeout: 2h
|
||||
|
||||
wait-for-completion-timeout: 3h
|
||||
|
||||
trigger-macos-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
workflow: m1_macos_build.yml
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-for-completion-timeout: 2h
|
||||
|
||||
|
||||
trigger-intel-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
workflow: intel_macos_build.yml
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
wait-for-completion-timeout: 2h
|
||||
|
||||
|
||||
trigger-docker-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
259
.github/workflows/test.yml
vendored
259
.github/workflows/test.yml
vendored
@@ -1,259 +0,0 @@
|
||||
name: Tests
|
||||
env:
|
||||
OPENBB_ENABLE_QUICK_EXIT: true
|
||||
OPENBB_LOG_COLLECT: false
|
||||
OPENBB_USE_ION: false
|
||||
OPENBB_USE_PROMPT_TOOLKIT: false
|
||||
OPENBB_FILE_OVERWRITE: true
|
||||
PIP_DEFAULT_TIMEOUT: 100
|
||||
|
||||
on: [workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
linting:
|
||||
name: General Linting
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # actions/checkout v3.0.2
|
||||
|
||||
- name: Setup Python 3.9
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
architecture: x64
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-linting-${{ hashFiles('**/poetry.lock') }}
|
||||
restore-keys: ${{ runner.os }}-linting-${{ hashFiles('**/poetry.lock') }}
|
||||
|
||||
- run: pip install bandit black codespell mypy==0.982 pyupgrade safety pylint==2.15.2 packaging==22 ruff==0.0.212
|
||||
- run: pip install types-pytz types-requests types-termcolor types-tabulate types-PyYAML types-python-dateutil types-setuptools types-six
|
||||
- run: bandit -x ./tests -r . || true
|
||||
- run: black --diff --check .
|
||||
- run: codespell --ignore-words-list=commun,statics,ro,zar,zlot,jewl,ba,buil,coo,ether,hist,hsi,mape,navagation,operatio,pres,ser,yeld,shold,ist,varian,datas,ake,creat,vie,hel,ket,toke,certi,buidl,ot,te --quiet-level=2 --skip=./tests,.git,*.css,*.csv,*.html,*.ini,*.ipynb,*.js,*.json,*.lock,*.scss,*.txt,*.yaml,./build/pyinstaller,./website/config.toml
|
||||
- run: ruff .
|
||||
- run: mypy --ignore-missing-imports openbb_terminal
|
||||
- run: shopt -s globstar && pyupgrade --py38-plus **/*.py
|
||||
- run: safety check
|
||||
- run: |
|
||||
pylint terminal.py openbb_terminal tests
|
||||
test:
|
||||
name: Base Tests - Ubuntu-latest - Python 3.9
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python 3.9
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
architecture: x64
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: 1.1.13
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
|
||||
- name: Load cached venv
|
||||
id: cached-poetry-dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .venv
|
||||
key: venv-${{ runner.os }}-v1-${{ hashFiles('**/poetry.lock') }}
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
|
||||
run: poetry install --no-interaction -E optimization
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
MPLBACKEND: Agg
|
||||
run: |
|
||||
source $VENV
|
||||
pytest tests/ --optimization
|
||||
|
||||
- name: Start Terminal and exit
|
||||
run: |
|
||||
source $VENV
|
||||
python terminal.py
|
||||
markdown-link-check:
|
||||
name: Markdown Linting
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
name: Check out the code
|
||||
- name: Lint Code Base
|
||||
uses: docker://avtodev/markdown-lint:v1
|
||||
with:
|
||||
args: "./*.md ./changelogs/*.md ./openbb_terminal/**/*.md ./discordbot/**/*.md"
|
||||
tests-python:
|
||||
name: Vanilla Python Tests - (${{ matrix.python-version }}, ${{ matrix.os }})
|
||||
needs: test
|
||||
runs-on: ${{ matrix.os }}
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
python-version: ["3.8", "3.9", "3.10"]
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
exclude:
|
||||
- os: ubuntu-latest
|
||||
python-version: "3.9"
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
architecture: x64
|
||||
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: 1.1.13
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
|
||||
- name: Load cached venv
|
||||
id: cached-poetry-dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: .venv
|
||||
key: venv-${{ runner.os }}-v1-${{ hashFiles('**/poetry.lock') }}
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
|
||||
run: poetry install --no-interaction -E optimization
|
||||
|
||||
- name: List installed packages
|
||||
shell: bash -l {0}
|
||||
run: |
|
||||
source $VENV
|
||||
pip list
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
MPLBACKEND: Agg
|
||||
run: |
|
||||
source $VENV
|
||||
pytest tests/ --optimization
|
||||
|
||||
- name: Start Terminal and exit
|
||||
run: |
|
||||
source $VENV
|
||||
python terminal.py
|
||||
tests-conda:
|
||||
name: Anaconda Python Tests - (${{ matrix.python-version }}, ${{ matrix.os }})
|
||||
needs: test
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: ["ubuntu", "windows"]
|
||||
python-version: ["3.9"]
|
||||
dependencies: [""]
|
||||
include:
|
||||
- os: ubuntu
|
||||
python: 3.9
|
||||
- os: windows
|
||||
python: 3.9
|
||||
env:
|
||||
OS: ${{ matrix.os }}
|
||||
PYTHON: ${{ matrix.python }}
|
||||
PYTHONNOUSERSITE: 1
|
||||
steps:
|
||||
- name: Set git to use LF
|
||||
if: runner.os == 'Windows'
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
git config --global core.eol lf
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Need to fetch more than the last commit so that setuptools-scm can
|
||||
# create the correct version string. If the number of commits since
|
||||
# the last release is greater than this, the version still be wrong.
|
||||
# Increase if necessary.
|
||||
fetch-depth: 100
|
||||
# The GitHub token is preserved by default but this job doesn't need
|
||||
# to be able to push to GitHub.
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup caching for conda packages
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/conda_pkgs_dir
|
||||
key: conda-${{ runner.os }}-v1-${{ matrix.python }}-${{ hashFiles('build/conda/conda-3-9-env-full.yaml') }}
|
||||
|
||||
- name: Setup Miniconda
|
||||
uses: conda-incubator/setup-miniconda@v2
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
miniconda-version: "latest"
|
||||
auto-update-conda: true
|
||||
channels: conda-forge,defaults
|
||||
show-channel-urls: true
|
||||
channel-priority: flexible
|
||||
environment-file: build/conda/conda-3-9-env.yaml
|
||||
activate-environment: testing
|
||||
# Needed for caching
|
||||
use-only-tar-bz2: true
|
||||
|
||||
- name: Update Temp permissions on windows
|
||||
if: runner.os == 'Windows'
|
||||
shell: cmd /C CALL {0}
|
||||
run: >-
|
||||
c:\windows\system32\icacls C:\Users\runneradmin\AppData\Local\Temp /grant "everyone":F /t
|
||||
|
||||
- name: Get pip cache dir
|
||||
shell: bash -l {0}
|
||||
id: pip-cache
|
||||
run: |
|
||||
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: pip cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-v1-3-9-pip-${{ hashFiles('requirements-full.txt') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-v1-pip-
|
||||
|
||||
- name: Uninstall Brotlipy
|
||||
shell: bash -l {0}
|
||||
run: |
|
||||
conda remove brotlipy -y
|
||||
|
||||
- name: Install dependencies (Bash)
|
||||
shell: bash -l {0}
|
||||
run: |
|
||||
pip install --requirement requirements-full.txt
|
||||
|
||||
- name: List installed packages (Bash)
|
||||
shell: bash -l {0}
|
||||
run: |
|
||||
conda list
|
||||
pip list
|
||||
|
||||
- name: Run tests (Bash)
|
||||
env:
|
||||
MPLBACKEND: Agg
|
||||
shell: bash -l {0}
|
||||
run: pytest tests/ -m "not linux" --optimization
|
||||
|
||||
- name: Start Terminal and exit
|
||||
shell: bash -l {0}
|
||||
run: python terminal.py
|
||||
10
.github/workflows/unit-test.yml
vendored
10
.github/workflows/unit-test.yml
vendored
@@ -6,6 +6,7 @@ env:
|
||||
OPENBB_USE_ION: false
|
||||
OPENBB_USE_PROMPT_TOOLKIT: false
|
||||
OPENBB_FILE_OVERWRITE: true
|
||||
OPENBB_ENABLE_CHECK_API: false
|
||||
PIP_DEFAULT_TIMEOUT: 100
|
||||
|
||||
on:
|
||||
@@ -17,7 +18,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- release/*
|
||||
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -76,7 +78,7 @@ jobs:
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: 1.1.13
|
||||
version: 1.3.2
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
|
||||
@@ -128,7 +130,7 @@ jobs:
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: 1.1.13
|
||||
version: 1.3.2
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
|
||||
@@ -187,7 +189,7 @@ jobs:
|
||||
- name: Install Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: 1.1.13
|
||||
version: 1.3.2
|
||||
virtualenvs-create: true
|
||||
virtualenvs-in-project: true
|
||||
|
||||
|
||||
28
.github/workflows/windows10_build.yml
vendored
28
.github/workflows/windows10_build.yml
vendored
@@ -17,7 +17,7 @@ concurrency:
|
||||
jobs:
|
||||
Windows-Build:
|
||||
name: Windows10 Build ENV
|
||||
runs-on: windows-latest
|
||||
runs-on: windows-2019
|
||||
# Configuring -------------
|
||||
steps:
|
||||
- name: Setup Windows Git Configuration # This is under the assumption that git is already installed on the system
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
git config --system core.longpaths true
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.4.2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python & Create OpenBB Environment
|
||||
uses: actions/setup-python@v4
|
||||
@@ -38,10 +38,9 @@ jobs:
|
||||
python -m venv venv
|
||||
source venv/Scripts/activate
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install poetry==1.1.13
|
||||
python -m pip install setuptools==64.0.2 wheel
|
||||
python -m pip install charset_normalizer==2.1.1
|
||||
python -m poetry install -E installer -E all
|
||||
python -m pip install -r requirements-full.txt
|
||||
python -m pip uninstall papermill -y
|
||||
python -m pip install git+https://github.com/nteract/papermill.git@main
|
||||
python -m PyInstaller build/pyinstaller/terminal.spec --clean
|
||||
@@ -67,7 +66,7 @@ jobs:
|
||||
git config --system core.longpaths true
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.4.2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Download Artifact
|
||||
uses: actions/download-artifact@v2
|
||||
@@ -84,6 +83,12 @@ jobs:
|
||||
run: |
|
||||
C:\'.\Program Files (x86)\'\nsis\makensis.exe .\build\nsis\setup.nsi
|
||||
|
||||
# This should autosign the installer now - We really need to only do this when needed though. ON RELEASE
|
||||
- name: Sign Installer
|
||||
if: startsWith(github.ref, 'refs/heads/main')
|
||||
run: |
|
||||
& 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86\signtool.exe' sign /fd sha256 /tr http://ts.ssl.com /td sha256 /sha1 26a8e28dbe3b1db7407b92d7de97c290d4436a32 'C:\Users\Administrator\actions-runner\_work\OpenBBTerminal\OpenBBTerminal\build\nsis\OpenBB Terminal Setup.exe'
|
||||
|
||||
- name: Save Build Artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -92,7 +97,18 @@ jobs:
|
||||
|
||||
- name: Run Integration Tests
|
||||
run: |
|
||||
dist\OpenBBTerminal\OpenBBTerminal.exe -t
|
||||
dist\OpenBBTerminal\OpenBBTerminal.exe -t -s forecast cryptocurrency reports alternative economy futures econometrics dashboards portfolio stocks/test_stocks_options_screen.openbb stocks/test_stocks_options.openbb forex etf stocks/test_stocks_fa.openbb > result.txt
|
||||
cat result.txt
|
||||
Select-String "================================ integration test summary ================================" result.txt -Context 0,100 | Select-Object -Last 2000 | Out-File failed_tests.txt
|
||||
|
||||
- name: Upload summary to Slack
|
||||
uses: adrey/slack-file-upload-action@master
|
||||
with:
|
||||
token: ${{ secrets.SLACK_API_TOKEN }}
|
||||
initial_comment: "Windows Build Integration test summary"
|
||||
title: "Windows Build Integration test summary"
|
||||
path: failed_tests.txt
|
||||
channel: ${{ secrets.SLACK_CHANNEL_ID }}
|
||||
|
||||
# Cleaning ------------------------
|
||||
# Make sure to add Remove-Item C:\Users\Administrator\Desktop\OPENBB-exports -Recurse whenever integration tests get fixed
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -61,3 +61,6 @@ darts_logs/
|
||||
# User data
|
||||
custom_imports/*.csv
|
||||
custom_imports/*/*.csv
|
||||
|
||||
# lightning logs
|
||||
lightning_logs/
|
||||
|
||||
7
.pre-commit-config.yaml
vendored
7
.pre-commit-config.yaml
vendored
@@ -14,7 +14,7 @@ repos:
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||
rev: 'v0.0.236'
|
||||
rev: 'v0.0.243'
|
||||
hooks:
|
||||
- id: ruff
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
@@ -64,11 +64,6 @@ repos:
|
||||
"website/content/api/*",
|
||||
]
|
||||
exclude: package.lock.json
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: reserved_args
|
||||
|
||||
215
CONTRIBUTING.md
vendored
215
CONTRIBUTING.md
vendored
@@ -13,7 +13,6 @@ Use your best judgment, and feel free to propose changes to this document in a p
|
||||
- [View](#view)
|
||||
- [Controller](#controller)
|
||||
- [Add SDK endpoint](#add-sdk-endpoint)
|
||||
- [Add Documentation](#add-documentation)
|
||||
- [Open a Pull Request](#open-a-pull-request)
|
||||
- [Review Process](#review-process)
|
||||
- [Understand Code Structure](#understand-code-structure)
|
||||
@@ -310,160 +309,40 @@ In addition, note the `self.load_class` which allows to not create a new DarkPoo
|
||||
|
||||
In order to add a command to the SDK, follow these steps:
|
||||
|
||||
1. Go to the `sdk.py` file and scroll to the `functions` dictionary, it should look like this:
|
||||
1. Go to the `trail_map.csv` file located in `openbb_terminal/miscellaneous/library`, which should look like this:
|
||||
|
||||
```python
|
||||
functions = {
|
||||
"stocks.get_news": {
|
||||
"model": "openbb_terminal.common.newsapi_model.get_news",
|
||||
},
|
||||
"stocks.load": {
|
||||
"model": "openbb_terminal.stocks.stocks_helper.load",
|
||||
},
|
||||
"stocks.candle": {
|
||||
"model": "openbb_terminal.stocks.stocks_helper.load",
|
||||
"view": "openbb_terminal.stocks.stocks_helper.display_candle",
|
||||
},
|
||||
```csv
|
||||
trail,model,view
|
||||
|
||||
...
|
||||
stocks.fa.rot,openbb_terminal.stocks.fundamental_analysis.finnhub_model.get_rating_over_time,openbb_terminal.stocks.fundamental_analysis.finnhub_view.rating_over_time
|
||||
stocks.fa.analyst,openbb_terminal.stocks.fundamental_analysis.finviz_model.get_analyst_data,
|
||||
```
|
||||
|
||||
2. Add a new key to the dictionary, which corresponds to the way the added command shall be accessed from the sdk.
|
||||
This is called the `virtual path`. In this case it should be `stocks.dps.shorted`.
|
||||
3. Now it is time to add the value to the key. This key shall be another dictionary with a `model` key and possibly a
|
||||
`view` key.
|
||||
1. The model keys value should be the path from project root to the new commands model function as a string. This
|
||||
case it is `openbb_terminal.stocks.dark_pool_shorts.yahoofinance_model.get_most_shorted`.
|
||||
2. If and **only if** the view function of the command **displays a chart**, then the `view` key and its value is
|
||||
the view functions path from the project root as a string. In this example the view function of the only prints a
|
||||
table and thus this step can be ignored.
|
||||
4. Done!!! The final new dictionary looks like this after the added example:
|
||||
In this file, the trail represents the path to the function to be called. The model represents the path to the model function and the view represents the path to the view function. Note that the second line does not have an associated view function, so we leave that column empty (the trailing comma is required to load).
|
||||
|
||||
```python
|
||||
functions = {
|
||||
...
|
||||
2. Add your new function to this structure. In our example of the `shorted` function, our trail would be `stocks.dps.shorted`.
|
||||
The model is the path to the `_model` function that was written, `openbb_terminal.stocks.dark_pool_shorts.yahoofinance_model.get_most_shorted`.
|
||||
The view is the path to the `_view` function that was written: `openbb_terminal.stocks.dark_pool_shorts.yahoofinance_view.display_most_shorted`.
|
||||
The added line of the file should look like this:
|
||||
|
||||
"stocks.dps.shorted": {
|
||||
"model": "openbb_terminal.stocks.dark_pool_shorts.yahoofinance_model.get_most_shorted",
|
||||
},
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
### Add Documentation
|
||||
|
||||
To check whether documentation is added correctly follow [Hugo Server instructions](/website/README.md).
|
||||
|
||||
This is the structure that the documentation follows:
|
||||
|
||||
```txt
|
||||
website/content/_index.md
|
||||
/stocks/_index.md
|
||||
/load/_index.md
|
||||
/candle/_index.md
|
||||
/discovery/_index.md
|
||||
/ipo/_index.md
|
||||
/...
|
||||
/...
|
||||
/...
|
||||
/cryptocurrency/_index.md
|
||||
/chart/_index.md
|
||||
/defi/_index.md
|
||||
/borrow/_index.md
|
||||
/...
|
||||
/...
|
||||
/...
|
||||
/common/_index.md
|
||||
/technical_analysis/_index.md
|
||||
/ema/_index.md
|
||||
/...
|
||||
/...
|
||||
```
|
||||
|
||||
Note that the `common` folder holds features that are common across contexts, e.g. `technical analysis` can be performed on both `stocks` or `crytpo`.
|
||||
|
||||
To add a new command, there are two main actions that need to be done:
|
||||
|
||||
1. Create a directory with the name of the command and a `_index.md` file within. Examples:
|
||||
|
||||
- When adding `ipo`, since this command belongs to context `stocks` and category `discovery`, we added a `ipo` folder with a `_index.md` file within to `website/content/stocks/discovery`.
|
||||
|
||||
- When adding `candle`, since this command belongs to context `stocks`, we added a `candle` folder with a `_index.md` file within to `website/content/stocks/`.
|
||||
|
||||
2. The `_index.md` file should have the output of the `command -h` followed by a screenshot example of what the user can expect. Note that you can now drag and drop the images while editing the readme file on the remote web version of your PR branch. Github will create a link for it with format (<https://user-images.githubusercontent.com/***/***.file_format>).
|
||||
|
||||
Example:
|
||||
|
||||
---
|
||||
|
||||
```shell
|
||||
usage: ipo [--past PAST_DAYS] [--future FUTURE_DAYS]
|
||||
```
|
||||
|
||||
Past and future IPOs. [Source: <https://finnhub.io>]
|
||||
|
||||
- --past : Number of past days to look for IPOs. Default 0.
|
||||
- --future : Number of future days to look for IPOs. Default 10.
|
||||
|
||||
<IMAGE HERE - Use drag and drop hint mentioned above>
|
||||
|
||||
---
|
||||
|
||||
3. Update the Navigation bar to match the content you've added. This is done by adding 2 lines of code to `website/data/menu/`, i.e. a `name` and a `ref`. Example:
|
||||
|
||||
```python
|
||||
---
|
||||
main:
|
||||
- name: stocks
|
||||
ref: "/stocks"
|
||||
sub:
|
||||
- name: load
|
||||
ref: "/stocks/load"
|
||||
- name: candle
|
||||
ref: "/stocks/candle"
|
||||
- name: discovery
|
||||
ref: "/stocks/discovery"
|
||||
sub:
|
||||
- name: ipo
|
||||
ref: "/stocks/discovery/ipo"
|
||||
- name: map
|
||||
ref: "/stocks/discovery/map"
|
||||
```csv
|
||||
stocks.dps.shorted,openbb_terminal.stocks.dark_pool_shorts.yahoofinance_model.get_most_shorted,openbb_terminal.stocks.dark_pool_shorts.yahoofinance_view.display_most_shorted
|
||||
```
|
||||
|
||||
### Open a Pull Request
|
||||
|
||||
Once you're happy with what you have, push your branch to remote. E.g. `git push origin feature/AmazingFeature`
|
||||
Once you're happy with what you have, push your branch to remote. E.g. `git push origin feature/AmazingFeature`. Note that we follow gitflow naming convention, so your branch name should be prefixed with `feature/` or `hotfix/` depending on the type of work you are doing.
|
||||
|
||||
A user may create a **Draft Pull Request** when he/she wants to discuss implementation with the team.
|
||||
|
||||
The team will then assign your PR one of the following labels:
|
||||
|
||||
| Label name | Description | Example |
|
||||
| -------------- | ----------------------------------- | ---------------------------------------------- |
|
||||
| `feat XS` | Extra small feature | Add a preset |
|
||||
| `feat S` | Small T-Shirt size Feature | New single command added |
|
||||
| `feat M` | Medium T-Shirt size feature | Multiple commands added from same data source |
|
||||
| `feat L` | Large T-Shirt size Feature | New category added under context |
|
||||
| `feat XL` | Extra Large feature | New context added |
|
||||
| `enhancement` | Enhancement | Add new parameter to existing command |
|
||||
| `bug` | Fix a bug | Fixes terminal crashing or warning message |
|
||||
| `build` | Build-related work | Fix a github action that is breaking the build |
|
||||
| `tests` | Test-related work | Add/improve tests |
|
||||
| `docs` | Improvements on documentation | Add/improve documentation |
|
||||
| `refactor` | Refactor code | Changing argparse location |
|
||||
| `docker` | Docker-related work | Add/improve docker |
|
||||
| `help wanted` | Extra attention is needed | When a contributor needs help |
|
||||
| `do not merge` | Label to prevent pull request merge | When PR is not ready to be merged just yet |
|
||||
|
||||
### Review Process
|
||||
|
||||
As soon as the Pull Request is opened, our repository has a specific set of github actions that will not only run
|
||||
linters on the branch just pushed, but also run pytest on it. This allows for another layer of safety on the code developed.
|
||||
|
||||
In addition, our team is known for performing `diligent` code reviews. This not only allows us to reduce the amount of
|
||||
iterations on that code and have it to be more future proof, but also allows the developer to learn/improve his coding skills.
|
||||
In addition, our team is known for performing `diligent` code reviews. This not only allows us to reduce the amount of iterations on that code and have it to be more future proof, but also allows the developer to learn/improve his coding skills.
|
||||
|
||||
Often in the past the reviewers have suggested better coding practices, e.g. using `1_000_000` instead of `1000000` for
|
||||
better visibility, or suggesting a speed optimization improvement.
|
||||
Often in the past the reviewers have suggested better coding practices, e.g. using `1_000_000` instead of `1000000` forbetter visibility, or suggesting a speed optimization improvement.
|
||||
|
||||
## Understand Code Structure
|
||||
|
||||
@@ -1100,9 +979,8 @@ The following linters are used by our codebase:
|
||||
| bandit | security analyzer |
|
||||
| black | code formatter |
|
||||
| codespell | spelling checker |
|
||||
| flake8 | style guide enforcer |
|
||||
| ruff | a fast python linter |
|
||||
| mypy | static typing checker |
|
||||
| pyupgrade | upgrade syntax for newer versions |
|
||||
| safety | checks security vulnerabilities |
|
||||
| pylint | bug and quality checker |
|
||||
| markdownlint | markdown linter |
|
||||
@@ -1146,39 +1024,46 @@ Note that a `OPENBB_` is added so that the user knows that that environment vari
|
||||
|
||||
One of the first steps once adding a new data source that requires an API key is to add that key to our [keys_controller.py](/openbb_terminal/keys_controller.py). This menu allows the user to set API keys and check their validity.
|
||||
|
||||
The following code allows to check the validity of the IEX Cloud API key.
|
||||
The following code allows to check the validity of the Polygon API key.
|
||||
|
||||
```python
|
||||
def check_iex_key(show_output: bool = False) -> str:
|
||||
"""Check IEX Cloud key
|
||||
|
||||
def check_polygon_key(show_output: bool = False) -> str:
|
||||
"""Check Polygon key
|
||||
|
||||
Parameters
|
||||
----------
|
||||
show_output: bool
|
||||
Display status string or not.
|
||||
show_output: bool
|
||||
Display status string or not. By default, False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
status: str
|
||||
|
||||
str
|
||||
Status of key set
|
||||
"""
|
||||
|
||||
if cfg.API_IEX_TOKEN == "REPLACE_ME": # nosec
|
||||
logger.info("IEX Cloud key not defined")
|
||||
if cfg.API_POLYGON_KEY == "REPLACE_ME":
|
||||
logger.info("Polygon key not defined")
|
||||
status = KeyStatus.NOT_DEFINED
|
||||
else:
|
||||
try:
|
||||
pyEX.Client(api_token=cfg.API_IEX_TOKEN, version="v1").quote(symbol="AAPL")
|
||||
logger.info("IEX Cloud key defined, test passed")
|
||||
status = KeyStatus.DEFINED_TEST_PASSED
|
||||
except Exception as _: # noqa: F841
|
||||
logger.warning("IEX Cloud key defined, test failed")
|
||||
r = request(
|
||||
"https://api.polygon.io/v2/aggs/ticker/AAPL/range/1/day/2020-06-01/2020-06-17"
|
||||
f"?apiKey={cfg.API_POLYGON_KEY}"
|
||||
)
|
||||
if r.status_code in [403, 401]:
|
||||
logger.warning("Polygon key defined, test failed")
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
elif r.status_code == 200:
|
||||
logger.info("Polygon key defined, test passed")
|
||||
status = KeyStatus.DEFINED_TEST_PASSED
|
||||
else:
|
||||
logger.warning("Polygon key defined, test inconclusive")
|
||||
status = KeyStatus.DEFINED_TEST_INCONCLUSIVE
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return status
|
||||
return str(status)
|
||||
```
|
||||
|
||||
Note that there are usually 3 states:
|
||||
@@ -1193,13 +1078,13 @@ A function can then be created with the following format to allow the user to ch
|
||||
|
||||
```python
|
||||
@log_start_end(log=logger)
|
||||
def call_iex(self, other_args: List[str]):
|
||||
"""Process iex command"""
|
||||
def call_polygon(self, other_args: List[str]):
|
||||
"""Process polygon command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="iex",
|
||||
description="Set IEX Cloud API key.",
|
||||
prog="polygon",
|
||||
description="Set Polygon API key.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-k",
|
||||
@@ -1209,13 +1094,14 @@ def call_iex(self, other_args: List[str]):
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://iexcloud.io\n")
|
||||
console.print("For your API Key, visit: https://polygon.io\n")
|
||||
return
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-k")
|
||||
ns_parser = parse_simple_args(parser, other_args)
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
if ns_parser:
|
||||
self.status_dict["iex"] = keys_model.set_iex_key(
|
||||
self.status_dict["polygon"] = keys_model.set_polygon_key(
|
||||
key=ns_parser.key, persist=True, show_output=True
|
||||
)
|
||||
```
|
||||
@@ -1270,7 +1156,6 @@ The convention is as follows:
|
||||
"candle": [],
|
||||
"load": [
|
||||
"YahooFinance",
|
||||
"IEXCloud",
|
||||
"AlphaVantage",
|
||||
"Polygon",
|
||||
"EODHD"
|
||||
@@ -1300,7 +1185,7 @@ The way to interpret this file is by following the path to a data source, e.g.
|
||||
|
||||
- `stocks/search` relies on `FinanceDatabase`
|
||||
- `stocks/candle` does not rely on any data source. This means that it relies on data that has been loaded before.
|
||||
- `stocks/load` relies on `YahooFinance`, `IEXCloud`, `AlphaVantage`, `Polygon` or `EODHD`.
|
||||
- `stocks/load` relies on `YahooFinance`, `AlphaVantage`, `Polygon` or `EODHD`.
|
||||
- **The order is important as the first data source is the one utilized by default.**
|
||||
- `stoks/options/unu` relies on `FDScanner`.
|
||||
- `stocks/options/exp` relies on `YahooFinance` by default but `Tradier` and `Nasdaq` sources are allowed.
|
||||
|
||||
5
build/conda/conda-3-8-env-full.yaml
vendored
5
build/conda/conda-3-8-env-full.yaml
vendored
@@ -4,5 +4,6 @@ channels:
|
||||
dependencies:
|
||||
- python=3.8
|
||||
- pip
|
||||
- poetry=1.1.13
|
||||
- lightgbm=3.3.3
|
||||
- conda-forge::lightgbm=3.3.3
|
||||
- pip:
|
||||
- poetry==1.3.2
|
||||
|
||||
3
build/conda/conda-3-8-env.yaml
vendored
3
build/conda/conda-3-8-env.yaml
vendored
@@ -4,4 +4,5 @@ channels:
|
||||
dependencies:
|
||||
- python=3.8
|
||||
- pip
|
||||
- poetry=1.1.13
|
||||
- pip:
|
||||
- poetry==1.3.2
|
||||
|
||||
7
build/conda/conda-3-9-env-full.yaml
vendored
7
build/conda/conda-3-9-env-full.yaml
vendored
@@ -2,7 +2,8 @@ channels:
|
||||
- conda-forge
|
||||
- defaults
|
||||
dependencies:
|
||||
- python=3.9.6
|
||||
- python=3.9.13
|
||||
- pip
|
||||
- poetry=1.1.13
|
||||
- lightgbm=3.3.3
|
||||
- conda-forge::lightgbm=3.3.3
|
||||
- pip:
|
||||
- poetry==1.3.2
|
||||
|
||||
5
build/conda/conda-3-9-env.yaml
vendored
5
build/conda/conda-3-9-env.yaml
vendored
@@ -2,6 +2,7 @@ channels:
|
||||
- conda-forge
|
||||
- defaults
|
||||
dependencies:
|
||||
- python=3.9
|
||||
- python=3.9.13
|
||||
- pip
|
||||
- poetry=1.1.13
|
||||
- pip:
|
||||
- poetry==1.3.2
|
||||
|
||||
8
build/conda/conda-3-9-windows.yaml
vendored
Normal file
8
build/conda/conda-3-9-windows.yaml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
channels:
|
||||
- conda-forge
|
||||
- defaults
|
||||
dependencies:
|
||||
- python=3.9
|
||||
- poetry==1.3.2
|
||||
- setuptools=64.0.2
|
||||
- pip
|
||||
2
build/docker/openbb.dockerfile
vendored
2
build/docker/openbb.dockerfile
vendored
@@ -40,7 +40,7 @@ FROM debian as poetry
|
||||
ENV PATH="/home/python/.local/bin:${PATH}"
|
||||
|
||||
RUN pip install --upgrade pip wheel
|
||||
RUN pip install poetry==1.1.15
|
||||
RUN pip install poetry==1.3.2
|
||||
|
||||
# SETUP OPENBB IMAGE
|
||||
FROM poetry as repository
|
||||
|
||||
137
build/nsis/setup.nsi
vendored
137
build/nsis/setup.nsi
vendored
@@ -30,6 +30,7 @@
|
||||
!define MUI_HEADERIMAGE_BITMAP "assets\installer_horizontal.bmp"
|
||||
!define MUI_ABORTWARNING
|
||||
!define MUI_WELCOMEPAGE_TITLE "${SLUG} Setup"
|
||||
!define UninstId "OpenBBTerminal" ; You might want to use a GUID here
|
||||
|
||||
;--------------------------------
|
||||
; Pages
|
||||
@@ -49,15 +50,145 @@
|
||||
; Set UI language
|
||||
!insertmacro MUI_LANGUAGE "English"
|
||||
|
||||
|
||||
Var /GLOBAL installerPath
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
; StrContains
|
||||
; This function does a case sensitive searches for an occurrence of a substring in a string.
|
||||
; It returns the substring if it is found.
|
||||
; Otherwise it returns null("").
|
||||
; Written by kenglish_hi
|
||||
; Adapted from StrReplace written by dandaman32
|
||||
|
||||
|
||||
Var STR_HAYSTACK
|
||||
Var STR_NEEDLE
|
||||
Var STR_CONTAINS_VAR_1
|
||||
Var STR_CONTAINS_VAR_2
|
||||
Var STR_CONTAINS_VAR_3
|
||||
Var STR_CONTAINS_VAR_4
|
||||
Var STR_RETURN_VAR
|
||||
|
||||
Function StrContains
|
||||
Exch $STR_NEEDLE
|
||||
Exch 1
|
||||
Exch $STR_HAYSTACK
|
||||
; Uncomment to debug
|
||||
;MessageBox MB_OK 'STR_NEEDLE = $STR_NEEDLE STR_HAYSTACK = $STR_HAYSTACK '
|
||||
StrCpy $STR_RETURN_VAR ""
|
||||
StrCpy $STR_CONTAINS_VAR_1 -1
|
||||
StrLen $STR_CONTAINS_VAR_2 $STR_NEEDLE
|
||||
StrLen $STR_CONTAINS_VAR_4 $STR_HAYSTACK
|
||||
loop:
|
||||
IntOp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_1 + 1
|
||||
StrCpy $STR_CONTAINS_VAR_3 $STR_HAYSTACK $STR_CONTAINS_VAR_2 $STR_CONTAINS_VAR_1
|
||||
StrCmp $STR_CONTAINS_VAR_3 $STR_NEEDLE found
|
||||
StrCmp $STR_CONTAINS_VAR_1 $STR_CONTAINS_VAR_4 done
|
||||
Goto loop
|
||||
found:
|
||||
StrCpy $STR_RETURN_VAR $STR_NEEDLE
|
||||
Goto done
|
||||
done:
|
||||
Pop $STR_NEEDLE ;Prevent "invalid opcode" errors and keep the
|
||||
Exch $STR_RETURN_VAR
|
||||
FunctionEnd
|
||||
|
||||
!macro _StrContainsConstructor OUT NEEDLE HAYSTACK
|
||||
Push `${HAYSTACK}`
|
||||
Push `${NEEDLE}`
|
||||
Call StrContains
|
||||
Pop `${OUT}`
|
||||
!macroend
|
||||
|
||||
!define StrContains '!insertmacro "_StrContainsConstructor"'
|
||||
|
||||
|
||||
;-------------------------------
|
||||
; Uninstall Previous Version if exists
|
||||
|
||||
!macro UninstallExisting exitcode uninstcommand
|
||||
Push `${uninstcommand}`
|
||||
Call UninstallExisting
|
||||
Pop ${exitcode}
|
||||
!macroend
|
||||
Function UninstallExisting
|
||||
Exch $1 ; uninstcommand
|
||||
Push $2 ; Uninstaller
|
||||
Push $3 ; Len
|
||||
StrCpy $3 ""
|
||||
StrCpy $2 $1 1
|
||||
StrCmp $2 '"' qloop sloop
|
||||
sloop:
|
||||
StrCpy $2 $1 1 $3
|
||||
IntOp $3 $3 + 1
|
||||
StrCmp $2 "" +2
|
||||
StrCmp $2 ' ' 0 sloop
|
||||
IntOp $3 $3 - 1
|
||||
Goto run
|
||||
qloop:
|
||||
StrCmp $3 "" 0 +2
|
||||
StrCpy $1 $1 "" 1 ; Remove initial quote
|
||||
IntOp $3 $3 + 1
|
||||
StrCpy $2 $1 1 $3
|
||||
StrCmp $2 "" +2
|
||||
StrCmp $2 '"' 0 qloop
|
||||
run:
|
||||
StrCpy $2 $1 $3 ; Path to uninstaller
|
||||
StrCpy $1 161 ; ERROR_BAD_PATHNAME
|
||||
GetFullPathName $3 "$2\.." ; $InstDir
|
||||
IfFileExists "$2" 0 +4
|
||||
ExecWait '"$2" /S _?=$3' $1 ; This assumes the existing uninstaller is a NSIS uninstaller, other uninstallers don't support /S nor _?=
|
||||
IntCmp $1 0 "" +2 +2 ; Don't delete the installer if it was aborted
|
||||
Delete "$2" ; Delete the uninstaller
|
||||
RMDir "$3" ; Try to delete $InstDir
|
||||
RMDir "$3\.." ; (Optional) Try to delete the parent of $InstDir
|
||||
Pop $3
|
||||
Pop $2
|
||||
Exch $1 ; exitcode
|
||||
FunctionEnd
|
||||
|
||||
|
||||
Function .onInit
|
||||
ReadRegStr $0 HKCU "Software\Software\Microsoft\Windows\CurrentVersion\Uninstall\${UninstId}" "UninstallString"
|
||||
${If} $0 != ""
|
||||
${AndIf} ${Cmd} `MessageBox MB_YESNO|MB_ICONQUESTION "It is highly recommended to uninstall the previous version of OpenBB Terminal - Please click Yes to proceed (Note - You will not lose your custom settings) - Or you can uninstall manually. " /SD IDYES IDYES`
|
||||
!insertmacro UninstallExisting $0 $0
|
||||
${If} $0 <> 0
|
||||
MessageBox MB_YESNO|MB_ICONSTOP "Failed to uninstall, continue anyway?" /SD IDYES IDYES +2
|
||||
Abort
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
;--------------------------------
|
||||
; Section - Install App
|
||||
|
||||
Section "-hidden app"
|
||||
SectionIn RO
|
||||
SetOutPath "$INSTDIR"
|
||||
${StrContains} $0 "\OpenBB" "$INSTDIR"
|
||||
; Making sure here if user manually removes \openbb from their path that it still installs there
|
||||
; so we dont have issues with uninstaller later.
|
||||
StrCmp $0 "" notfound
|
||||
; MessageBox MB_OK 'Found string $0 $INSTDIR'
|
||||
SetOutPath "$INSTDIR"
|
||||
Goto done
|
||||
notfound:
|
||||
; MessageBox MB_OK "$INSTDIR is 'bla'"
|
||||
SetOutPath "$INSTDIR\OpenBB"
|
||||
StrCpy $InstDir "$INSTDIR\OpenBB"
|
||||
; MessageBox MB_OK 'Did not find string "$INSTDIR\OpenBB" "$installerPath"'
|
||||
done:
|
||||
|
||||
File /r "app\*.*"
|
||||
WriteRegStr HKCU "Software\${NAME}" "" $INSTDIR
|
||||
WriteUninstaller "$INSTDIR\Uninstall.exe"
|
||||
WriteRegStr HKCU "Software\Software\Microsoft\Windows\CurrentVersion\Uninstall\${UninstId}" "OpenBBTerminal" "OpenBB"
|
||||
WriteRegStr HKCU "Software\Software\Microsoft\Windows\CurrentVersion\Uninstall\${UninstId}" "UninstallString" '"$InstDir\Uninstall.exe"'
|
||||
WriteRegStr HKCU "Software\Software\Microsoft\Windows\CurrentVersion\Uninstall\${UninstId}" "QuietUninstallString" '"$InstDir\Uninstall.exe" /S'
|
||||
SectionEnd
|
||||
|
||||
|
||||
@@ -109,9 +240,13 @@ Section "Uninstall"
|
||||
;Delete Directory
|
||||
Delete '$SMPROGRAMS\${Company}\${NAME}'
|
||||
|
||||
;Delete Reg Key
|
||||
DeleteRegKey HKCU "Software\Software\Microsoft\Windows\CurrentVersion\Uninstall\${UninstId}"
|
||||
|
||||
;Delete Uninstall
|
||||
Delete "$INSTDIR\Uninstall.exe"
|
||||
|
||||
|
||||
;Delete Folder
|
||||
RMDir /r "$INSTDIR"
|
||||
${RMDirUP} "$INSTDIR"
|
||||
|
||||
6
build/pyinstaller/terminal.spec
vendored
6
build/pyinstaller/terminal.spec
vendored
@@ -69,10 +69,6 @@ added_files = [
|
||||
os.path.join(pathex, "statsmodels", "datasets"),
|
||||
os.path.join("statsmodels", "datasets"),
|
||||
),
|
||||
(
|
||||
os.path.join(pathex, "investpy", "resources"),
|
||||
os.path.join("investpy", "resources"),
|
||||
),
|
||||
(
|
||||
os.path.join(pathex, "debugpy", "_vendored"),
|
||||
os.path.join("debugpy", "_vendored"),
|
||||
@@ -85,6 +81,7 @@ if is_win:
|
||||
added_files.append(
|
||||
(os.path.join(f"{os.path.dirname(scipy.__file__)}.libs"), "scipy.libs/"),
|
||||
)
|
||||
|
||||
# Python libraries that are explicitly pulled into the bundle
|
||||
hidden_imports = [
|
||||
"sklearn.utils._cython_blas",
|
||||
@@ -103,7 +100,6 @@ hidden_imports = [
|
||||
"user_agent",
|
||||
"vaderSentiment",
|
||||
"frozendict",
|
||||
"textwrap3",
|
||||
"pyEX",
|
||||
"feedparser",
|
||||
"_sysconfigdata__darwin_darwin",
|
||||
|
||||
3
openbb_terminal/SDK_README.md
vendored
3
openbb_terminal/SDK_README.md
vendored
@@ -279,9 +279,6 @@ df_yf = openbb.stocks.load("AAPL", source='YahooFinance')
|
||||
## From AlphaVantage
|
||||
df_av = openbb.stocks.load("AAPL", source='AlphaVantage')
|
||||
|
||||
## From IEXCloud
|
||||
df_iex = openbb.stocks.load("AAPL", source='IEXCloud')
|
||||
|
||||
## From Polygon
|
||||
df_pol = openbb.stocks.load("AAPL", source='Polygon')
|
||||
```
|
||||
|
||||
402
openbb_terminal/account/account_controller.py
Normal file
402
openbb_terminal/account/account_controller.py
Normal file
@@ -0,0 +1,402 @@
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
from prompt_toolkit.completion import NestedCompleter
|
||||
|
||||
from openbb_terminal import feature_flags as obbff
|
||||
from openbb_terminal.account.account_model import get_diff, get_routines_info
|
||||
from openbb_terminal.account.account_view import display_routines_list
|
||||
from openbb_terminal.core.config.paths import USER_ROUTINES_DIRECTORY
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.featflags_controller import FeatureFlagsController
|
||||
from openbb_terminal.helper_funcs import check_positive
|
||||
from openbb_terminal.menu import session
|
||||
from openbb_terminal.parent_classes import BaseController
|
||||
from openbb_terminal.rich_config import MenuText, console
|
||||
from openbb_terminal.session import (
|
||||
hub_model as Hub,
|
||||
local_model as Local,
|
||||
)
|
||||
from openbb_terminal.session.session_model import logout
|
||||
from openbb_terminal.session.user import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AccountController(BaseController):
|
||||
"""Account Controller Class"""
|
||||
|
||||
CHOICES_COMMANDS = [
|
||||
"logout",
|
||||
"sync",
|
||||
"pull",
|
||||
"clear",
|
||||
"list",
|
||||
"upload",
|
||||
"download",
|
||||
"delete",
|
||||
]
|
||||
|
||||
PATH = "/account/"
|
||||
|
||||
def __init__(self, queue: List[str] = None):
|
||||
super().__init__(queue)
|
||||
self.ROUTINE_FILES: Dict[str, Path] = {}
|
||||
self.REMOTE_CHOICES: List[str] = []
|
||||
self.update_runtime_choices()
|
||||
|
||||
def update_runtime_choices(self):
|
||||
"""Update runtime choices"""
|
||||
self.ROUTINE_FILES = self.get_routines()
|
||||
if session and obbff.USE_PROMPT_TOOLKIT:
|
||||
choices: dict = {c: {} for c in self.controller_choices}
|
||||
choices["sync"] = {"--on": {}, "--off": {}}
|
||||
choices["upload"]["--file"] = {c: {} for c in self.ROUTINE_FILES}
|
||||
choices["upload"]["-f"] = choices["upload"]["--file"]
|
||||
choices["download"]["--name"] = {c: {} for c in self.REMOTE_CHOICES}
|
||||
choices["download"]["-n"] = choices["download"]["--name"]
|
||||
choices["delete"]["--name"] = {c: {} for c in self.REMOTE_CHOICES}
|
||||
choices["delete"]["-n"] = choices["delete"]["--name"]
|
||||
self.completer = NestedCompleter.from_nested_dict(choices)
|
||||
|
||||
def get_routines(self):
|
||||
"""Get routines"""
|
||||
routines = {
|
||||
filepath.name: filepath
|
||||
for filepath in USER_ROUTINES_DIRECTORY.glob("*.openbb")
|
||||
}
|
||||
user_folder = USER_ROUTINES_DIRECTORY / User.get_uuid()
|
||||
if os.path.exists(user_folder):
|
||||
routines.update(
|
||||
{filepath.name: filepath for filepath in user_folder.rglob("*.openbb")}
|
||||
)
|
||||
return routines
|
||||
|
||||
def print_help(self):
|
||||
"""Print help"""
|
||||
|
||||
mt = MenuText("account/", 100)
|
||||
mt.add_info("_info_")
|
||||
mt.add_cmd("sync")
|
||||
mt.add_cmd("pull")
|
||||
mt.add_cmd("clear")
|
||||
mt.add_raw("\n")
|
||||
mt.add_info("_routines_")
|
||||
mt.add_cmd("list")
|
||||
mt.add_cmd("upload")
|
||||
mt.add_cmd("download")
|
||||
mt.add_cmd("delete")
|
||||
mt.add_raw("\n")
|
||||
mt.add_info("_authentication_")
|
||||
mt.add_cmd("logout")
|
||||
console.print(text=mt.menu_text, menu="Account")
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_logout(self, other_args: List[str]) -> None:
|
||||
"""Process logout command."""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="logout",
|
||||
description="Logout from current session.",
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
logout(
|
||||
auth_header=User.get_auth_header(),
|
||||
token=User.get_token(),
|
||||
guest=User.is_guest(),
|
||||
cls=True,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_sync(self, other_args: List[str]):
|
||||
"""Sync"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="sync",
|
||||
description="Turn on/off the automatic sending of configurations when changed.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--on",
|
||||
dest="on",
|
||||
help="Turn on sync",
|
||||
action="store_true",
|
||||
default=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--off",
|
||||
dest="off",
|
||||
help="Turn on sync",
|
||||
action="store_true",
|
||||
default=False,
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
if ns_parser.on:
|
||||
if not obbff.SYNC_ENABLED:
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_SYNC_ENABLED", True, force=True
|
||||
)
|
||||
elif ns_parser.off:
|
||||
if obbff.SYNC_ENABLED:
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_SYNC_ENABLED", False, force=True
|
||||
)
|
||||
|
||||
if obbff.SYNC_ENABLED:
|
||||
sync = "ON"
|
||||
else:
|
||||
sync = "OFF"
|
||||
|
||||
if ns_parser.on or ns_parser.off:
|
||||
console.print(f"[info]sync:[/info] {sync}")
|
||||
else:
|
||||
console.print(f"sync is {sync}, use --on or --off to change.")
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_pull(self, other_args: List[str]):
|
||||
"""Pull data"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="pull",
|
||||
description="Pull and apply stored configurations from the cloud.",
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
response = Hub.fetch_user_configs(User.get_session())
|
||||
if response:
|
||||
configs_diff = get_diff(configs=json.loads(response.content))
|
||||
if configs_diff:
|
||||
i = console.input(
|
||||
"\nDo you want to load the configurations above? (y/n): "
|
||||
)
|
||||
if i.lower() in ["y", "yes"]:
|
||||
Local.apply_configs(configs=configs_diff)
|
||||
console.print("\n[info]Done.[/info]")
|
||||
else:
|
||||
console.print("\n[info]Aborted.[/info]")
|
||||
else:
|
||||
console.print("[info]No changes to apply.[/info]")
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_clear(self, other_args: List[str]):
|
||||
"""Clear data"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="clear",
|
||||
description="Clear stored configurations from the cloud.",
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
i = console.input(
|
||||
"[red]This action is irreversible![/red]\n"
|
||||
"Are you sure you want to permanently delete your data? (y/n): "
|
||||
)
|
||||
if i.lower() in ["y", "yes"]:
|
||||
console.print("")
|
||||
Hub.clear_user_configs(auth_header=User.get_auth_header())
|
||||
else:
|
||||
console.print("\n[info]Aborted.[/info]")
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_list(self, other_args: List[str]):
|
||||
"""List routines"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="list",
|
||||
description="List routines available in the cloud.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--page",
|
||||
type=check_positive,
|
||||
dest="page",
|
||||
default=1,
|
||||
help="The page number.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--size",
|
||||
type=check_positive,
|
||||
dest="size",
|
||||
default=10,
|
||||
help="The number of results per page.",
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
response = Hub.list_routines(
|
||||
auth_header=User.get_auth_header(),
|
||||
page=ns_parser.page,
|
||||
size=ns_parser.size,
|
||||
)
|
||||
df, page, pages = get_routines_info(response)
|
||||
if not df.empty:
|
||||
self.REMOTE_CHOICES = list(df["name"])
|
||||
self.update_runtime_choices()
|
||||
display_routines_list(df, page, pages)
|
||||
else:
|
||||
console.print("[red]No routines found.[/red]")
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_upload(self, other_args: List[str]):
|
||||
"""Upload"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="upload",
|
||||
description="Upload a routine to the cloud.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--file",
|
||||
type=str,
|
||||
dest="file",
|
||||
required="-h" not in other_args,
|
||||
help="The file to be loaded",
|
||||
metavar="FILE",
|
||||
nargs="+",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--description",
|
||||
type=str,
|
||||
dest="description",
|
||||
help="The description of the routine",
|
||||
default="",
|
||||
nargs="+",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--name",
|
||||
type=str,
|
||||
dest="name",
|
||||
help="The name of the routine.",
|
||||
nargs="+",
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
routine = Local.get_routine(file_name=" ".join(ns_parser.file))
|
||||
if routine:
|
||||
description = " ".join(ns_parser.description)
|
||||
|
||||
name = (
|
||||
" ".join(ns_parser.name)
|
||||
if ns_parser.name
|
||||
else " ".join(ns_parser.file).split(sep=".openbb", maxsplit=-1)[0]
|
||||
)
|
||||
|
||||
response = Hub.upload_routine(
|
||||
auth_header=User.get_auth_header(),
|
||||
name=name,
|
||||
description=description,
|
||||
routine=routine,
|
||||
)
|
||||
if response is not None and response.status_code == 409:
|
||||
i = console.input(
|
||||
"A routine with the same name already exists, "
|
||||
"do you want to overwrite it? (y/n): "
|
||||
)
|
||||
console.print("")
|
||||
if i.lower() in ["y", "yes"]:
|
||||
response = Hub.upload_routine(
|
||||
auth_header=User.get_auth_header(),
|
||||
name=name,
|
||||
description=description,
|
||||
routine=routine,
|
||||
override=True,
|
||||
)
|
||||
else:
|
||||
console.print("[info]Aborted.[/info]")
|
||||
|
||||
if response and response.status_code == 200:
|
||||
self.REMOTE_CHOICES.append(name)
|
||||
self.update_runtime_choices()
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_download(self, other_args: List[str]):
|
||||
"""Download"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="download",
|
||||
description="Download a routine from the cloud.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--name",
|
||||
type=str,
|
||||
dest="name",
|
||||
help="The name of the routine.",
|
||||
required="-h" not in other_args,
|
||||
nargs="+",
|
||||
)
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-n")
|
||||
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
response = Hub.download_routine(
|
||||
auth_header=User.get_auth_header(),
|
||||
name=" ".join(ns_parser.name),
|
||||
)
|
||||
|
||||
if response and response.status_code == 200:
|
||||
data = response.json()
|
||||
if data:
|
||||
name = data.get("name", "")
|
||||
if name:
|
||||
console.print(f"[info]Name:[/info] {name}")
|
||||
|
||||
description = data.get("description", "")
|
||||
if description:
|
||||
console.print(f"[info]Description:[/info] {description}")
|
||||
|
||||
script = data.get("script", "")
|
||||
if script:
|
||||
file_name = f"{name}.openbb"
|
||||
file_path = Local.save_routine(
|
||||
file_name=file_name,
|
||||
routine=script,
|
||||
)
|
||||
if file_path:
|
||||
console.print(f"[info]Location:[/info] {file_path}")
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_delete(self, other_args: List[str]):
|
||||
"""Delete"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="delete",
|
||||
description="Delete a routine on the cloud.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--name",
|
||||
type=str,
|
||||
dest="name",
|
||||
help="The name of the routine",
|
||||
required="-h" not in other_args,
|
||||
nargs="+",
|
||||
)
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-n")
|
||||
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
name = " ".join(ns_parser.name)
|
||||
response = Hub.delete_routine(
|
||||
auth_header=User.get_auth_header(),
|
||||
name=name,
|
||||
)
|
||||
if response and response.status_code == 200:
|
||||
if name in self.REMOTE_CHOICES:
|
||||
self.REMOTE_CHOICES.remove(name)
|
||||
self.update_runtime_choices()
|
||||
178
openbb_terminal/account/account_model.py
Normal file
178
openbb_terminal/account/account_model.py
Normal file
@@ -0,0 +1,178 @@
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from openbb_terminal import (
|
||||
config_plot as cfg_plot,
|
||||
config_terminal as cfg,
|
||||
feature_flags as obbff,
|
||||
)
|
||||
from openbb_terminal.base_helpers import strtobool
|
||||
from openbb_terminal.core.config import paths
|
||||
from openbb_terminal.rich_config import console
|
||||
|
||||
|
||||
def get_diff(configs: dict) -> dict:
|
||||
"""Show the diff between the local and remote configs.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
configs : dict
|
||||
The configs.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
The diff.
|
||||
"""
|
||||
SETTINGS = "features_settings"
|
||||
KEYS = "features_keys"
|
||||
configs_diff: Dict[str, Dict[str, Any]] = {SETTINGS: {}, KEYS: {}}
|
||||
|
||||
diff_settings = get_diff_settings(configs.get(SETTINGS, {}))
|
||||
if diff_settings:
|
||||
console.print("[info]Settings:[/info]")
|
||||
for k, v in diff_settings.items():
|
||||
configs_diff[SETTINGS][k] = v[1]
|
||||
console.print(f" [menu]{k}[/menu]: {v[0]} -> {v[1]}")
|
||||
|
||||
diff_keys = get_diff_keys(configs.get(KEYS, {}))
|
||||
if diff_keys:
|
||||
console.print("[info]Keys:[/info]")
|
||||
for k, v in diff_keys.items():
|
||||
configs_diff[KEYS][k] = v[1]
|
||||
console.print(f" [menu]{k}[/menu]: {v[0]} -> {v[1]}")
|
||||
|
||||
if not configs_diff[SETTINGS]:
|
||||
configs_diff.pop(SETTINGS)
|
||||
|
||||
if not configs_diff[KEYS]:
|
||||
configs_diff.pop(KEYS)
|
||||
|
||||
return configs_diff
|
||||
|
||||
|
||||
def get_diff_settings(settings: dict) -> dict:
|
||||
"""Get the diff between the local and remote settings.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
configs : dict
|
||||
The configs.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
The diff.
|
||||
"""
|
||||
diff = {}
|
||||
if settings:
|
||||
for k, v in sorted(settings.items()):
|
||||
if hasattr(obbff, k):
|
||||
old, new = get_var_diff(obbff, k, v)
|
||||
if new is not None:
|
||||
diff[k] = (old, new)
|
||||
elif hasattr(cfg, k):
|
||||
old, new = get_var_diff(cfg, k, v)
|
||||
if new is not None:
|
||||
diff[k] = (old, new)
|
||||
elif hasattr(cfg_plot, k):
|
||||
old, new = get_var_diff(cfg_plot, k, v)
|
||||
if new is not None:
|
||||
diff[k] = (old, new)
|
||||
elif hasattr(paths, k):
|
||||
old, new = get_var_diff(paths, k, v)
|
||||
if new is not None:
|
||||
diff[k] = (old, new)
|
||||
|
||||
return diff
|
||||
|
||||
|
||||
def get_diff_keys(keys: dict) -> dict:
|
||||
"""Get the diff between the local and remote keys.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
configs : dict
|
||||
The configs.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
The diff.
|
||||
"""
|
||||
diff = {}
|
||||
if keys:
|
||||
for k, v in sorted(keys.items()):
|
||||
if hasattr(cfg, k):
|
||||
old, new = get_var_diff(cfg, k, v)
|
||||
if new is not None:
|
||||
diff[k] = (old, new)
|
||||
|
||||
return diff
|
||||
|
||||
|
||||
def get_var_diff(obj, name, value) -> Tuple[Any, Any]:
|
||||
"""Set attribute in object.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
obj : object
|
||||
The object.
|
||||
name : str
|
||||
The attribute name.
|
||||
value : str
|
||||
The attribute value.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple[Any, Any]
|
||||
The old and new values.
|
||||
"""
|
||||
current_value = getattr(obj, name)
|
||||
|
||||
if str(value).lower() in ["true", "false"]:
|
||||
cast_value = strtobool(value)
|
||||
elif isinstance(getattr(obj, name), int):
|
||||
cast_value = int(value)
|
||||
elif isinstance(getattr(obj, name), float):
|
||||
cast_value = float(value)
|
||||
elif isinstance(getattr(obj, name), Path):
|
||||
cast_value = Path(value)
|
||||
else:
|
||||
cast_value = value
|
||||
|
||||
if current_value != cast_value:
|
||||
return current_value, cast_value
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def get_routines_info(response) -> Tuple[pd.DataFrame, int, int]:
|
||||
"""Get the routines list.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
response : requests.Response
|
||||
The response.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple[pd.DataFrame, int, int]
|
||||
The routines list, the current page and the total number of pages.
|
||||
"""
|
||||
df = pd.DataFrame()
|
||||
page = 1
|
||||
pages = 1
|
||||
if response and response.status_code == 200:
|
||||
data = response.json()
|
||||
page = data.get("page", 1)
|
||||
pages = data.get("pages", 1)
|
||||
items = data.get("items", [])
|
||||
if items:
|
||||
df = pd.DataFrame(items)
|
||||
df.index = np.arange(1, len(df) + 1)
|
||||
|
||||
return df, page, pages
|
||||
28
openbb_terminal/account/account_view.py
Normal file
28
openbb_terminal/account/account_view.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import pandas as pd
|
||||
|
||||
from openbb_terminal.helper_funcs import print_rich_table
|
||||
|
||||
|
||||
def display_routines_list(df: pd.DataFrame, page: int, pages: int):
|
||||
"""Display the routines list.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
df : pd.DataFrame
|
||||
The routines list.
|
||||
page : int
|
||||
The current page.
|
||||
pages : int
|
||||
The total number of pages.
|
||||
"""
|
||||
title = f"Available routines - page {page}"
|
||||
if pages:
|
||||
title += f" of {pages}"
|
||||
|
||||
print_rich_table(
|
||||
df=df,
|
||||
title=title,
|
||||
headers=["Name", "Description"],
|
||||
show_index=True,
|
||||
index_name="#",
|
||||
)
|
||||
@@ -1,9 +1,19 @@
|
||||
# This is for helpers that do NOT import any OpenBB Modules
|
||||
import importlib
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Callable, Literal
|
||||
import sys
|
||||
from typing import Any, Callable, List, Literal, Optional
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from rich.console import Console
|
||||
|
||||
from openbb_terminal.core.config.paths import (
|
||||
PACKAGE_ENV_FILE,
|
||||
REPOSITORY_ENV_FILE,
|
||||
USER_ENV_FILE,
|
||||
)
|
||||
|
||||
console = Console()
|
||||
|
||||
menus = Literal["", "featflags", "settings"]
|
||||
@@ -78,10 +88,66 @@ def strtobool(val):
|
||||
"""
|
||||
val = str(val).lower()
|
||||
if val in ("y", "yes", "t", "true", "on", "1"):
|
||||
output = 1
|
||||
output = True
|
||||
elif val in ("n", "no", "f", "false", "off", "0"):
|
||||
output = 0
|
||||
output = False
|
||||
else:
|
||||
raise ValueError(f"invalid truth value {val}")
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def load_dotenv_and_reload_configs():
|
||||
"""Loads the dotenv files in the following order:
|
||||
1. Repository .env file
|
||||
2. Package .env file
|
||||
3. User .env file
|
||||
|
||||
This allows the user to override the package settings with their own
|
||||
settings, and the package to override the repository settings.
|
||||
|
||||
openbb_terminal modules are reloaded to refresh config files with new env,
|
||||
otherwise they will use cache with old variables.
|
||||
"""
|
||||
load_dotenv(REPOSITORY_ENV_FILE, override=True)
|
||||
load_dotenv(PACKAGE_ENV_FILE, override=True)
|
||||
load_dotenv(USER_ENV_FILE, override=True)
|
||||
|
||||
reload_openbb_config_modules()
|
||||
|
||||
|
||||
def reload_openbb_config_modules():
|
||||
"""Reloads openbb config modules"""
|
||||
|
||||
reload_modules(
|
||||
to_reload=[
|
||||
"openbb_terminal.config_plot",
|
||||
"openbb_terminal.config_terminal",
|
||||
"openbb_terminal.feature_flags",
|
||||
"openbb_terminal.core.config",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def reload_modules(to_reload: List[str]):
|
||||
"""Reloads modules"""
|
||||
modules = sys.modules.copy()
|
||||
for module in modules:
|
||||
if module in to_reload:
|
||||
importlib.reload(sys.modules[module])
|
||||
|
||||
|
||||
def remove_log_handlers():
|
||||
"""Remove the log handlers - needs to be done before reloading modules."""
|
||||
for handler in logging.root.handlers[:]:
|
||||
logging.root.removeHandler(handler)
|
||||
|
||||
|
||||
def clear_openbb_env_vars(exceptions: Optional[List[str]] = None):
|
||||
"""Clear openbb environment variables."""
|
||||
for v in os.environ:
|
||||
if v.startswith("OPENBB"):
|
||||
if not exceptions:
|
||||
os.environ.pop(v)
|
||||
elif v not in exceptions:
|
||||
os.environ.pop(v)
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import os
|
||||
|
||||
import dotenv
|
||||
|
||||
from openbb_terminal.base_helpers import load_env_vars
|
||||
from openbb_terminal.core.config.paths import REPOSITORY_ENV_FILE, USER_ENV_FILE
|
||||
|
||||
dotenv.load_dotenv(USER_ENV_FILE)
|
||||
dotenv.load_dotenv(REPOSITORY_ENV_FILE, override=True)
|
||||
|
||||
PLOT_DPI = load_env_vars("OPENBB_PLOT_DPI", int, 100, "settings")
|
||||
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
# IMPORTATION STANDARD
|
||||
import os
|
||||
|
||||
# IMPORTATION THIRDPARTY
|
||||
import dotenv
|
||||
|
||||
# IMPORTATION INTERNAL
|
||||
from openbb_terminal.base_helpers import load_env_vars, strtobool
|
||||
from openbb_terminal.core.config.paths import (
|
||||
PACKAGE_ENV_FILE,
|
||||
REPOSITORY_ENV_FILE,
|
||||
USER_ENV_FILE,
|
||||
)
|
||||
|
||||
from .helper_classes import TerminalStyle as _TerminalStyle
|
||||
|
||||
# IMPORTATION INTERNAL
|
||||
|
||||
|
||||
dotenv.load_dotenv(USER_ENV_FILE)
|
||||
dotenv.load_dotenv(REPOSITORY_ENV_FILE, override=True)
|
||||
dotenv.load_dotenv(PACKAGE_ENV_FILE, override=True)
|
||||
SENSITIVE_KEYS = [
|
||||
"RH_USERNAME",
|
||||
"RH_PASSWORD",
|
||||
"DG_USERNAME",
|
||||
"DG_PASSWORD",
|
||||
"DG_TOTP_SECRET",
|
||||
"OANDA_ACCOUNT_TYPE",
|
||||
"OANDA_ACCOUNT",
|
||||
"OANDA_TOKEN",
|
||||
]
|
||||
|
||||
# Network requests
|
||||
# Set request timeout
|
||||
@@ -197,3 +194,11 @@ API_TOKEN_TERMINAL_KEY = os.getenv("OPENBB_API_TOKEN_TERMINAL_KEY") or "REPLACE_
|
||||
|
||||
# https://stocksera.pythonanywhere.com/accounts/developers
|
||||
API_STOCKSERA_KEY = os.getenv("OPENBB_API_STOCKSERA_KEY") or "REPLACE_ME"
|
||||
|
||||
# https://my.openbb.co
|
||||
OPENBB_PERSONAL_ACCESS_TOKEN = (
|
||||
os.getenv("OPENBB_OPENBB_PERSONAL_ACCESS_TOKEN") or "REPLACE_ME"
|
||||
)
|
||||
|
||||
# https://intrinio.com/starter-plan
|
||||
API_INTRINIO_KEY = os.getenv("OPENBB_API_INTRINIO_KEY") or "REPLACE_ME"
|
||||
|
||||
@@ -2,26 +2,9 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
|
||||
def load_dotenv_with_priority():
|
||||
"""Loads the dotenv files in the following order:
|
||||
1. Repository .env file
|
||||
2. Package .env file
|
||||
3. User .env file
|
||||
|
||||
This allows the user to override the package settings with their own
|
||||
settings, and the package to override the repository settings.
|
||||
"""
|
||||
load_dotenv(REPOSITORY_ENV_FILE, override=True)
|
||||
load_dotenv(PACKAGE_ENV_FILE, override=True)
|
||||
load_dotenv(USER_ENV_FILE, override=True)
|
||||
|
||||
|
||||
def get_user_data_directory():
|
||||
"""Gets user data path from .env file or returns default path"""
|
||||
load_dotenv_with_priority()
|
||||
if os.getenv("OPENBB_USER_DATA_DIRECTORY"):
|
||||
user_data_directory = Path(os.getenv("OPENBB_USER_DATA_DIRECTORY"))
|
||||
else:
|
||||
@@ -37,6 +20,7 @@ REPOSITORY_ENV_FILE = REPOSITORY_DIRECTORY / ".env"
|
||||
PACKAGE_ENV_FILE = PACKAGE_DIRECTORY / ".env"
|
||||
|
||||
SETTINGS_DIRECTORY = HOME_DIRECTORY / ".openbb_terminal"
|
||||
HIST_FILE_PATH = SETTINGS_DIRECTORY / ".openbb_terminal.his"
|
||||
USER_ENV_FILE = SETTINGS_DIRECTORY / ".env"
|
||||
USER_DATA_DIRECTORY = get_user_data_directory()
|
||||
USER_EXPORTS_DIRECTORY = USER_DATA_DIRECTORY / "exports"
|
||||
|
||||
@@ -7,6 +7,7 @@ from typing import Any, Callable, Dict, List, Optional
|
||||
import openbb_terminal.config_terminal as cfg
|
||||
from openbb_terminal.core.library.metadata import Metadata
|
||||
from openbb_terminal.core.library.trail_map import TrailMap
|
||||
from openbb_terminal.session.sdk_session import login
|
||||
|
||||
# pylint: disable=import-outside-toplevel
|
||||
|
||||
@@ -228,7 +229,7 @@ class OperationLogger:
|
||||
"""
|
||||
|
||||
if func_module == "openbb_terminal.keys_model":
|
||||
from openbb_terminal.core.log.generation.settings_logger import ( # pylint: disable=C0415
|
||||
from openbb_terminal.core.log.generation.settings_logger import ( # pylint: disable=import-outside-toplevel
|
||||
log_keys,
|
||||
)
|
||||
|
||||
@@ -248,6 +249,9 @@ class OperationLogger:
|
||||
method_result=method_result,
|
||||
method_chosen=self.__method_chosen,
|
||||
)
|
||||
self.__log_if_login(
|
||||
method_chosen=self.__method_chosen,
|
||||
)
|
||||
self.__log_end(
|
||||
logger=logger,
|
||||
method_chosen=self.__method_chosen,
|
||||
@@ -271,6 +275,17 @@ class OperationLogger:
|
||||
extra={"func_name_override": method_chosen.__name__},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def __log_if_login(
|
||||
method_chosen: Callable,
|
||||
):
|
||||
if method_chosen.__name__ == login.__name__:
|
||||
from openbb_terminal.core.log.generation.user_logger import ( # pylint: disable=import-outside-toplevel
|
||||
log_user,
|
||||
)
|
||||
|
||||
log_user(with_rollover=False)
|
||||
|
||||
@staticmethod
|
||||
def __log_end(logger: Logger, method_chosen: Callable):
|
||||
logger.info(
|
||||
|
||||
@@ -130,7 +130,8 @@ class LogSender(Thread):
|
||||
|
||||
def max_size_exceeded(self, file: Path) -> bool:
|
||||
"""Check if the log file is bigger than 2MB."""
|
||||
|
||||
if not file.exists():
|
||||
return False
|
||||
return file.stat().st_size > 2 * 1024 * 1024
|
||||
|
||||
def send_path(self, path: Path, last: bool = False):
|
||||
|
||||
20
openbb_terminal/core/log/generation/common.py
Normal file
20
openbb_terminal/core/log/generation/common.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# IMPORTATION STANDARD
|
||||
import logging
|
||||
|
||||
# IMPORTATION THIRDPARTY
|
||||
# IMPORTATION INTERNAL
|
||||
from openbb_terminal.core.log.generation.path_tracking_file_handler import (
|
||||
PathTrackingFileHandler,
|
||||
)
|
||||
|
||||
|
||||
def do_rollover():
|
||||
"""RollOver the log file."""
|
||||
|
||||
for handler in logging.getLogger().handlers:
|
||||
if isinstance(handler, PathTrackingFileHandler):
|
||||
try:
|
||||
handler.doRollover()
|
||||
# IGNORE PermissionError : WINDOWS FILE OPEN
|
||||
except PermissionError:
|
||||
pass
|
||||
23
openbb_terminal/core/log/generation/custom_logger.py
Normal file
23
openbb_terminal/core/log/generation/custom_logger.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# IMPORTATION STANDARD
|
||||
import logging
|
||||
|
||||
from openbb_terminal.core.log.generation import settings_logger, user_logger
|
||||
from openbb_terminal.core.log.generation.common import do_rollover
|
||||
|
||||
# IMPORTATION THIRDPARTY
|
||||
# IMPORTATION INTERNAL
|
||||
from openbb_terminal.loggers import setup_logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def log_terminal(test_mode: bool):
|
||||
"""Logs for the terminal"""
|
||||
|
||||
if not test_mode:
|
||||
setup_logging()
|
||||
|
||||
logger.info("START")
|
||||
settings_logger.log_all_settings(with_rollover=False)
|
||||
user_logger.log_user(with_rollover=False)
|
||||
do_rollover()
|
||||
@@ -59,6 +59,13 @@ class FormatterWithExceptions(logging.Formatter):
|
||||
|
||||
return text_mocked
|
||||
|
||||
@staticmethod
|
||||
def mock_password(text: str) -> str:
|
||||
pattern = r'("password": ")[^"]+'
|
||||
replacement = r"\1 FILTERED_PASSWORD "
|
||||
text_mocked = re.sub(pattern, replacement, text)
|
||||
return text_mocked
|
||||
|
||||
@staticmethod
|
||||
def mock_home_directory(text: str) -> str:
|
||||
user_home_directory = str(HOME_DIRECTORY)
|
||||
@@ -77,6 +84,7 @@ class FormatterWithExceptions(logging.Formatter):
|
||||
def filter_piis(cls, text: str) -> str:
|
||||
text_filtered = cls.mock_ipv4(text=text)
|
||||
text_filtered = cls.mock_email(text=text_filtered)
|
||||
text_filtered = cls.mock_password(text=text_filtered)
|
||||
text_filtered = cls.mock_home_directory(text=text_filtered)
|
||||
|
||||
return text_filtered
|
||||
|
||||
@@ -8,10 +8,8 @@ from types import FunctionType, ModuleType
|
||||
# IMPORTATION INTERNAL
|
||||
import openbb_terminal.feature_flags as obbff
|
||||
from openbb_terminal import config_terminal as cfg
|
||||
from openbb_terminal.core.log.generation.path_tracking_file_handler import (
|
||||
PathTrackingFileHandler,
|
||||
)
|
||||
from openbb_terminal.terminal_helper import is_packaged_application
|
||||
from openbb_terminal.core.log.generation.common import do_rollover
|
||||
from openbb_terminal.terminal_helper import is_installer
|
||||
|
||||
SENSITIVE_WORDS = [
|
||||
"API",
|
||||
@@ -28,14 +26,15 @@ SENSITIVE_WORDS = [
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def log_all_settings() -> None:
|
||||
def log_all_settings(with_rollover: bool = True) -> None:
|
||||
"""Log all settings"""
|
||||
log_settings()
|
||||
log_config_terminal()
|
||||
log_feature_flags()
|
||||
log_keys()
|
||||
|
||||
do_rollover()
|
||||
if with_rollover:
|
||||
do_rollover()
|
||||
|
||||
|
||||
def log_settings() -> None:
|
||||
@@ -55,7 +54,7 @@ def log_settings() -> None:
|
||||
settings_dict["watermark"] = "True" if obbff.USE_WATERMARK else "False"
|
||||
settings_dict["autoscaling"] = "True" if obbff.USE_PLOT_AUTOSCALING else "False"
|
||||
settings_dict["dt"] = "True" if obbff.USE_DATETIME else "False"
|
||||
settings_dict["packaged"] = "True" if is_packaged_application() else "False"
|
||||
settings_dict["packaged"] = "True" if is_installer() else "False"
|
||||
settings_dict["python"] = str(platform.python_version())
|
||||
settings_dict["os"] = str(platform.system())
|
||||
|
||||
@@ -113,11 +112,3 @@ def log_keys() -> None:
|
||||
current_keys[cfg_var_name] = "not_defined"
|
||||
|
||||
logger.info("KEYS: %s ", json.dumps(current_keys))
|
||||
|
||||
|
||||
def do_rollover():
|
||||
"""RollOver the log file."""
|
||||
|
||||
for handler in logging.getLogger().handlers:
|
||||
if isinstance(handler, PathTrackingFileHandler):
|
||||
handler.doRollover()
|
||||
|
||||
26
openbb_terminal/core/log/generation/user_logger.py
Normal file
26
openbb_terminal/core/log/generation/user_logger.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# IMPORTATION STANDARD
|
||||
import json
|
||||
import logging
|
||||
|
||||
from openbb_terminal.core.log.generation.common import do_rollover
|
||||
|
||||
# IMPORTATION THIRDPARTY
|
||||
# IMPORTATION INTERNAL
|
||||
from openbb_terminal.session.user import User
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def log_user(with_rollover: bool = True):
|
||||
"""Log user"""
|
||||
if not User.is_guest():
|
||||
_log_user_info()
|
||||
|
||||
if with_rollover:
|
||||
do_rollover()
|
||||
|
||||
|
||||
def _log_user_info():
|
||||
"""Log user info"""
|
||||
user_info = {"user_uuid": User.get_uuid()}
|
||||
logger.info("USER: %s ", json.dumps(user_info))
|
||||
@@ -19,7 +19,10 @@ from openbb_terminal.cryptocurrency.discovery import (
|
||||
)
|
||||
from openbb_terminal.custom_prompt_toolkit import NestedCompleter
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.helper_funcs import EXPORT_ONLY_RAW_DATA_ALLOWED, check_positive
|
||||
from openbb_terminal.helper_funcs import (
|
||||
EXPORT_ONLY_RAW_DATA_ALLOWED,
|
||||
check_positive,
|
||||
)
|
||||
from openbb_terminal.menu import session
|
||||
from openbb_terminal.parent_classes import BaseController
|
||||
from openbb_terminal.rich_config import MenuText, console, get_ordered_list_sources
|
||||
@@ -52,6 +55,16 @@ class DiscoveryController(BaseController):
|
||||
if session and obbff.USE_PROMPT_TOOLKIT:
|
||||
choices: dict = self.choices_default
|
||||
|
||||
ordered_list_sources_top = get_ordered_list_sources(f"{self.PATH}top")
|
||||
if ordered_list_sources_top and ordered_list_sources_top[0] == "CoinGecko":
|
||||
choices["top"]["--sort"] = {
|
||||
c: {} for c in pycoingecko_view.COINS_COLUMNS
|
||||
}
|
||||
else:
|
||||
choices["top"]["--sort"] = {c: {} for c in coinmarketcap_model.FILTERS}
|
||||
|
||||
choices["top"]["-s"] = choices["top"]["--sort"]
|
||||
|
||||
self.completer = NestedCompleter.from_nested_dict(choices)
|
||||
|
||||
def print_help(self):
|
||||
@@ -78,11 +91,6 @@ class DiscoveryController(BaseController):
|
||||
else:
|
||||
argument_sort_default = "CMC_Rank"
|
||||
|
||||
if ordered_list_sources_top and ordered_list_sources_top[0] == "CoinGecko":
|
||||
argument_sort_choices = pycoingecko_view.COINS_COLUMNS
|
||||
else:
|
||||
argument_sort_choices = coinmarketcap_model.FILTERS
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="top",
|
||||
add_help=False,
|
||||
@@ -121,7 +129,6 @@ class DiscoveryController(BaseController):
|
||||
nargs="+",
|
||||
help="Sort by given column. Default: Market Cap Rank",
|
||||
default=argument_sort_default,
|
||||
choices=argument_sort_choices,
|
||||
metavar="SORTBY",
|
||||
)
|
||||
parser.add_argument(
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
"""Econ Data Helper Module"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import mplfinance as mpf
|
||||
import pandas as pd
|
||||
|
||||
from openbb_terminal.stocks import stocks_helper
|
||||
|
||||
|
||||
# TODO: Is this chart not used and can be removed?
|
||||
# pylint: disable=too-many-arguments
|
||||
def draw_graph(
|
||||
ticker: str,
|
||||
report_cache_dir: str,
|
||||
time_delta: int = 180,
|
||||
line_type: str = "candle",
|
||||
draw_mas: tuple = (20, 50),
|
||||
draw_volume: bool = True,
|
||||
high_trend: bool = True,
|
||||
low_trend: bool = True,
|
||||
):
|
||||
"""Draw Graph is a helper that wraps mplfinance, caching and trendline
|
||||
Parameters
|
||||
----------
|
||||
ticker : str
|
||||
Stock ticker to draw
|
||||
report_cache_dir : str
|
||||
Ticker data cache directory to use
|
||||
time_delta : int, optional
|
||||
Graph period in days, by default 180
|
||||
line_type : str, optional
|
||||
Graph line type to pass to mplfinance, by default "candle"
|
||||
draw_mas : tuple, optional
|
||||
SMAs to draw, by default (20, 50)
|
||||
draw_volume : bool, optional
|
||||
Switch to draw trading volume by default True
|
||||
high_trend : bool, optional
|
||||
Switch to draw high trend line, by default True
|
||||
low_trend : bool, optional
|
||||
Switch to draw low trend line, by default True
|
||||
"""
|
||||
if report_cache_dir:
|
||||
df_stock_cache = pathlib.Path(
|
||||
report_cache_dir, f"{ticker}_stock_data_{time_delta}.pkl"
|
||||
)
|
||||
if os.path.isfile(df_stock_cache):
|
||||
df_stock = pd.read_pickle(df_stock_cache)
|
||||
else:
|
||||
df_stock = stocks_helper.load_ticker(
|
||||
ticker,
|
||||
(datetime.now() - timedelta(days=time_delta)).strftime("%Y-%m-%d"),
|
||||
)
|
||||
df_stock = stocks_helper.find_trendline(df_stock, "OC_High", "high")
|
||||
df_stock = stocks_helper.find_trendline(df_stock, "OC_Low", "low")
|
||||
df_stock.to_pickle(df_stock_cache)
|
||||
|
||||
else:
|
||||
df_stock = stocks_helper.load_ticker(
|
||||
ticker, (datetime.now() - timedelta(days=time_delta)).strftime("%Y-%m-%d")
|
||||
)
|
||||
df_stock = stocks_helper.find_trendline(df_stock, "OC_High", "high")
|
||||
df_stock = stocks_helper.find_trendline(df_stock, "OC_Low", "low")
|
||||
|
||||
mc = mpf.make_marketcolors(
|
||||
up="green", down="red", edge="black", wick="black", volume="in", ohlc="i"
|
||||
)
|
||||
s = mpf.make_mpf_style(marketcolors=mc, gridstyle=":", y_on_right=True)
|
||||
|
||||
ap0 = []
|
||||
|
||||
if "OC_High_trend" in df_stock.columns and high_trend:
|
||||
ap0.append(
|
||||
mpf.make_addplot(df_stock["OC_High_trend"], color="g"),
|
||||
)
|
||||
|
||||
if "OC_Low_trend" in df_stock.columns and low_trend:
|
||||
ap0.append(
|
||||
mpf.make_addplot(df_stock["OC_Low_trend"], color="b"),
|
||||
)
|
||||
|
||||
mpf.plot(
|
||||
df_stock,
|
||||
type=line_type,
|
||||
mav=draw_mas,
|
||||
volume=draw_volume,
|
||||
addplot=ap0,
|
||||
xrotation=0,
|
||||
style=s,
|
||||
figratio=(10, 7),
|
||||
figscale=2.00,
|
||||
update_width_config=dict(
|
||||
candle_linewidth=1.0, candle_width=0.8, volume_linewidth=1.0
|
||||
),
|
||||
)
|
||||
@@ -230,7 +230,7 @@ def show_treasuries(
|
||||
export,
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"treasuries_data",
|
||||
treasury_data,
|
||||
treasury_data / 100,
|
||||
sheet_name,
|
||||
)
|
||||
|
||||
|
||||
@@ -257,6 +257,7 @@ class EconomyController(BaseController):
|
||||
mt.add_cmd("bigmac")
|
||||
# Comment out spread while investpy is donw :()
|
||||
# mt.add_cmd("spread")
|
||||
mt.add_cmd("ycrv")
|
||||
mt.add_cmd("events")
|
||||
mt.add_cmd("edebt")
|
||||
mt.add_raw("\n")
|
||||
|
||||
@@ -1,532 +0,0 @@
|
||||
""" Investing.com Model """
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import logging
|
||||
import math
|
||||
from typing import Dict, List, Tuple, Union
|
||||
|
||||
import investpy
|
||||
import pandas as pd
|
||||
import pytz
|
||||
from tqdm import tqdm
|
||||
|
||||
from openbb_terminal import helper_funcs
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.rich_config import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
BOND_COUNTRIES = investpy.bonds.get_bond_countries()
|
||||
CALENDAR_COUNTRIES = list(investpy.utils.constant.COUNTRY_ID_FILTERS.keys()) + ["all"]
|
||||
CATEGORIES = [
|
||||
"employment",
|
||||
"credit",
|
||||
"balance",
|
||||
"economic_activity",
|
||||
"central_banks",
|
||||
"bonds",
|
||||
"inflation",
|
||||
"confidence_index",
|
||||
]
|
||||
IMPORTANCES = ["high", "medium", "low", "all"]
|
||||
|
||||
# Commented countries either have no data or are not correctly formatted in investpy itself
|
||||
MATRIX_COUNTRIES = {
|
||||
"G7": [
|
||||
"United states",
|
||||
"Canada",
|
||||
"Japan",
|
||||
"Germany",
|
||||
"France",
|
||||
"Italy",
|
||||
"United Kingdom",
|
||||
],
|
||||
"PIIGS": [
|
||||
"Portugal",
|
||||
"Italy",
|
||||
"Ireland",
|
||||
"Greece",
|
||||
"Spain",
|
||||
],
|
||||
"EZ": [
|
||||
"Austria",
|
||||
"Belgium",
|
||||
"Cyprus",
|
||||
# "Estonia",
|
||||
"Finland",
|
||||
"France",
|
||||
"Germany",
|
||||
"Greece",
|
||||
"Ireland",
|
||||
"Italy",
|
||||
# "Latvia",
|
||||
# "Lithuania",
|
||||
# "Luxembourg",
|
||||
"Malta",
|
||||
"Netherlands",
|
||||
"Portugal",
|
||||
"Slovakia",
|
||||
"Slovenia",
|
||||
"Spain",
|
||||
],
|
||||
"AMERICAS": [
|
||||
"Brazil",
|
||||
"Canada",
|
||||
"Chile",
|
||||
"Colombia",
|
||||
"Mexico",
|
||||
"Peru",
|
||||
"United states",
|
||||
],
|
||||
"EUROPE": [
|
||||
"Austria",
|
||||
"Belgium",
|
||||
"Bulgaria",
|
||||
"Croatia",
|
||||
"Cyprus",
|
||||
# "Czech Republic",
|
||||
"Finland",
|
||||
"France",
|
||||
"Germany",
|
||||
"Greece",
|
||||
"Hungary",
|
||||
"Iceland",
|
||||
"Ireland",
|
||||
"Italy",
|
||||
"Malta",
|
||||
"Netherlands",
|
||||
"Norway",
|
||||
"Poland",
|
||||
"Portugal",
|
||||
"Romania",
|
||||
"Russia",
|
||||
"Serbia",
|
||||
"Slovakia",
|
||||
"Slovenia",
|
||||
"Spain",
|
||||
"Switzerland",
|
||||
"Turkey",
|
||||
# "Ukraine",
|
||||
"United Kingdom",
|
||||
],
|
||||
"ME": [
|
||||
# "Bahrain",
|
||||
"Egypt",
|
||||
"Israel",
|
||||
"Jordan",
|
||||
"Qatar",
|
||||
],
|
||||
"APAC": [
|
||||
"Australia",
|
||||
"Bangladesh",
|
||||
"China",
|
||||
# "Hong Kong",
|
||||
"India",
|
||||
"Indonesia",
|
||||
"Japan",
|
||||
# "Kazakhstan",
|
||||
"Malaysia",
|
||||
# "New Zealand",
|
||||
"Pakistan",
|
||||
"Philippines",
|
||||
"Singapore",
|
||||
# "South Korea",
|
||||
# "Sri Lanka",
|
||||
"Taiwan",
|
||||
"Vietnam",
|
||||
],
|
||||
"AFRICA": [
|
||||
# "Botswana",
|
||||
"Kenya",
|
||||
"Mauritius",
|
||||
"Morocco",
|
||||
"Namibia",
|
||||
"Nigeria",
|
||||
# "South Africa",
|
||||
"Uganda",
|
||||
],
|
||||
}
|
||||
|
||||
MATRIX_CHOICES = list(MATRIX_COUNTRIES.keys())
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def check_correct_country(country: str, countries: list) -> bool:
|
||||
"""Check if country is in list and warn if not."""
|
||||
if country.lower() not in countries:
|
||||
joined_countries = [x.replace(" ", "_").lower() for x in countries]
|
||||
choices = ", ".join(joined_countries)
|
||||
console.print(
|
||||
f"[red]'{country}' is an invalid country. Choose from {choices}[/red]\n"
|
||||
)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def countries_string_to_list(countries_list: str) -> List[str]:
|
||||
"""Transform countries string to list if countries valid
|
||||
|
||||
Parameters
|
||||
----------
|
||||
countries_list : str
|
||||
String of countries separated by commas
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[str]
|
||||
List of countries
|
||||
"""
|
||||
valid_countries = [
|
||||
country.lower().strip()
|
||||
for country in countries_list.split(",")
|
||||
if check_correct_country(country.strip(), BOND_COUNTRIES)
|
||||
]
|
||||
|
||||
if valid_countries:
|
||||
return valid_countries
|
||||
raise argparse.ArgumentTypeError("No valid countries provided.")
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def create_matrix(dictionary: Dict[str, Dict[str, float]]) -> pd.DataFrame:
|
||||
"""Create matrix of yield and spreads.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dictionary: Dict[str, Dict[str, float]]
|
||||
Dictionary of yield data by country. E.g. {'10Y': {'United States': 4.009, 'Canada': 3.48}}
|
||||
|
||||
Returns
|
||||
-------
|
||||
pd.DataFrame
|
||||
Spread matrix.
|
||||
"""
|
||||
|
||||
maturity = list(dictionary.keys())[0]
|
||||
d = dictionary[maturity]
|
||||
countries = list(d.keys())
|
||||
|
||||
# Create empty matrix
|
||||
matrix: List[List[float]] = []
|
||||
N = len(d)
|
||||
for i in range(N):
|
||||
matrix.append([0] * N)
|
||||
|
||||
for i, country_i in enumerate(countries):
|
||||
for j, country_j in enumerate(countries):
|
||||
matrix[i][j] = round((d[country_i] - d[country_j]) * 100, 1)
|
||||
|
||||
matrixdf = pd.DataFrame(matrix)
|
||||
matrixdf.columns = list(d.keys())
|
||||
matrixdf = matrixdf.set_index(matrixdf.columns)
|
||||
matrixdf.insert(
|
||||
0, "Yield " + maturity, pd.DataFrame.from_dict(d, orient="index") * 100
|
||||
)
|
||||
|
||||
return matrixdf
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_spread_matrix(
|
||||
countries: Union[str, List[str]] = "G7",
|
||||
maturity: str = "10Y",
|
||||
change: bool = False,
|
||||
) -> pd.DataFrame:
|
||||
"""Get spread matrix. [Source: Investing.com]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
countries: Union[str, List[str]]
|
||||
Countries or group of countries. List of available countries is accessible through get_ycrv_countries().
|
||||
maturity: str
|
||||
Maturity to get data. By default 10Y.
|
||||
change: bool
|
||||
Flag to use 1 day change or not. By default False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pd.DataFrame
|
||||
Spread matrix.
|
||||
"""
|
||||
|
||||
if isinstance(countries, str) and countries.upper() in MATRIX_CHOICES:
|
||||
countries = MATRIX_COUNTRIES[countries.upper()]
|
||||
|
||||
d0: Dict[str, Dict[str, float]] = {maturity: {}}
|
||||
d1: Dict[str, Dict[str, float]] = {maturity: {}}
|
||||
no_data_countries = []
|
||||
for country in tqdm(countries, desc="Downloading"):
|
||||
country = country.title()
|
||||
try:
|
||||
df = investpy.bonds.get_bonds_overview(country)
|
||||
d0[maturity][country] = df[df["name"].str.contains(maturity)]["last"].iloc[
|
||||
0
|
||||
]
|
||||
d1[maturity][country] = df[df["name"].str.contains(maturity)][
|
||||
"last_close"
|
||||
].iloc[0]
|
||||
except Exception:
|
||||
no_data_countries.append(country)
|
||||
|
||||
if no_data_countries:
|
||||
s = ", ".join(no_data_countries)
|
||||
console.print(f"[red]No data for {s}.[/red]")
|
||||
|
||||
if change:
|
||||
return create_matrix(d0) - create_matrix(d1)
|
||||
return create_matrix(d0)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_ycrv_countries() -> List[str]:
|
||||
"""Get available countries for ycrv command.
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[str]
|
||||
List of available countries.
|
||||
"""
|
||||
return BOND_COUNTRIES
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_events_countries() -> List[str]:
|
||||
"""Get available countries for events command.
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[str]
|
||||
List of available countries.
|
||||
"""
|
||||
return CALENDAR_COUNTRIES
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_events_categories() -> List[str]:
|
||||
"""Get available event categories for events command.
|
||||
|
||||
Returns
|
||||
-------
|
||||
List[str]
|
||||
List of available event categories.
|
||||
"""
|
||||
return CATEGORIES
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_yieldcurve(country: str = "United States") -> pd.DataFrame:
|
||||
"""Get yield curve for specified country. [Source: Investing.com]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
country: str
|
||||
Country to display yield curve. List of available countries is accessible through get_ycrv_countries().
|
||||
|
||||
Returns
|
||||
-------
|
||||
pd.DataFrame
|
||||
Country yield curve
|
||||
"""
|
||||
|
||||
if not check_correct_country(country, BOND_COUNTRIES):
|
||||
return pd.DataFrame()
|
||||
|
||||
try:
|
||||
data = investpy.bonds.get_bonds_overview(country)
|
||||
except Exception:
|
||||
console.print(f"[red]Yield curve data not found for {country}.[/red]\n")
|
||||
return pd.DataFrame()
|
||||
|
||||
data.drop(columns=data.columns[0], axis=1, inplace=True)
|
||||
data.rename(
|
||||
columns={
|
||||
"name": "Tenor",
|
||||
"last": "Current",
|
||||
"last_close": "Previous",
|
||||
"high": "High",
|
||||
"low": "Low",
|
||||
"change": "Change",
|
||||
"change_percentage": "% Change",
|
||||
},
|
||||
inplace=True,
|
||||
)
|
||||
|
||||
data = data.replace(float("NaN"), "")
|
||||
|
||||
data["Change"] = (data["Current"] - data["Previous"]) * 100
|
||||
|
||||
for i, row in data.iterrows():
|
||||
t = row["Tenor"][-3:].strip()
|
||||
data.at[i, "Tenor"] = t
|
||||
if t[-1] == "M":
|
||||
data.at[i, "Tenor"] = int(t[:-1]) / 12
|
||||
elif t[-1] == "Y":
|
||||
data.at[i, "Tenor"] = int(t[:-1])
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def format_date(date: datetime.date) -> str:
|
||||
year = str(date.year)
|
||||
if date.month < 10:
|
||||
month = "0" + str(date.month)
|
||||
else:
|
||||
month = str(date.month)
|
||||
if date.day < 10:
|
||||
day = "0" + str(date.day)
|
||||
else:
|
||||
day = str(date.day)
|
||||
|
||||
return day + "/" + month + "/" + year
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_economic_calendar(
|
||||
country: str = "all",
|
||||
importance: str = "",
|
||||
category: str = "",
|
||||
start_date: str = "",
|
||||
end_date: str = "",
|
||||
limit=100,
|
||||
) -> Tuple[pd.DataFrame, str]:
|
||||
"""Get economic calendar [Source: Investing.com]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
country: str
|
||||
Country selected. List of available countries is accessible through get_events_countries().
|
||||
importance: str
|
||||
Importance selected from high, medium, low or all
|
||||
category: str
|
||||
Event category. List of available categories is accessible through get_events_categories().
|
||||
start_date: datetime.date
|
||||
First date to get events.
|
||||
end_date: datetime.date
|
||||
Last date to get events.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple[pd.DataFrame, str]
|
||||
Economic calendar Dataframe and detail string about country/time zone.
|
||||
"""
|
||||
|
||||
if not check_correct_country(country, CALENDAR_COUNTRIES):
|
||||
return pd.DataFrame(), ""
|
||||
|
||||
time_filter = "time_only"
|
||||
|
||||
countries_list = []
|
||||
importances_list = []
|
||||
categories_list = []
|
||||
|
||||
if country:
|
||||
countries_list = [country.lower()]
|
||||
if importance:
|
||||
importances_list = [importance.lower()]
|
||||
if category:
|
||||
categories_list = [category.title()]
|
||||
|
||||
# Joint default for countries and importances
|
||||
if countries_list == ["all"]:
|
||||
countries_list = CALENDAR_COUNTRIES[:-1]
|
||||
if not importances_list:
|
||||
importances_list = ["high"]
|
||||
elif importances_list is None:
|
||||
importances_list = ["all"]
|
||||
|
||||
if start_date and not end_date:
|
||||
end_date_string = (
|
||||
datetime.datetime.strptime(start_date, "%Y-%m-%d")
|
||||
+ datetime.timedelta(days=7)
|
||||
).strftime("%d/%m/%Y")
|
||||
start_date_string = (
|
||||
datetime.datetime.strptime(start_date, "%Y-%m-%d")
|
||||
).strftime("%d/%m/%Y")
|
||||
elif end_date and not start_date:
|
||||
start_date_string = (
|
||||
datetime.datetime.strptime(end_date, "%Y-%m-%d")
|
||||
+ datetime.timedelta(days=-7)
|
||||
).strftime("%d/%m/%Y")
|
||||
end_date_string = (datetime.datetime.strptime(end_date, "%Y-%m-%d")).strftime(
|
||||
"%d/%m/%Y"
|
||||
)
|
||||
elif end_date and start_date:
|
||||
start_date_string = (
|
||||
datetime.datetime.strptime(start_date, "%Y-%m-%d")
|
||||
).strftime("%d/%m/%Y")
|
||||
end_date_string = (datetime.datetime.strptime(end_date, "%Y-%m-%d")).strftime(
|
||||
"%d/%m/%Y"
|
||||
)
|
||||
else:
|
||||
start_date_string = None
|
||||
end_date_string = None
|
||||
|
||||
# Get user time zone in GMT offset format
|
||||
user_time_zone = pytz.timezone(helper_funcs.get_user_timezone())
|
||||
diff = pd.Timestamp.now(tz=user_time_zone).tz_localize(
|
||||
None
|
||||
) - pd.Timestamp.utcnow().tz_localize(None)
|
||||
|
||||
# Ceil time difference, might have actual decimal difference
|
||||
# between .now() and .utcnow()
|
||||
offset = divmod(math.ceil(diff.total_seconds()), 3600)[0]
|
||||
sign = "+" if offset > 0 else ""
|
||||
time_zone = "GMT " + sign + str(int(offset)) + ":00"
|
||||
|
||||
args = [
|
||||
time_filter,
|
||||
countries_list,
|
||||
importances_list,
|
||||
categories_list,
|
||||
start_date_string,
|
||||
end_date_string,
|
||||
]
|
||||
try:
|
||||
data = investpy.news.economic_calendar(time_zone, *args)
|
||||
except Exception:
|
||||
try:
|
||||
data = investpy.news.economic_calendar(None, *args)
|
||||
except Exception:
|
||||
console.print(
|
||||
f"[red]Economic calendar data not found for {country}.[/red]\n"
|
||||
)
|
||||
return pd.DataFrame(), ""
|
||||
|
||||
if data.empty:
|
||||
logger.error("No data")
|
||||
console.print("[red]No data.[/red]\n")
|
||||
return pd.DataFrame(), ""
|
||||
|
||||
data.drop(columns=data.columns[0], axis=1, inplace=True)
|
||||
data.drop_duplicates(keep="first", inplace=True)
|
||||
data["date"] = data["date"].apply(
|
||||
lambda date: date[-4:] + "-" + date[3:5] + "-" + date[:2]
|
||||
)
|
||||
data.sort_values(by=data.columns[0], inplace=True)
|
||||
|
||||
if importances_list:
|
||||
if importances_list == ["all"]:
|
||||
importances_list = IMPORTANCES
|
||||
data = data[data["importance"].isin(importances_list)]
|
||||
|
||||
if time_zone is None:
|
||||
time_zone = "GMT"
|
||||
console.print("[red]Error on timezone, default was used.[/red]\n")
|
||||
|
||||
data.fillna(value="", inplace=True)
|
||||
data.columns = data.columns.str.title()
|
||||
if len(countries_list) == 1:
|
||||
del data["Zone"]
|
||||
detail = f"{country.title()} economic calendar ({time_zone})"
|
||||
else:
|
||||
detail = f"Economic Calendar ({time_zone})"
|
||||
data["Zone"] = data["Zone"].str.title()
|
||||
|
||||
data["Importance"] = data["Importance"].str.title()
|
||||
|
||||
data = data[:limit]
|
||||
|
||||
return data, detail
|
||||
@@ -1,347 +0,0 @@
|
||||
""" Investing.com View """
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import seaborn as sns
|
||||
from matplotlib import colors, ticker
|
||||
from matplotlib.ticker import FormatStrFormatter
|
||||
from pandas.plotting import register_matplotlib_converters
|
||||
|
||||
from openbb_terminal.config_plot import PLOT_DPI
|
||||
from openbb_terminal.config_terminal import theme
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.economy import investingcom_model
|
||||
from openbb_terminal.economy.economy_helpers import text_transform
|
||||
from openbb_terminal.helper_funcs import (
|
||||
export_data,
|
||||
is_valid_axes_count,
|
||||
plot_autoscale,
|
||||
print_rich_table,
|
||||
)
|
||||
from openbb_terminal.rich_config import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
register_matplotlib_converters()
|
||||
|
||||
COLORS = ["rgb", "binary", "openbb"]
|
||||
|
||||
# pylint: disable=unnecessary-lambda-assignment
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def display_spread_matrix(
|
||||
countries: Union[str, List[str]] = "G7",
|
||||
maturity: str = "10Y",
|
||||
change: bool = False,
|
||||
color: str = "openbb",
|
||||
raw: bool = False,
|
||||
external_axes: Optional[List[plt.Axes]] = None,
|
||||
export: str = "",
|
||||
sheet_name: str = None,
|
||||
):
|
||||
"""Display spread matrix. [Source: Investing.com]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
countries: Union[str, List[str]]
|
||||
Countries or group of countries. List of available countries is accessible through get_ycrv_countries().
|
||||
maturity: str
|
||||
Maturity to get data. By default 10Y.
|
||||
change: bool
|
||||
Flag to use 1 day change or not. By default False.
|
||||
color: str
|
||||
Color theme to use on heatmap, from rgb, binary or openbb By default, openbb.
|
||||
raw : bool
|
||||
Output only raw data.
|
||||
export : str
|
||||
Export dataframe data to csv,json,xlsx file
|
||||
external_axes : Optional[List[plt.Axes]], optional
|
||||
External axes (1 axis is expected in the list), by default None
|
||||
|
||||
"""
|
||||
|
||||
df = investingcom_model.get_spread_matrix(countries, maturity, change)
|
||||
|
||||
if not df.empty:
|
||||
if raw:
|
||||
pretty_df = df.copy()
|
||||
|
||||
# Convert to string spreads
|
||||
pretty_df[list(pretty_df.columns)[1:]] = pretty_df[
|
||||
list(pretty_df.columns)[1:]
|
||||
].applymap(lambda x: f"{x:+.1f}" if x != 0 else "")
|
||||
|
||||
# Convert to string yields
|
||||
pretty_df[list(pretty_df.columns)[0]] = pd.DataFrame(
|
||||
df[list(pretty_df.columns)[0]]
|
||||
).applymap(lambda x: f"{x/100:.3f}%" if not change else f"{x:+.1f}")
|
||||
|
||||
# Add colors
|
||||
pretty_df = pretty_df.applymap(
|
||||
lambda x: f"[{theme.down_color}]{x}[/{theme.down_color}]"
|
||||
if "-" in x
|
||||
else f"[{theme.up_color}]{x}[/{theme.up_color}]"
|
||||
)
|
||||
|
||||
if isinstance(countries, str):
|
||||
title = f"{countries} - Spread Matrix - {maturity}"
|
||||
else:
|
||||
title = f"Spread Matrix - {maturity}"
|
||||
|
||||
print_rich_table(
|
||||
pretty_df,
|
||||
headers=list(pretty_df.columns),
|
||||
show_index=True,
|
||||
title=title,
|
||||
)
|
||||
|
||||
else:
|
||||
# This plot has 1 axis
|
||||
if not external_axes:
|
||||
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
elif is_valid_axes_count(external_axes, 1):
|
||||
(ax,) = external_axes
|
||||
else:
|
||||
return
|
||||
|
||||
mask = np.zeros((df.shape[0], df.shape[1]), dtype=bool)
|
||||
mask[np.tril_indices(len(mask))] = True
|
||||
mask[:, 0] = False
|
||||
for i in range(df.shape[0]):
|
||||
mask[i][i + 1] = True
|
||||
|
||||
x_labels = list(df.columns)
|
||||
x_labels[1] = ""
|
||||
|
||||
# https://stackoverflow.com/questions/53754012/create-a-gradient-colormap-matplotlib
|
||||
def truncate_colormap(cmap, minval=0.0, maxval=1.0, n=100):
|
||||
new_cmap = colors.LinearSegmentedColormap.from_list(
|
||||
f"trunc({cmap.name},{minval:.2f},{maxval:.2f})",
|
||||
cmap(np.linspace(minval, maxval, n)),
|
||||
)
|
||||
return new_cmap
|
||||
|
||||
# This is not a bool so that we can add different colors in future
|
||||
if color.lower() == "rgb":
|
||||
cmap = truncate_colormap(plt.get_cmap("brg"), 1, 0.4)
|
||||
elif color.lower() == "openbb":
|
||||
cmap = truncate_colormap(plt.get_cmap("magma"), 0.5, 0.1)
|
||||
else: # binary
|
||||
cmap = colors.ListedColormap([theme.up_color, theme.down_color])
|
||||
|
||||
heatmap = sns.heatmap(
|
||||
df,
|
||||
cmap=cmap,
|
||||
cbar=False,
|
||||
annot=True,
|
||||
annot_kws={
|
||||
"fontsize": 12,
|
||||
},
|
||||
center=0,
|
||||
fmt="+.1f",
|
||||
linewidths=0.5,
|
||||
linecolor="black"
|
||||
if any(substring in theme.mpl_style for substring in ["dark", "boring"])
|
||||
else "white",
|
||||
xticklabels=x_labels,
|
||||
mask=mask,
|
||||
ax=ax,
|
||||
)
|
||||
|
||||
ax.xaxis.tick_top()
|
||||
ax.xaxis.set_major_locator(
|
||||
ticker.FixedLocator([x + 0.25 for x in ax.get_xticks().tolist()])
|
||||
)
|
||||
ax.set_xticklabels(x_labels, rotation=45)
|
||||
ax.set_yticklabels(list(df.index.values), rotation=0)
|
||||
ax.yaxis.set_label_position("left")
|
||||
|
||||
y_labels = list(df.index.values)
|
||||
y_labels[-1] = ""
|
||||
ax1 = ax.twinx()
|
||||
ticks_loc = ax.get_yticks().tolist()
|
||||
ax1.yaxis.set_major_locator(ticker.FixedLocator(ticks_loc))
|
||||
ax1.set_yticklabels(y_labels, rotation=0)
|
||||
ax1.yaxis.set_label_position("right")
|
||||
ax1.set_ylim(ax.get_ylim())
|
||||
ax1.grid(False)
|
||||
ax1.set_frame_on(False)
|
||||
|
||||
# Set 3 decimal places for yield and 1 spread
|
||||
if not change:
|
||||
spacing = df.shape[1] - 1
|
||||
k = 0
|
||||
for index, t in enumerate(heatmap.texts):
|
||||
current_text = t.get_text()
|
||||
|
||||
if index == k:
|
||||
k += spacing
|
||||
spacing -= 1
|
||||
|
||||
t.set_text(text_transform(current_text))
|
||||
else:
|
||||
t.set_text(current_text)
|
||||
|
||||
if isinstance(countries, str):
|
||||
ax.set_title(f"{countries} - Spread matrix - {maturity}", loc="center")
|
||||
else:
|
||||
ax.set_title(f"Spread matrix - {maturity}", loc="center")
|
||||
|
||||
if not external_axes:
|
||||
theme.visualize_output()
|
||||
|
||||
export_data(
|
||||
export,
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"spread",
|
||||
df,
|
||||
sheet_name,
|
||||
)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def display_yieldcurve(
|
||||
country: str = "United States",
|
||||
external_axes: Optional[List[plt.Axes]] = None,
|
||||
raw: bool = False,
|
||||
export: str = "",
|
||||
sheet_name: str = None,
|
||||
):
|
||||
"""Display yield curve for specified country. [Source: Investing.com]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
country: str
|
||||
Country to display yield curve. List of available countries is accessible through get_ycrv_countries().
|
||||
export : str
|
||||
Export dataframe data to csv,json,xlsx file
|
||||
"""
|
||||
|
||||
country = country.title()
|
||||
df = investingcom_model.get_yieldcurve(country)
|
||||
|
||||
if not df.empty:
|
||||
if external_axes is None:
|
||||
_, (ax1, ax2) = plt.subplots(
|
||||
nrows=2,
|
||||
ncols=1,
|
||||
figsize=plot_autoscale(),
|
||||
dpi=PLOT_DPI,
|
||||
gridspec_kw={"height_ratios": [2, 1]},
|
||||
)
|
||||
|
||||
else:
|
||||
if len(external_axes) != 2:
|
||||
logger.error("Expected list of 3 axis items")
|
||||
console.print("[red]Expected list of 3 axis items.\n[/red]")
|
||||
return
|
||||
(ax1, ax2) = external_axes
|
||||
|
||||
ax1.plot(
|
||||
df["Tenor"],
|
||||
df["Previous"],
|
||||
linestyle="--",
|
||||
marker="o",
|
||||
label="Previous",
|
||||
)
|
||||
ax1.plot(df["Tenor"], df["Current"], "-o", label="Current")
|
||||
ax1.set_ylabel("Yield (%)")
|
||||
theme.style_primary_axis(ax1)
|
||||
ax1.yaxis.set_label_position("left")
|
||||
ax1.yaxis.set_ticks_position("left")
|
||||
ax1.yaxis.set_major_formatter(FormatStrFormatter("%.2f"))
|
||||
ax1.legend(
|
||||
loc="lower right",
|
||||
prop={"size": 9},
|
||||
ncol=3,
|
||||
)
|
||||
|
||||
colors_ = [
|
||||
theme.up_color if x > 0 else theme.down_color for x in df["Change"].values
|
||||
]
|
||||
ax2.bar(df["Tenor"], df["Change"], width=1, color=colors_)
|
||||
ax2.set_ylabel("Change (bps)")
|
||||
ax2.set_xlabel("Maturity (years)")
|
||||
theme.style_primary_axis(ax2)
|
||||
ax2.yaxis.set_label_position("left")
|
||||
ax2.yaxis.set_ticks_position("left")
|
||||
|
||||
if external_axes is None:
|
||||
ax1.set_title(f"Yield Curve - {country.title()} ")
|
||||
theme.visualize_output()
|
||||
|
||||
if raw:
|
||||
print_rich_table(
|
||||
df,
|
||||
headers=list(df.columns),
|
||||
show_index=False,
|
||||
title=f"{country.title()} Yield Curve",
|
||||
floatfmt=".3f",
|
||||
)
|
||||
|
||||
export_data(
|
||||
export,
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"ycrv",
|
||||
df,
|
||||
sheet_name,
|
||||
)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def display_economic_calendar(
|
||||
country: str = "all",
|
||||
importance: str = "",
|
||||
category: str = "",
|
||||
start_date: str = "",
|
||||
end_date: str = "",
|
||||
limit=100,
|
||||
export: str = "",
|
||||
sheet_name: str = None,
|
||||
):
|
||||
"""Display economic calendar. [Source: Investing.com]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
country: str
|
||||
Country selected. List of available countries is accessible through get_events_countries().
|
||||
importances: str
|
||||
Importance selected from high, medium, low or all.
|
||||
categories: str
|
||||
Event category. List of available categories is accessible through get_events_categories().
|
||||
start_date: datetime.date
|
||||
First date to get events.
|
||||
end_date: datetime.date
|
||||
Last date to get events.
|
||||
limit: int
|
||||
The maximum number of events to show, default is 100.
|
||||
export : str
|
||||
Export dataframe data to csv,json,xlsx file
|
||||
"""
|
||||
|
||||
df, detail = investingcom_model.get_economic_calendar(
|
||||
country, importance, category, start_date, end_date, limit
|
||||
)
|
||||
|
||||
if not df.empty:
|
||||
print_rich_table(
|
||||
df,
|
||||
headers=list(df.columns),
|
||||
show_index=False,
|
||||
title=detail,
|
||||
)
|
||||
|
||||
export_data(
|
||||
export,
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"events",
|
||||
df,
|
||||
sheet_name,
|
||||
)
|
||||
@@ -638,6 +638,7 @@ def get_index(
|
||||
interval=interval,
|
||||
progress=False,
|
||||
show_errors=False,
|
||||
ignore_tz=True,
|
||||
)
|
||||
|
||||
if column not in index_data.columns:
|
||||
|
||||
@@ -8,7 +8,6 @@ from datetime import datetime, timedelta
|
||||
from typing import List
|
||||
|
||||
import yfinance as yf
|
||||
from thepassiveinvestor import create_ETF_report
|
||||
|
||||
from openbb_terminal import feature_flags as obbff
|
||||
from openbb_terminal.common import newsapi_view
|
||||
@@ -16,11 +15,9 @@ from openbb_terminal.common.quantitative_analysis import qa_view
|
||||
from openbb_terminal.custom_prompt_toolkit import NestedCompleter
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.etf import (
|
||||
etf_helper,
|
||||
financedatabase_view,
|
||||
stockanalysis_model,
|
||||
fmp_view,
|
||||
stockanalysis_view,
|
||||
yfinance_view,
|
||||
)
|
||||
from openbb_terminal.etf.discovery import disc_controller
|
||||
from openbb_terminal.etf.screener import screener_controller
|
||||
@@ -29,7 +26,6 @@ from openbb_terminal.helper_funcs import (
|
||||
EXPORT_BOTH_RAW_DATA_AND_FIGURES,
|
||||
EXPORT_ONLY_RAW_DATA_ALLOWED,
|
||||
check_positive,
|
||||
compose_export_path,
|
||||
export_data,
|
||||
list_from_str,
|
||||
valid_date,
|
||||
@@ -56,9 +52,7 @@ class ETFController(BaseController):
|
||||
"holdings",
|
||||
"news",
|
||||
"candle",
|
||||
"pir",
|
||||
"weights",
|
||||
"summary",
|
||||
"compare",
|
||||
"resources",
|
||||
]
|
||||
@@ -113,11 +107,9 @@ class ETFController(BaseController):
|
||||
mt.add_cmd("overview", self.etf_name)
|
||||
mt.add_cmd("holdings", self.etf_name)
|
||||
mt.add_cmd("weights", self.etf_name)
|
||||
mt.add_cmd("summary", self.etf_name)
|
||||
mt.add_cmd("news", self.etf_name)
|
||||
mt.add_cmd("candle", self.etf_name)
|
||||
mt.add_raw("\n")
|
||||
mt.add_cmd("pir", self.etf_name)
|
||||
mt.add_cmd("compare", self.etf_name)
|
||||
mt.add_raw("\n")
|
||||
mt.add_menu("ta", self.etf_name)
|
||||
@@ -265,41 +257,11 @@ class ETFController(BaseController):
|
||||
|
||||
self.etf_name = ns_parser.ticker.upper()
|
||||
self.etf_data = df_etf_candidate
|
||||
holdings = stockanalysis_model.get_etf_holdings(self.etf_name)
|
||||
if holdings.empty:
|
||||
quote_type = etf_helper.get_quote_type(self.etf_name)
|
||||
if quote_type != "ETF":
|
||||
if quote_type == "N/A":
|
||||
console.print(
|
||||
"[red]Cannot determine ticker type. Holdings only shown for ETFs\n[/red]"
|
||||
)
|
||||
else:
|
||||
console.print(f"{self.etf_name} is: {quote_type.lower()}")
|
||||
console.print("No company holdings found!")
|
||||
else:
|
||||
self.etf_holdings.clear()
|
||||
console.print("Top holdings found:")
|
||||
for val in holdings["Name"].values[: ns_parser.limit].tolist():
|
||||
console.print(f" {val}")
|
||||
|
||||
for tick, name in zip(
|
||||
holdings.index[: ns_parser.limit].tolist(),
|
||||
holdings["Name"].values[: ns_parser.limit].tolist(),
|
||||
):
|
||||
if tick != "N/A" and " " not in tick:
|
||||
if (
|
||||
"ETF" not in name
|
||||
and "Future" not in name
|
||||
and "Bill" not in name
|
||||
and "Portfolio" not in name
|
||||
and "%" not in name
|
||||
):
|
||||
self.etf_holdings.append(tick)
|
||||
|
||||
if not self.etf_holdings:
|
||||
console.print("\n[red]No valid stock ticker was found![/red]")
|
||||
|
||||
console.print()
|
||||
console.print(
|
||||
f"Loading Daily data for {self.etf_name} with starting period {ns_parser.start.strftime('%Y-%m-%d')}.",
|
||||
"\n",
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_overview(self, other_args: List[str]):
|
||||
@@ -415,12 +377,8 @@ class ETFController(BaseController):
|
||||
sources[idx] += ".com"
|
||||
clean_sources = ",".join(sources)
|
||||
|
||||
d_stock = yf.Ticker(self.etf_name).info
|
||||
|
||||
newsapi_view.display_news(
|
||||
query=d_stock["shortName"].replace(" ", "+")
|
||||
if "shortName" in d_stock
|
||||
else self.etf_name,
|
||||
query=self.etf_name,
|
||||
limit=ns_parser.limit,
|
||||
start_date=ns_parser.n_start_date.strftime("%Y-%m-%d"),
|
||||
show_newest=ns_parser.n_oldest,
|
||||
@@ -554,69 +512,6 @@ class ETFController(BaseController):
|
||||
ns_parser.sheet_name,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_pir(self, other_args):
|
||||
"""Process pir command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="pir",
|
||||
description="Create passive investor ETF excel report which contains most of the important metrics "
|
||||
"about an ETF obtained from Yahoo Finnace. You are able to input any ETF ticker you like "
|
||||
"within the command to create am extensive report",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--etfs",
|
||||
type=str,
|
||||
dest="names",
|
||||
help="Symbols to create a report for (e.g. pir ARKW ARKQ QQQ VOO)",
|
||||
default=self.etf_name,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--filename",
|
||||
default=f"ETF_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
|
||||
dest="filename",
|
||||
help="Filename of the excel ETF report",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--folder",
|
||||
default=compose_export_path(
|
||||
func_name=parser.prog,
|
||||
dir_path=os.path.dirname(os.path.abspath(__file__)),
|
||||
).parent,
|
||||
dest="folder",
|
||||
help="Folder where the excel ETF report will be saved",
|
||||
)
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-e")
|
||||
ns_parser = self.parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
etfs = list_from_str(ns_parser.names.upper())
|
||||
if ns_parser.names:
|
||||
# Automatically creates the etf folder inside /OpenBBUserData/exports
|
||||
# if it doesn't exist
|
||||
if not os.path.isdir(ns_parser.folder):
|
||||
os.makedirs(ns_parser.folder)
|
||||
try:
|
||||
create_ETF_report(
|
||||
etfs,
|
||||
filename=ns_parser.filename,
|
||||
folder=ns_parser.folder,
|
||||
)
|
||||
except FileNotFoundError:
|
||||
console.print(
|
||||
f"[red]Could not find the file: {ns_parser.filename}[/red]\n"
|
||||
)
|
||||
return
|
||||
except Exception:
|
||||
console.print("[red]Failed to create report.[/red]\n")
|
||||
return
|
||||
|
||||
console.print(
|
||||
f"Created ETF report as {ns_parser.filename} in folder {ns_parser.folder} \n"
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_weights(self, other_args: List[str]):
|
||||
"""Process weights command"""
|
||||
@@ -626,16 +521,7 @@ class ETFController(BaseController):
|
||||
prog="weights",
|
||||
description="Look at ETF sector holdings",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
"--min",
|
||||
type=check_positive,
|
||||
dest="min",
|
||||
help="Minimum positive float to display sector",
|
||||
default=5,
|
||||
choices=range(1, 100),
|
||||
metavar="MIN",
|
||||
)
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-l")
|
||||
|
||||
@@ -646,34 +532,15 @@ class ETFController(BaseController):
|
||||
raw=True,
|
||||
)
|
||||
if ns_parser:
|
||||
yfinance_view.display_etf_weightings(
|
||||
fmp_view.display_etf_weightings(
|
||||
name=self.etf_name,
|
||||
raw=ns_parser.raw,
|
||||
min_pct_to_display=ns_parser.min,
|
||||
export=ns_parser.export,
|
||||
sheet_name=" ".join(ns_parser.sheet_name)
|
||||
if ns_parser.sheet_name
|
||||
else None,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_summary(self, other_args: List[str]):
|
||||
"""Process summary command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="summary",
|
||||
description="Print ETF description summary",
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(
|
||||
parser,
|
||||
other_args,
|
||||
)
|
||||
if ns_parser:
|
||||
yfinance_view.display_etf_description(
|
||||
name=self.etf_name,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_ta(self, _):
|
||||
"""Process ta command"""
|
||||
|
||||
@@ -42,13 +42,15 @@ def display_etf_by_name(
|
||||
table_data = pd.DataFrame(data).T[
|
||||
["long_name", "family", "category", "total_assets"]
|
||||
]
|
||||
|
||||
# Sort by total assets but it is then dropped due to not being completely up to date
|
||||
table_data_sorted = table_data.sort_values(by="total_assets", ascending=False)
|
||||
table_data_sorted["total_assets"] = table_data_sorted["total_assets"] / 1e6
|
||||
table_data_sorted = table_data_sorted.drop("total_assets", axis=1)
|
||||
|
||||
print_rich_table(
|
||||
table_data_sorted.iloc[:limit],
|
||||
show_index=True,
|
||||
headers=["Name", "Family", "Category", "Total Assets [M]"],
|
||||
headers=["Name", "Family", "Category"],
|
||||
title="ETFs by Total Assets",
|
||||
)
|
||||
|
||||
@@ -90,13 +92,14 @@ def display_etf_by_description(
|
||||
table_data = pd.DataFrame(data).T[
|
||||
["long_name", "family", "category", "total_assets"]
|
||||
]
|
||||
# Sort by total assets but it is then dropped due to not being completely up to date
|
||||
table_data_sorted = table_data.sort_values(by="total_assets", ascending=False)
|
||||
table_data_sorted["total_assets"] = table_data_sorted["total_assets"] / 1e6
|
||||
table_data_sorted = table_data_sorted.drop("total_assets", axis=1)
|
||||
|
||||
print_rich_table(
|
||||
table_data_sorted.iloc[:limit],
|
||||
show_index=True,
|
||||
headers=["Name", "Family", "Category", "Total Assets [M]"],
|
||||
headers=["Name", "Family", "Category"],
|
||||
title="ETFs by Total Assets",
|
||||
)
|
||||
|
||||
@@ -138,13 +141,14 @@ def display_etf_by_category(
|
||||
table_data = pd.DataFrame(data).T[
|
||||
["long_name", "family", "category", "total_assets"]
|
||||
]
|
||||
# Sort by total assets but it is then dropped due to not being completely up to date
|
||||
table_data_sorted = table_data.sort_values(by="total_assets", ascending=False)
|
||||
table_data_sorted["total_assets"] = table_data_sorted["total_assets"] / 1e6
|
||||
table_data_sorted = table_data_sorted.drop("total_assets", axis=1)
|
||||
|
||||
print_rich_table(
|
||||
table_data_sorted.iloc[:limit],
|
||||
show_index=True,
|
||||
headers=["Name", "Family", "Category", "Total Assets [M]"],
|
||||
headers=["Name", "Family", "Category"],
|
||||
title="ETFs by Category and Total Assets",
|
||||
)
|
||||
|
||||
|
||||
48
openbb_terminal/etf/fmp_model.py
Normal file
48
openbb_terminal/etf/fmp_model.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""FinancialModelingPrep model"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import json
|
||||
import logging
|
||||
from typing import Dict
|
||||
from urllib.error import HTTPError
|
||||
from urllib.request import urlopen
|
||||
|
||||
from openbb_terminal import config_terminal as cfg
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.rich_config import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# pylint: disable=consider-using-with
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_etf_sector_weightings(name: str) -> Dict:
|
||||
"""Return sector weightings allocation of ETF. [Source: FinancialModelingPrep]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: str
|
||||
ETF name
|
||||
|
||||
Returns
|
||||
-------
|
||||
Dict[str, Any]
|
||||
Dictionary with sector weightings allocation
|
||||
"""
|
||||
try:
|
||||
response = urlopen(
|
||||
f"https://financialmodelingprep.com/api/v3/etf-sector-weightings/{name}?apikey={cfg.API_KEY_FINANCIALMODELINGPREP}"
|
||||
)
|
||||
data = json.loads(response.read().decode("utf-8"))
|
||||
except HTTPError:
|
||||
console.print(
|
||||
"This endpoint is only for premium members. Please visit the subscription page to upgrade the "
|
||||
"plan (Starter or higher) at https://financialmodelingprep.com/developer/docs/pricing"
|
||||
)
|
||||
return dict()
|
||||
|
||||
if "Error Message" in data:
|
||||
raise ValueError(data["Error Message"])
|
||||
|
||||
return data
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Yahoo Finance view"""
|
||||
"""FinancialModelingPrep view"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import logging
|
||||
@@ -11,7 +11,7 @@ from matplotlib import pyplot as plt
|
||||
from openbb_terminal.config_plot import PLOT_DPI
|
||||
from openbb_terminal.config_terminal import theme
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.etf import yfinance_model
|
||||
from openbb_terminal.etf import fmp_model
|
||||
from openbb_terminal.helper_funcs import (
|
||||
export_data,
|
||||
is_valid_axes_count,
|
||||
@@ -27,12 +27,11 @@ logger = logging.getLogger(__name__)
|
||||
def display_etf_weightings(
|
||||
name: str,
|
||||
raw: bool = False,
|
||||
min_pct_to_display: float = 5,
|
||||
export: str = "",
|
||||
sheet_name: str = None,
|
||||
external_axes: Optional[List[plt.Axes]] = None,
|
||||
):
|
||||
"""Display sector weightings allocation of ETF. [Source: Yahoo Finance]
|
||||
"""Display sector weightings allocation of ETF. [Source: FinancialModelingPrep]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -49,34 +48,32 @@ def display_etf_weightings(
|
||||
external_axes : Optional[List[plt.Axes]], optional
|
||||
External axes (1 axis is expected in the list), by default None
|
||||
"""
|
||||
sectors = yfinance_model.get_etf_sector_weightings(name)
|
||||
sectors = fmp_model.get_etf_sector_weightings(name)
|
||||
if not sectors:
|
||||
console.print("No data was found for that ETF\n")
|
||||
return
|
||||
|
||||
holdings = pd.DataFrame(sectors, index=[0]).T
|
||||
|
||||
title = f"Sector holdings of {name}"
|
||||
|
||||
sector_weights_formatted = {}
|
||||
for sector_weight in sectors:
|
||||
sector_weights_formatted[sector_weight["sector"]] = (
|
||||
float(sector_weight["weightPercentage"].strip("%")) / 100
|
||||
)
|
||||
sector_weights_formatted = dict(sorted(sector_weights_formatted.items()))
|
||||
|
||||
if raw:
|
||||
console.print(f"\n{title}")
|
||||
holdings.columns = ["% of holdings in the sector"]
|
||||
sectors_df = pd.DataFrame(sectors).sort_values(by="sector")
|
||||
print_rich_table(
|
||||
holdings,
|
||||
headers=list(holdings.columns),
|
||||
show_index=True,
|
||||
title="Sector Weightings Allocation",
|
||||
sectors_df,
|
||||
headers=["Sector", "Weight"],
|
||||
show_index=False,
|
||||
title=f"\n{title}",
|
||||
)
|
||||
|
||||
else:
|
||||
main_holdings = holdings[holdings.values > min_pct_to_display].to_dict()[
|
||||
holdings.columns[0]
|
||||
]
|
||||
if len(main_holdings) < len(holdings):
|
||||
main_holdings["Others"] = 100 - sum(main_holdings.values())
|
||||
|
||||
legend, values = zip(*main_holdings.items())
|
||||
leg = [f"{le}\n{round(v,2)}%" for le, v in zip(legend, values)]
|
||||
legend, values = zip(*sector_weights_formatted.items())
|
||||
leg = [f"{le}\n{round(v * 100,2)}%" for le, v in zip(legend, values)]
|
||||
|
||||
if external_axes is None:
|
||||
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
@@ -101,23 +98,6 @@ def display_etf_weightings(
|
||||
export,
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"weights",
|
||||
holdings,
|
||||
pd.DataFrame([sector_weights_formatted]).T,
|
||||
sheet_name,
|
||||
)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def display_etf_description(name: str):
|
||||
"""Display ETF description summary. [Source: Yahoo Finance]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: str
|
||||
ETF name
|
||||
"""
|
||||
description = yfinance_model.get_etf_summary_description(name)
|
||||
if not description:
|
||||
console.print("No data was found for that ETF\n")
|
||||
return
|
||||
|
||||
console.print(description, "\n")
|
||||
@@ -1,62 +0,0 @@
|
||||
"""Yahoo Finance model"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import logging
|
||||
from typing import Dict
|
||||
|
||||
import yfinance as yf
|
||||
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_etf_sector_weightings(name: str) -> Dict:
|
||||
"""Return sector weightings allocation of ETF. [Source: Yahoo Finance]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: str
|
||||
ETF name
|
||||
|
||||
Returns
|
||||
-------
|
||||
Dict[str, Any]
|
||||
Dictionary with sector weightings allocation
|
||||
"""
|
||||
weights = yf.Ticker(name).info
|
||||
if "sectorWeightings" in weights:
|
||||
sectors = {}
|
||||
for item in weights["sectorWeightings"]:
|
||||
k = list(item.keys())[0].replace("_", " ").title()
|
||||
k = "Real Estate" if k == "Realestate" else k
|
||||
v = round(100 * list(item.values())[0], 2)
|
||||
sectors[k] = v
|
||||
|
||||
sectors = dict(sorted(sectors.items(), key=lambda x: x[1], reverse=True))
|
||||
return sectors
|
||||
|
||||
return dict()
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_etf_summary_description(name: str) -> str:
|
||||
"""Return summary description of ETF. [Source: Yahoo Finance]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: str
|
||||
ETF name
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Summary description of the ETF
|
||||
"""
|
||||
data = yf.Ticker(name).info
|
||||
|
||||
if "longBusinessSummary" in data:
|
||||
return data["longBusinessSummary"]
|
||||
|
||||
return ""
|
||||
@@ -3,7 +3,7 @@ __docformat__ = "numpy"
|
||||
|
||||
# IMPORTATION STANDARD
|
||||
import logging
|
||||
from typing import List
|
||||
from typing import List, Union
|
||||
|
||||
# IMPORTATION THIRDPARTY
|
||||
from dotenv import set_key
|
||||
@@ -16,6 +16,8 @@ from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.menu import session
|
||||
from openbb_terminal.parent_classes import BaseController
|
||||
from openbb_terminal.rich_config import MenuText, console
|
||||
from openbb_terminal.session.hub_model import patch_user_configs
|
||||
from openbb_terminal.session.user import User
|
||||
|
||||
# pylint: disable=too-many-lines,no-member,too-many-public-methods,C0302
|
||||
# pylint:disable=import-outside-toplevel
|
||||
@@ -78,130 +80,143 @@ class FeatureFlagsController(BaseController):
|
||||
|
||||
console.print(text=mt.menu_text, menu="Feature Flags")
|
||||
|
||||
@staticmethod
|
||||
def set_feature_flag(name: str, value: Union[bool, str], force: bool = False):
|
||||
"""Set feature flag
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name : str
|
||||
Environment variable name
|
||||
value : str
|
||||
Environment variable value
|
||||
force : bool, optional
|
||||
Force remote storage of feature flag, by default False
|
||||
"""
|
||||
|
||||
if User.is_guest():
|
||||
set_key(str(USER_ENV_FILE), name, str(value))
|
||||
|
||||
# Remove "OPENBB_" prefix from env_var
|
||||
if name.startswith("OPENBB_"):
|
||||
name = name[7:]
|
||||
|
||||
# Set obbff.env_var_name = not env_var_value
|
||||
setattr(obbff, name, value)
|
||||
|
||||
# Send feature flag to server
|
||||
if not User.is_guest() and User.is_sync_enabled() or force:
|
||||
patch_user_configs(
|
||||
key=name,
|
||||
value=str(value),
|
||||
type_="settings",
|
||||
auth_header=User.get_auth_header(),
|
||||
)
|
||||
|
||||
def call_overwrite(self, _):
|
||||
"""Process overwrite command"""
|
||||
obbff.FILE_OVERWITE = not obbff.FILE_OVERWITE
|
||||
set_key(USER_ENV_FILE, "OPENBB_FILE_OVERWITE", str(obbff.FILE_OVERWITE))
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_FILE_OVERWITE", not obbff.FILE_OVERWITE
|
||||
)
|
||||
|
||||
def call_retryload(self, _):
|
||||
"""Process retryload command"""
|
||||
obbff.RETRY_WITH_LOAD = not obbff.RETRY_WITH_LOAD
|
||||
set_key(USER_ENV_FILE, "OPENBB_RETRY_WITH_LOAD", str(obbff.RETRY_WITH_LOAD))
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_RETRY_WITH_LOAD", not obbff.RETRY_WITH_LOAD
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_tab(self, _):
|
||||
"""Process tab command"""
|
||||
obbff.USE_TABULATE_DF = not obbff.USE_TABULATE_DF
|
||||
set_key(USER_ENV_FILE, "OPENBB_USE_TABULATE_DF", str(obbff.USE_TABULATE_DF))
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_USE_TABULATE_DF", not obbff.USE_TABULATE_DF
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_cls(self, _):
|
||||
"""Process cls command"""
|
||||
obbff.USE_CLEAR_AFTER_CMD = not obbff.USE_CLEAR_AFTER_CMD
|
||||
set_key(
|
||||
USER_ENV_FILE,
|
||||
"OPENBB_USE_CLEAR_AFTER_CMD",
|
||||
str(obbff.USE_CLEAR_AFTER_CMD),
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_USE_CLEAR_AFTER_CMD", not obbff.USE_CLEAR_AFTER_CMD
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_color(self, _):
|
||||
"""Process color command"""
|
||||
obbff.USE_COLOR = not obbff.USE_COLOR
|
||||
set_key(USER_ENV_FILE, "OPENBB_USE_COLOR", str(obbff.USE_COLOR))
|
||||
FeatureFlagsController.set_feature_flag("OPENBB_USE_COLOR", not obbff.USE_COLOR)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_promptkit(self, _):
|
||||
"""Process promptkit command"""
|
||||
obbff.USE_PROMPT_TOOLKIT = not obbff.USE_PROMPT_TOOLKIT
|
||||
set_key(
|
||||
USER_ENV_FILE,
|
||||
"OPENBB_USE_PROMPT_TOOLKIT",
|
||||
str(obbff.USE_PROMPT_TOOLKIT),
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_USE_PROMPT_TOOLKIT", not obbff.USE_PROMPT_TOOLKIT
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_thoughts(self, _):
|
||||
"""Process thoughts command"""
|
||||
obbff.ENABLE_THOUGHTS_DAY = not obbff.ENABLE_THOUGHTS_DAY
|
||||
set_key(
|
||||
USER_ENV_FILE,
|
||||
"OPENBB_ENABLE_THOUGHTS_DAY",
|
||||
str(obbff.ENABLE_THOUGHTS_DAY),
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_ENABLE_THOUGHTS_DAY", not obbff.ENABLE_THOUGHTS_DAY
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_reporthtml(self, _):
|
||||
"""Process reporthtml command"""
|
||||
obbff.OPEN_REPORT_AS_HTML = not obbff.OPEN_REPORT_AS_HTML
|
||||
set_key(
|
||||
USER_ENV_FILE,
|
||||
"OPENBB_OPEN_REPORT_AS_HTML",
|
||||
str(obbff.OPEN_REPORT_AS_HTML),
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_OPEN_REPORT_AS_HTML", not obbff.OPEN_REPORT_AS_HTML
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_exithelp(self, _):
|
||||
"""Process exithelp command"""
|
||||
obbff.ENABLE_EXIT_AUTO_HELP = not obbff.ENABLE_EXIT_AUTO_HELP
|
||||
set_key(
|
||||
USER_ENV_FILE,
|
||||
"OPENBB_ENABLE_EXIT_AUTO_HELP",
|
||||
str(obbff.ENABLE_EXIT_AUTO_HELP),
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_ENABLE_EXIT_AUTO_HELP", not obbff.ENABLE_EXIT_AUTO_HELP
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_rcontext(self, _):
|
||||
"""Process rcontext command"""
|
||||
obbff.REMEMBER_CONTEXTS = not obbff.REMEMBER_CONTEXTS
|
||||
set_key(
|
||||
USER_ENV_FILE,
|
||||
"OPENBB_REMEMBER_CONTEXTS",
|
||||
str(obbff.REMEMBER_CONTEXTS),
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_REMEMBER_CONTEXTS", not obbff.REMEMBER_CONTEXTS
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_dt(self, _):
|
||||
"""Process dt command"""
|
||||
obbff.USE_DATETIME = not obbff.USE_DATETIME
|
||||
set_key(USER_ENV_FILE, "OPENBB_USE_DATETIME", str(obbff.USE_DATETIME))
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_USE_DATETIME", not obbff.USE_DATETIME
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_rich(self, _):
|
||||
"""Process rich command"""
|
||||
obbff.ENABLE_RICH = not obbff.ENABLE_RICH
|
||||
set_key(USER_ENV_FILE, "OPENBB_ENABLE_RICH", str(obbff.ENABLE_RICH))
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_ENABLE_RICH", not obbff.ENABLE_RICH
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_richpanel(self, _):
|
||||
"""Process richpanel command"""
|
||||
obbff.ENABLE_RICH_PANEL = not obbff.ENABLE_RICH_PANEL
|
||||
set_key(
|
||||
USER_ENV_FILE,
|
||||
"OPENBB_ENABLE_RICH_PANEL",
|
||||
str(obbff.ENABLE_RICH_PANEL),
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_ENABLE_RICH_PANEL", not obbff.ENABLE_RICH_PANEL
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_ion(self, _):
|
||||
"""Process ion command"""
|
||||
obbff.USE_ION = not obbff.USE_ION
|
||||
set_key(USER_ENV_FILE, "OPENBB_USE_ION", str(obbff.USE_ION))
|
||||
FeatureFlagsController.set_feature_flag("OPENBB_USE_ION", not obbff.USE_ION)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_watermark(self, _):
|
||||
"""Process watermark command"""
|
||||
obbff.USE_WATERMARK = not obbff.USE_WATERMARK
|
||||
set_key(USER_ENV_FILE, "OPENBB_USE_WATERMARK", str(obbff.USE_WATERMARK))
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_USE_WATERMARK", not obbff.USE_WATERMARK
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_cmdloc(self, _):
|
||||
"""Process cmdloc command"""
|
||||
obbff.USE_CMD_LOCATION_FIGURE = not obbff.USE_CMD_LOCATION_FIGURE
|
||||
set_key(
|
||||
USER_ENV_FILE,
|
||||
"OPENBB_USE_CMD_LOCATION_FIGURE",
|
||||
str(obbff.USE_CMD_LOCATION_FIGURE),
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_USE_CMD_LOCATION_FIGURE", not obbff.USE_CMD_LOCATION_FIGURE
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -209,9 +224,6 @@ class FeatureFlagsController(BaseController):
|
||||
"""Process tbhint command"""
|
||||
if obbff.TOOLBAR_HINT:
|
||||
console.print("Will take effect when running terminal next.")
|
||||
obbff.TOOLBAR_HINT = not obbff.TOOLBAR_HINT
|
||||
set_key(
|
||||
USER_ENV_FILE,
|
||||
"OPENBB_TOOLBAR_HINT",
|
||||
str(obbff.TOOLBAR_HINT),
|
||||
FeatureFlagsController.set_feature_flag(
|
||||
"OPENBB_TOOLBAR_HINT", not obbff.TOOLBAR_HINT
|
||||
)
|
||||
|
||||
@@ -12,15 +12,12 @@ from openbb_terminal.core.config import paths_helper
|
||||
from openbb_terminal.core.config.paths import (
|
||||
MISCELLANEOUS_DIRECTORY,
|
||||
USER_DATA_SOURCES_DEFAULT_FILE,
|
||||
load_dotenv_with_priority,
|
||||
)
|
||||
|
||||
paths_helper.init_userdata()
|
||||
|
||||
# pylint: disable=no-member,c-extension-no-member
|
||||
|
||||
load_dotenv_with_priority()
|
||||
|
||||
try:
|
||||
__import__("git")
|
||||
except ImportError:
|
||||
@@ -28,6 +25,8 @@ except ImportError:
|
||||
else:
|
||||
WITH_GIT = True
|
||||
|
||||
# Sync with OpenBB server
|
||||
SYNC_ENABLED = load_env_vars("OPENBB_SYNC_ENABLED", strtobool, True, "featflags")
|
||||
|
||||
# Always overwrite exported files
|
||||
FILE_OVERWITE = load_env_vars("OPENBB_FILE_OVERWRITE", strtobool, False, "featflags")
|
||||
@@ -46,9 +45,6 @@ USE_CLEAR_AFTER_CMD = load_env_vars(
|
||||
# Use coloring features
|
||||
USE_COLOR = load_env_vars("OPENBB_USE_COLOR", strtobool, True, "featflags")
|
||||
|
||||
# Select console flair (choose from config_terminal.py list)
|
||||
USE_FLAIR = str(os.getenv("OPENBB_USE_FLAIR", ":openbb"))
|
||||
|
||||
# Add date and time to command line
|
||||
USE_DATETIME = load_env_vars("OPENBB_USE_DATETIME", strtobool, True, "featflags")
|
||||
|
||||
@@ -131,6 +127,12 @@ PREFERRED_DATA_SOURCE_FILE = str(
|
||||
)
|
||||
)
|
||||
|
||||
# Timezone
|
||||
TIMEZONE = str(os.getenv("OPENBB_TIMEZONE", "America/New_York"))
|
||||
|
||||
# Select console flair (choose from config_terminal.py list)
|
||||
USE_FLAIR = str(os.getenv("OPENBB_USE_FLAIR", ":openbb"))
|
||||
|
||||
# Guess file
|
||||
GUESS_EASTER_EGG_FILE = str(
|
||||
os.getenv(
|
||||
|
||||
@@ -38,6 +38,7 @@ def get_brnn_data(
|
||||
model_save_name: str = "brnn_model",
|
||||
force_reset: bool = True,
|
||||
save_checkpoints: bool = True,
|
||||
metric: str = "mape",
|
||||
) -> Tuple[
|
||||
Optional[List[TimeSeries]],
|
||||
Optional[List[TimeSeries]],
|
||||
@@ -84,6 +85,8 @@ def get_brnn_data(
|
||||
discarded). Defaults to True.
|
||||
save_checkpoints: bool
|
||||
Whether or not to automatically save the untrained model and checkpoints from training. Defaults to True.
|
||||
metric: str
|
||||
Metric to use for model selection. Defaults to "mape".
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -174,4 +177,5 @@ def get_brnn_data(
|
||||
train_split,
|
||||
forecast_horizon,
|
||||
n_predict,
|
||||
metric,
|
||||
)
|
||||
|
||||
@@ -43,6 +43,7 @@ def display_brnn_forecast(
|
||||
end_date: Optional[datetime] = None,
|
||||
naive: bool = False,
|
||||
export_pred_raw: bool = False,
|
||||
metric: str = "mape",
|
||||
external_axes: Optional[List[plt.axes]] = None,
|
||||
):
|
||||
"""Display BRNN forecast
|
||||
@@ -102,6 +103,10 @@ def display_brnn_forecast(
|
||||
naive: bool
|
||||
Whether to show the naive baseline. This just assumes the closing price will be the same
|
||||
as the previous day's closing price. Defaults to False.
|
||||
export_pred_raw: bool
|
||||
Whether to export the raw predicted values. Defaults to False.
|
||||
metric: str
|
||||
The metric to use for the model. Defaults to "mape".
|
||||
external_axes: Optional[List[plt.axes]]
|
||||
External axes to plot on
|
||||
"""
|
||||
@@ -137,6 +142,7 @@ def display_brnn_forecast(
|
||||
model_save_name=model_save_name,
|
||||
force_reset=force_reset,
|
||||
save_checkpoints=save_checkpoints,
|
||||
metric=metric,
|
||||
)
|
||||
if ticker_series == []:
|
||||
return
|
||||
@@ -160,6 +166,7 @@ def display_brnn_forecast(
|
||||
forecast_only=forecast_only,
|
||||
naive=naive,
|
||||
export_pred_raw=export_pred_raw,
|
||||
metric=metric,
|
||||
external_axes=external_axes,
|
||||
)
|
||||
if residuals:
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import List, Optional, Tuple, Union
|
||||
|
||||
import pandas as pd
|
||||
from darts import TimeSeries
|
||||
from darts.metrics import mape
|
||||
from darts.metrics import mape, mse, rmse, smape
|
||||
from darts.models import ExponentialSmoothing
|
||||
from darts.utils.utils import ModelMode, SeasonalityMode
|
||||
from numpy import ndarray
|
||||
@@ -40,6 +40,7 @@ def get_expo_data(
|
||||
n_predict: int = 5,
|
||||
start_window: float = 0.85,
|
||||
forecast_horizon: int = 5,
|
||||
metric: str = "mape",
|
||||
) -> Tuple[
|
||||
List[TimeSeries],
|
||||
List[TimeSeries],
|
||||
@@ -76,6 +77,8 @@ def get_expo_data(
|
||||
Size of sliding window from start of timeseries and onwards
|
||||
forecast_horizon: int
|
||||
Number of days to forecast when backtesting and retraining historical
|
||||
metric: str
|
||||
Metric to use for backtesting. Defaults to MAPE.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -149,8 +152,19 @@ def get_expo_data(
|
||||
# we have the historical fcast, now lets train on entire set and predict.
|
||||
best_model.fit(ticker_series)
|
||||
probabilistic_forecast = best_model.predict(int(n_predict), num_samples=500)
|
||||
precision = mape(actual_series=ticker_series, pred_series=historical_fcast_es)
|
||||
console.print(f"Exponential smoothing obtains MAPE: {precision:.2f}% \n")
|
||||
|
||||
if metric == "rmse":
|
||||
precision = rmse(actual_series=ticker_series, pred_series=historical_fcast_es)
|
||||
elif metric == "mse":
|
||||
precision = mse(actual_series=ticker_series, pred_series=historical_fcast_es)
|
||||
elif metric == "mape":
|
||||
precision = mape(actual_series=ticker_series, pred_series=historical_fcast_es)
|
||||
elif metric == "smape":
|
||||
precision = smape(actual_series=ticker_series, pred_series=historical_fcast_es)
|
||||
|
||||
console.print(
|
||||
f"Exponential smoothing obtains {metric.upper()}: {precision:.2f}% \n"
|
||||
)
|
||||
|
||||
return (
|
||||
ticker_series,
|
||||
|
||||
@@ -36,6 +36,7 @@ def display_expo_forecast(
|
||||
end_date: Optional[datetime] = None,
|
||||
naive: bool = False,
|
||||
export_pred_raw: bool = False,
|
||||
metric: str = "mape",
|
||||
external_axes: Optional[List[plt.axes]] = None,
|
||||
):
|
||||
"""Display Probabilistic Exponential Smoothing forecast
|
||||
@@ -80,6 +81,10 @@ def display_expo_forecast(
|
||||
naive: bool
|
||||
Whether to show the naive baseline. This just assumes the closing price will be the same
|
||||
as the previous day's closing price. Defaults to False.
|
||||
export_pred_raw: bool
|
||||
Whether to export the raw predicted values. Defaults to False.
|
||||
metric: str
|
||||
The metric to use when backtesting. Defaults to "mape".
|
||||
external_axes: Optional[List[plt.axes]]
|
||||
External axes to plot on
|
||||
"""
|
||||
@@ -103,6 +108,7 @@ def display_expo_forecast(
|
||||
n_predict=n_predict,
|
||||
start_window=start_window,
|
||||
forecast_horizon=forecast_horizon,
|
||||
metric=metric,
|
||||
)
|
||||
|
||||
if ticker_series == []:
|
||||
@@ -127,6 +133,7 @@ def display_expo_forecast(
|
||||
forecast_only=forecast_only,
|
||||
naive=naive,
|
||||
export_pred_raw=export_pred_raw,
|
||||
metric=metric,
|
||||
external_axes=external_axes,
|
||||
)
|
||||
if residuals:
|
||||
|
||||
@@ -356,6 +356,7 @@ class ForecastController(BaseController):
|
||||
naive: bool = False,
|
||||
explainability_raw: bool = False,
|
||||
export_pred_raw: bool = False,
|
||||
metric: bool = False,
|
||||
):
|
||||
if hidden_size:
|
||||
parser.add_argument(
|
||||
@@ -531,7 +532,7 @@ class ForecastController(BaseController):
|
||||
action="store",
|
||||
dest="model_type",
|
||||
default="LSTM",
|
||||
help='Either a string specifying the RNN module type ("RNN", "LSTM" or "GRU")',
|
||||
help='Enter a string specifying the RNN module type ("RNN", "LSTM" or "GRU")',
|
||||
)
|
||||
if dropout is not None:
|
||||
parser.add_argument(
|
||||
@@ -631,6 +632,17 @@ class ForecastController(BaseController):
|
||||
help="Export predictions to a csv file.",
|
||||
)
|
||||
|
||||
if metric:
|
||||
parser.add_argument(
|
||||
"--metric",
|
||||
type=str,
|
||||
action="store",
|
||||
dest="metric",
|
||||
default="mape",
|
||||
choices=["rmse", "mse", "mape", "smape"],
|
||||
help="Calculate precision based on a specific metric (rmse, mse, mape)",
|
||||
)
|
||||
|
||||
# if user does not put in --dataset
|
||||
return parser
|
||||
|
||||
@@ -2114,6 +2126,7 @@ class ForecastController(BaseController):
|
||||
end=True,
|
||||
naive=True,
|
||||
export_pred_raw=True,
|
||||
metric=True,
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(
|
||||
parser,
|
||||
@@ -2143,6 +2156,7 @@ class ForecastController(BaseController):
|
||||
end_date=ns_parser.s_end_date,
|
||||
naive=ns_parser.naive,
|
||||
export_pred_raw=ns_parser.export_pred_raw,
|
||||
metric=ns_parser.metric,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -2175,6 +2189,7 @@ class ForecastController(BaseController):
|
||||
end=True,
|
||||
naive=True,
|
||||
export_pred_raw=True,
|
||||
metric=True,
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(
|
||||
parser,
|
||||
@@ -2202,6 +2217,7 @@ class ForecastController(BaseController):
|
||||
end_date=ns_parser.s_end_date,
|
||||
naive=ns_parser.naive,
|
||||
export_pred_raw=ns_parser.export_pred_raw,
|
||||
metric=ns_parser.metric,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -2261,6 +2277,7 @@ class ForecastController(BaseController):
|
||||
end=True,
|
||||
naive=True,
|
||||
export_pred_raw=True,
|
||||
metric=True,
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(
|
||||
parser,
|
||||
@@ -2297,6 +2314,7 @@ class ForecastController(BaseController):
|
||||
end_date=ns_parser.s_end_date,
|
||||
naive=ns_parser.naive,
|
||||
export_pred_raw=ns_parser.export_pred_raw,
|
||||
metric=ns_parser.metric,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -2373,6 +2391,7 @@ class ForecastController(BaseController):
|
||||
end=True,
|
||||
naive=True,
|
||||
export_pred_raw=True,
|
||||
metric=True,
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(
|
||||
parser,
|
||||
@@ -2415,6 +2434,7 @@ class ForecastController(BaseController):
|
||||
end_date=ns_parser.s_end_date,
|
||||
naive=ns_parser.naive,
|
||||
export_pred_raw=ns_parser.export_pred_raw,
|
||||
metric=ns_parser.metric,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -2482,6 +2502,7 @@ class ForecastController(BaseController):
|
||||
end=True,
|
||||
naive=True,
|
||||
export_pred_raw=True,
|
||||
metric=True,
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(
|
||||
parser,
|
||||
@@ -2525,6 +2546,7 @@ class ForecastController(BaseController):
|
||||
end_date=ns_parser.s_end_date,
|
||||
naive=ns_parser.naive,
|
||||
export_pred_raw=ns_parser.export_pred_raw,
|
||||
metric=ns_parser.metric,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -2561,6 +2583,7 @@ class ForecastController(BaseController):
|
||||
naive=True,
|
||||
explainability_raw=True,
|
||||
export_pred_raw=True,
|
||||
metric=True,
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(
|
||||
parser,
|
||||
@@ -2595,6 +2618,7 @@ class ForecastController(BaseController):
|
||||
naive=ns_parser.naive,
|
||||
explainability_raw=ns_parser.explainability_raw,
|
||||
export_pred_raw=ns_parser.export_pred_raw,
|
||||
metric=ns_parser.metric,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -2630,6 +2654,7 @@ class ForecastController(BaseController):
|
||||
naive=True,
|
||||
explainability_raw=True,
|
||||
export_pred_raw=True,
|
||||
metric=True,
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(
|
||||
parser,
|
||||
@@ -2663,6 +2688,7 @@ class ForecastController(BaseController):
|
||||
naive=ns_parser.naive,
|
||||
explainability_raw=ns_parser.explainability_raw,
|
||||
export_pred_raw=ns_parser.export_pred_raw,
|
||||
metric=ns_parser.metric,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -2714,6 +2740,7 @@ class ForecastController(BaseController):
|
||||
end=True,
|
||||
naive=True,
|
||||
export_pred_raw=True,
|
||||
metric=True,
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(
|
||||
parser,
|
||||
@@ -2755,6 +2782,7 @@ class ForecastController(BaseController):
|
||||
end_date=ns_parser.s_end_date,
|
||||
naive=ns_parser.naive,
|
||||
export_pred_raw=ns_parser.export_pred_raw,
|
||||
metric=ns_parser.metric,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -2842,6 +2870,7 @@ class ForecastController(BaseController):
|
||||
end=True,
|
||||
naive=True,
|
||||
export_pred_raw=True,
|
||||
metric=True,
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(
|
||||
parser,
|
||||
@@ -2885,6 +2914,7 @@ class ForecastController(BaseController):
|
||||
end_date=ns_parser.s_end_date,
|
||||
naive=ns_parser.naive,
|
||||
export_pred_raw=ns_parser.export_pred_raw,
|
||||
metric=ns_parser.metric,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -2958,6 +2988,7 @@ class ForecastController(BaseController):
|
||||
end=True,
|
||||
naive=True,
|
||||
export_pred_raw=True,
|
||||
metric=True,
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(
|
||||
parser,
|
||||
@@ -3001,6 +3032,7 @@ class ForecastController(BaseController):
|
||||
end_date=ns_parser.s_end_date,
|
||||
naive=ns_parser.naive,
|
||||
export_pred_raw=ns_parser.export_pred_raw,
|
||||
metric=ns_parser.metric,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -3092,6 +3124,7 @@ class ForecastController(BaseController):
|
||||
end=True,
|
||||
naive=True,
|
||||
export_pred_raw=True,
|
||||
metric=True,
|
||||
)
|
||||
ns_parser = self.parse_known_args_and_warn(
|
||||
parser,
|
||||
@@ -3136,6 +3169,7 @@ class ForecastController(BaseController):
|
||||
end_date=ns_parser.s_end_date,
|
||||
naive=ns_parser.naive,
|
||||
export_pred_raw=ns_parser.export_pred_raw,
|
||||
metric=ns_parser.metric,
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
|
||||
@@ -11,7 +11,7 @@ import pandas as pd
|
||||
from darts import TimeSeries
|
||||
from darts.dataprocessing.transformers import MissingValuesFiller, Scaler
|
||||
from darts.explainability.shap_explainer import ShapExplainer
|
||||
from darts.metrics import mape
|
||||
from darts.metrics import mape, mse, rmse, smape
|
||||
from darts.models.forecasting.torch_forecasting_model import GlobalForecastingModel
|
||||
from darts.utils.statistics import plot_residuals_analysis
|
||||
from pytorch_lightning.callbacks.early_stopping import EarlyStopping
|
||||
@@ -477,6 +477,7 @@ def plot_forecast(
|
||||
forecast_only: bool = False,
|
||||
naive: bool = False,
|
||||
export_pred_raw: bool = False,
|
||||
metric: str = "mape",
|
||||
external_axes: Optional[List[plt.axes]] = None,
|
||||
):
|
||||
quant_kwargs = {}
|
||||
@@ -503,7 +504,16 @@ def plot_forecast(
|
||||
if naive:
|
||||
# show naive forecast shift timeseries by 1
|
||||
naive_fcast = naive_fcast.drop_before(historical_fcast.start_time())
|
||||
naive_precision = mape(ticker_series, naive_fcast)
|
||||
|
||||
# calculate precision based on metric
|
||||
if metric == "rsme":
|
||||
naive_precision = rmse(ticker_series, naive_fcast)
|
||||
elif metric == "mse":
|
||||
naive_precision = mse(ticker_series, naive_fcast)
|
||||
elif metric == "mape":
|
||||
naive_precision = mape(ticker_series, naive_fcast)
|
||||
elif metric == "smape":
|
||||
naive_precision = smape(ticker_series, naive_fcast)
|
||||
|
||||
naive_fcast.plot(
|
||||
label=f"Naive+1: {naive_precision:.2f}%",
|
||||
@@ -516,7 +526,7 @@ def plot_forecast(
|
||||
pred_label += " w/ past covs"
|
||||
predicted_values.plot(label=pred_label, **quant_kwargs, color="#00AAFF")
|
||||
ax.set_title(
|
||||
f"{name} for <{ticker_name}> for next [{n_predict}] days (MAPE={precision:.2f}%)"
|
||||
f"{name} for <{ticker_name}> for next [{n_predict}] days ({metric.upper()}={precision:.2f}%)"
|
||||
)
|
||||
ax.set_ylabel(target_col)
|
||||
ax.set_xlabel("Date")
|
||||
@@ -719,6 +729,7 @@ def get_prediction(
|
||||
train_split: float,
|
||||
forecast_horizon: int,
|
||||
n_predict: int,
|
||||
metric: str,
|
||||
):
|
||||
_, val = ticker_series.split_before(train_split)
|
||||
|
||||
@@ -771,10 +782,17 @@ def get_prediction(
|
||||
else:
|
||||
prediction = best_model.predict(series=ticker_series, n=n_predict)
|
||||
|
||||
precision = mape(
|
||||
actual_series=val, pred_series=historical_fcast
|
||||
) # mape = mean average percentage error
|
||||
console.print(f"{model_name} model obtains MAPE: {precision:.2f}% \n")
|
||||
# calculate precision based on metric (rmse, mse, mape)
|
||||
if metric == "rmse":
|
||||
precision = rmse(actual_series=val, pred_series=historical_fcast)
|
||||
elif metric == "mse":
|
||||
precision = mse(actual_series=val, pred_series=historical_fcast)
|
||||
elif metric == "mape":
|
||||
precision = mape(actual_series=val, pred_series=historical_fcast)
|
||||
elif metric == "smape":
|
||||
precision = smape(actual_series=val, pred_series=historical_fcast)
|
||||
|
||||
console.print(f"{model_name} model obtains {metric.upper()}: {precision:.2f}% \n")
|
||||
|
||||
# scale back
|
||||
if use_scalers and isinstance(scaler, Scaler):
|
||||
|
||||
@@ -27,6 +27,7 @@ def get_linear_regression_data(
|
||||
output_chunk_length: int = 5,
|
||||
lags: Union[int, List[int]] = 14,
|
||||
random_state: Optional[int] = None,
|
||||
metric: str = "mape",
|
||||
) -> Tuple[
|
||||
List[TimeSeries],
|
||||
List[TimeSeries],
|
||||
@@ -56,6 +57,8 @@ def get_linear_regression_data(
|
||||
lagged target values to predict the next time step
|
||||
random_state: Optional[int]
|
||||
The state for the model
|
||||
metric: str
|
||||
The metric to use for the model. Defaults to "mape".
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -112,4 +115,5 @@ def get_linear_regression_data(
|
||||
train_split,
|
||||
forecast_horizon,
|
||||
n_predict,
|
||||
metric,
|
||||
)
|
||||
|
||||
@@ -35,6 +35,7 @@ def display_linear_regression(
|
||||
naive: bool = False,
|
||||
explainability_raw: bool = False,
|
||||
export_pred_raw: bool = False,
|
||||
metric: str = "mape",
|
||||
external_axes: Optional[List[plt.axes]] = None,
|
||||
):
|
||||
"""Display Linear Regression Forecasting
|
||||
@@ -74,6 +75,12 @@ def display_linear_regression(
|
||||
naive: bool
|
||||
Whether to show the naive baseline. This just assumes the closing price will be the
|
||||
same as the previous day's closing price. Defaults to False.
|
||||
explainability_raw: bool
|
||||
Whether to show the raw explainability data. Defaults to False.
|
||||
export_pred_raw: bool
|
||||
Whether to export the raw prediction data. Defaults to False.
|
||||
metric: str
|
||||
The metric to use for the model. Defaults to "mape".
|
||||
external_axes: Optional[List[plt.axes]]
|
||||
External axes to plot on
|
||||
"""
|
||||
@@ -100,6 +107,7 @@ def display_linear_regression(
|
||||
forecast_horizon=forecast_horizon,
|
||||
output_chunk_length=output_chunk_length,
|
||||
lags=lags,
|
||||
metric=metric,
|
||||
)
|
||||
|
||||
probabilistic = True
|
||||
@@ -121,6 +129,7 @@ def display_linear_regression(
|
||||
forecast_only=forecast_only,
|
||||
naive=naive,
|
||||
export_pred_raw=export_pred_raw,
|
||||
metric=metric,
|
||||
external_axes=external_axes,
|
||||
)
|
||||
if residuals:
|
||||
|
||||
@@ -37,6 +37,7 @@ def get_NBEATS_data(
|
||||
model_save_name: str = "nbeats_model",
|
||||
force_reset: bool = True,
|
||||
save_checkpoints: bool = True,
|
||||
metric: str = "mape",
|
||||
) -> Tuple[
|
||||
Optional[List[TimeSeries]],
|
||||
Optional[List[TimeSeries]],
|
||||
@@ -88,6 +89,8 @@ def get_NBEATS_data(
|
||||
save_checkpoints: bool
|
||||
Whether or not to automatically save the untrained model and checkpoints from training.
|
||||
Defaults to True.
|
||||
metric: str
|
||||
Metric to use for model selection. Defaults to "mape".
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -168,4 +171,5 @@ def get_NBEATS_data(
|
||||
train_split,
|
||||
forecast_horizon,
|
||||
n_predict,
|
||||
metric,
|
||||
)
|
||||
|
||||
@@ -44,6 +44,7 @@ def display_nbeats_forecast(
|
||||
end_date: Optional[datetime] = None,
|
||||
naive: bool = False,
|
||||
export_pred_raw: bool = False,
|
||||
metric: str = "mape",
|
||||
external_axes: Optional[List[plt.axes]] = None,
|
||||
):
|
||||
"""Display NBEATS forecast
|
||||
@@ -108,6 +109,10 @@ def display_nbeats_forecast(
|
||||
naive: bool
|
||||
Whether to show the naive baseline. This just assumes the closing price will be the same
|
||||
as the previous day's closing price. Defaults to False.
|
||||
export_pred_raw: bool
|
||||
Whether to export the raw predicted values. Defaults to False.
|
||||
metric: str
|
||||
Metric to use for evaluation. Defaults to "mape".
|
||||
external_axes: Optional[List[plt.axes]]
|
||||
External axes to plot on
|
||||
"""
|
||||
@@ -145,6 +150,7 @@ def display_nbeats_forecast(
|
||||
model_save_name=model_save_name,
|
||||
force_reset=force_reset,
|
||||
save_checkpoints=save_checkpoints,
|
||||
metric=metric,
|
||||
)
|
||||
if ticker_series == []:
|
||||
return
|
||||
@@ -168,6 +174,7 @@ def display_nbeats_forecast(
|
||||
forecast_only=forecast_only,
|
||||
naive=naive,
|
||||
export_pred_raw=export_pred_raw,
|
||||
metric=metric,
|
||||
external_axes=external_axes,
|
||||
)
|
||||
if residuals:
|
||||
|
||||
@@ -43,6 +43,7 @@ def get_nhits_data(
|
||||
model_save_name: str = "nhits_model",
|
||||
force_reset: bool = True,
|
||||
save_checkpoints: bool = True,
|
||||
metric: str = "mape",
|
||||
) -> Tuple[
|
||||
Optional[List[TimeSeries]],
|
||||
Optional[List[TimeSeries]],
|
||||
@@ -109,6 +110,8 @@ def get_nhits_data(
|
||||
discarded). Defaults to True.
|
||||
save_checkpoints: bool
|
||||
Whether or not to automatically save the untrained model and checkpoints from training. Defaults to True.
|
||||
metric: str
|
||||
Metric to use for model selection. Defaults to "mape".
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -197,4 +200,5 @@ def get_nhits_data(
|
||||
train_split,
|
||||
forecast_horizon,
|
||||
n_predict,
|
||||
metric,
|
||||
)
|
||||
|
||||
@@ -49,6 +49,7 @@ def display_nhits_forecast(
|
||||
end_date: Optional[datetime] = None,
|
||||
naive: bool = False,
|
||||
export_pred_raw: bool = False,
|
||||
metric: str = "mape",
|
||||
external_axes: Optional[List[plt.axes]] = None,
|
||||
):
|
||||
"""Display Nhits forecast
|
||||
@@ -128,6 +129,10 @@ def display_nhits_forecast(
|
||||
naive: bool
|
||||
Whether to show the naive baseline. This just assumes the closing price will be the same
|
||||
as the previous day's closing price. Defaults to False.
|
||||
export_pred_raw: bool
|
||||
Whether to export the raw predicted values. Defaults to False.
|
||||
metric: str
|
||||
Metric to use for evaluation. Defaults to "mape".
|
||||
external_axes: Optional[List[plt.axes]]
|
||||
External axes to plot on
|
||||
"""
|
||||
@@ -169,6 +174,7 @@ def display_nhits_forecast(
|
||||
model_save_name=model_save_name,
|
||||
force_reset=force_reset,
|
||||
save_checkpoints=save_checkpoints,
|
||||
metric=metric,
|
||||
)
|
||||
if ticker_series == []:
|
||||
return
|
||||
@@ -192,6 +198,7 @@ def display_nhits_forecast(
|
||||
forecast_only=forecast_only,
|
||||
naive=naive,
|
||||
export_pred_raw=export_pred_raw,
|
||||
metric=metric,
|
||||
external_axes=external_axes,
|
||||
)
|
||||
if residuals:
|
||||
|
||||
@@ -26,6 +26,7 @@ def get_regression_data(
|
||||
forecast_horizon: int = 5,
|
||||
output_chunk_length: int = 5,
|
||||
lags: Union[int, List[int]] = 14,
|
||||
metric: str = "mape",
|
||||
) -> Tuple[
|
||||
List[TimeSeries], List[TimeSeries], List[TimeSeries], float, type[RegressionModel]
|
||||
]:
|
||||
@@ -49,6 +50,8 @@ def get_regression_data(
|
||||
The length of the forecast of the model. Defaults to 1.
|
||||
lags: Union[int, List[int]]
|
||||
lagged target values to predict the next time step
|
||||
metric: str
|
||||
Metric to use for evaluation. Defaults to "mape".
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -99,4 +102,5 @@ def get_regression_data(
|
||||
train_split,
|
||||
forecast_horizon,
|
||||
n_predict,
|
||||
metric,
|
||||
)
|
||||
|
||||
@@ -35,6 +35,7 @@ def display_regression(
|
||||
naive: bool = False,
|
||||
explainability_raw: bool = False,
|
||||
export_pred_raw: bool = False,
|
||||
metric: str = "mape",
|
||||
external_axes: Optional[List[plt.axes]] = None,
|
||||
):
|
||||
"""Display Regression Forecasting
|
||||
@@ -74,6 +75,8 @@ def display_regression(
|
||||
naive: bool
|
||||
Whether to show the naive baseline. This just assumes the closing price will be the same
|
||||
as the previous day's closing price. Defaults to False.
|
||||
metric: str
|
||||
The metric to use for the forecast. Defaults to "mape".
|
||||
external_axes: Optional[List[plt.axes]]
|
||||
External axes to plot on
|
||||
"""
|
||||
@@ -100,6 +103,7 @@ def display_regression(
|
||||
forecast_horizon=forecast_horizon,
|
||||
output_chunk_length=output_chunk_length,
|
||||
lags=lags,
|
||||
metric=metric,
|
||||
)
|
||||
probabilistic = False
|
||||
helpers.plot_forecast(
|
||||
@@ -120,6 +124,7 @@ def display_regression(
|
||||
forecast_only=forecast_only,
|
||||
naive=naive,
|
||||
export_pred_raw=export_pred_raw,
|
||||
metric=metric,
|
||||
external_axes=external_axes,
|
||||
)
|
||||
if residuals:
|
||||
|
||||
@@ -36,6 +36,7 @@ def get_rnn_data(
|
||||
input_chunk_size: int = 14,
|
||||
force_reset: bool = True,
|
||||
save_checkpoints: bool = True,
|
||||
metric: str = "mape",
|
||||
) -> Tuple[
|
||||
Optional[List[type[TimeSeries]]],
|
||||
Optional[List[type[TimeSeries]]],
|
||||
@@ -77,6 +78,8 @@ def get_rnn_data(
|
||||
save_checkpoints: bool
|
||||
Whether or not to automatically save the untrained model and checkpoints from training.
|
||||
Defaults to True.
|
||||
metric: str
|
||||
Metric to use for model selection. Defaults to "mape".
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -145,4 +148,5 @@ def get_rnn_data(
|
||||
train_split,
|
||||
forecast_horizon,
|
||||
n_predict,
|
||||
metric,
|
||||
)
|
||||
|
||||
@@ -42,6 +42,7 @@ def display_rnn_forecast(
|
||||
end_date: Optional[datetime] = None,
|
||||
naive: bool = False,
|
||||
export_pred_raw: bool = False,
|
||||
metric: str = "mape",
|
||||
external_axes: Optional[List[plt.axes]] = None,
|
||||
):
|
||||
"""Display RNN forecast
|
||||
@@ -94,6 +95,10 @@ def display_rnn_forecast(
|
||||
naive: bool
|
||||
Whether to show the naive baseline. This just assumes the closing price will be the same
|
||||
as the previous day's closing price. Defaults to False.
|
||||
export_pred_raw: bool
|
||||
Whether to export the raw predictions. Defaults to False.
|
||||
metric: str
|
||||
The metric to use for the forecast. Defaults to "mape".
|
||||
external_axes: Optional[List[plt.axes]]
|
||||
External axes to plot on
|
||||
"""
|
||||
@@ -124,6 +129,7 @@ def display_rnn_forecast(
|
||||
input_chunk_size=input_chunk_size,
|
||||
force_reset=force_reset,
|
||||
save_checkpoints=save_checkpoints,
|
||||
metric=metric,
|
||||
)
|
||||
if ticker_series == []:
|
||||
return
|
||||
@@ -148,6 +154,7 @@ def display_rnn_forecast(
|
||||
forecast_only=forecast_only,
|
||||
naive=naive,
|
||||
export_pred_raw=export_pred_raw,
|
||||
metric=metric,
|
||||
external_axes=external_axes,
|
||||
)
|
||||
if residuals:
|
||||
|
||||
@@ -37,6 +37,7 @@ def get_tcn_data(
|
||||
model_save_name: str = "tcn_model",
|
||||
force_reset: bool = True,
|
||||
save_checkpoints: bool = True,
|
||||
metric: str = "mape",
|
||||
) -> Tuple[
|
||||
List[Optional[TimeSeries]],
|
||||
List[Optional[TimeSeries]],
|
||||
@@ -86,6 +87,8 @@ def get_tcn_data(
|
||||
save_checkpoints: bool
|
||||
Whether or not to automatically save the untrained model and checkpoints from training.
|
||||
Defaults to True.
|
||||
metric: str
|
||||
Metric to use for model selection. Defaults to "mape".
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -166,4 +169,5 @@ def get_tcn_data(
|
||||
train_split,
|
||||
forecast_horizon,
|
||||
n_predict,
|
||||
metric,
|
||||
)
|
||||
|
||||
@@ -44,6 +44,7 @@ def display_tcn_forecast(
|
||||
end_date: Optional[datetime] = None,
|
||||
naive: bool = False,
|
||||
export_pred_raw: bool = False,
|
||||
metric: str = "mape",
|
||||
external_axes: Optional[List[plt.axes]] = None,
|
||||
):
|
||||
"""Display TCN forecast
|
||||
@@ -104,6 +105,10 @@ def display_tcn_forecast(
|
||||
naive: bool
|
||||
Whether to show the naive baseline. This just assumes the closing price will be the same
|
||||
as the previous day's closing price. Defaults to False.
|
||||
export_pred_raw: bool
|
||||
Whether to export the raw predicted values. Defaults to False.
|
||||
metric: str
|
||||
The metric to use for the model. Defaults to "mape".
|
||||
external_axes: Optional[List[plt.axes]]
|
||||
External axes to plot on
|
||||
"""
|
||||
@@ -141,6 +146,7 @@ def display_tcn_forecast(
|
||||
model_save_name=model_save_name,
|
||||
force_reset=force_reset,
|
||||
save_checkpoints=save_checkpoints,
|
||||
metric=metric,
|
||||
)
|
||||
if ticker_series == []:
|
||||
return
|
||||
@@ -165,6 +171,7 @@ def display_tcn_forecast(
|
||||
naive=naive,
|
||||
export_pred_raw=export_pred_raw,
|
||||
external_axes=external_axes,
|
||||
metric=metric,
|
||||
)
|
||||
if residuals:
|
||||
helpers.plot_residuals(
|
||||
|
||||
@@ -43,6 +43,7 @@ def get_tft_data(
|
||||
model_save_name: str = "tft_model",
|
||||
force_reset: bool = True,
|
||||
save_checkpoints: bool = True,
|
||||
metric: str = "mape",
|
||||
) -> Tuple[
|
||||
Optional[List[TimeSeries]],
|
||||
Optional[List[TimeSeries]],
|
||||
@@ -101,6 +102,8 @@ def get_tft_data(
|
||||
save_checkpoints: (bool, optional)
|
||||
Whether or not to automatically save the untrained model and checkpoints from training.
|
||||
Defaults to True.
|
||||
metric: (str, optional)
|
||||
Metric to use for model selection. Defaults to "mape".
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -204,4 +207,5 @@ def get_tft_data(
|
||||
train_split,
|
||||
forecast_horizon,
|
||||
n_predict,
|
||||
metric,
|
||||
)
|
||||
|
||||
@@ -45,6 +45,7 @@ def display_tft_forecast(
|
||||
end_date: Optional[datetime] = None,
|
||||
naive: bool = False,
|
||||
export_pred_raw: bool = False,
|
||||
metric: str = "mape",
|
||||
external_axes: Optional[List[plt.axes]] = None,
|
||||
):
|
||||
"""Display Temporal Fusion Transformer forecast
|
||||
@@ -105,6 +106,8 @@ def display_tft_forecast(
|
||||
naive: bool
|
||||
Whether to show the naive baseline. This just assumes the closing price will be the same
|
||||
as the previous day's closing price. Defaults to False.
|
||||
metric: str
|
||||
The metric to use for the model. Defaults to "mape".
|
||||
external_axes:Optional[List[plt.axes]]
|
||||
External axes to plot on
|
||||
"""
|
||||
@@ -143,6 +146,7 @@ def display_tft_forecast(
|
||||
model_save_name=model_save_name,
|
||||
force_reset=force_reset,
|
||||
save_checkpoints=save_checkpoints,
|
||||
metric=metric,
|
||||
)
|
||||
if ticker_series == []:
|
||||
return
|
||||
@@ -166,6 +170,7 @@ def display_tft_forecast(
|
||||
forecast_only=forecast_only,
|
||||
naive=naive,
|
||||
export_pred_raw=export_pred_raw,
|
||||
metric=metric,
|
||||
external_axes=external_axes,
|
||||
)
|
||||
if residuals:
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import List, Optional, Tuple, Union
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from darts import TimeSeries
|
||||
from darts.metrics import mape
|
||||
from darts.metrics import mape, mse, rmse, smape
|
||||
from darts.models import Theta
|
||||
from darts.utils.utils import SeasonalityMode
|
||||
from statsmodels.tools.sm_exceptions import ConvergenceWarning
|
||||
@@ -34,6 +34,7 @@ def get_theta_data(
|
||||
n_predict: int = 5,
|
||||
start_window: float = 0.85,
|
||||
forecast_horizon: int = 5,
|
||||
metric: str = "mape",
|
||||
) -> Tuple[
|
||||
Optional[List[TimeSeries]],
|
||||
Optional[List[TimeSeries]],
|
||||
@@ -64,7 +65,9 @@ def get_theta_data(
|
||||
start_window: float
|
||||
Size of sliding window from start of timeseries and onwards
|
||||
forecast_horizon: int
|
||||
Number of days to forecast when backtesting and retraining historical
|
||||
Number of days to forecast when backtesting and retraining historical data
|
||||
metric: str
|
||||
Metric to use when backtesting and retraining historical data. Defaults to "mape".
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -135,10 +138,23 @@ def get_theta_data(
|
||||
# fit model on entire series for final prediction
|
||||
best_theta_model_final.fit(ticker_series)
|
||||
prediction = best_theta_model_final.predict(int(n_predict))
|
||||
precision = mape(
|
||||
actual_series=ticker_series, pred_series=historical_fcast_theta
|
||||
) # mape = mean average precision error
|
||||
console.print(f"Theta Model obtains MAPE: {precision:.2f}% \n")
|
||||
|
||||
if metric == "rmse":
|
||||
precision = rmse(
|
||||
actual_series=ticker_series, pred_series=historical_fcast_theta
|
||||
)
|
||||
elif metric == "mse":
|
||||
precision = mse(actual_series=ticker_series, pred_series=historical_fcast_theta)
|
||||
elif metric == "mape":
|
||||
precision = mape(
|
||||
actual_series=ticker_series, pred_series=historical_fcast_theta
|
||||
)
|
||||
elif metric == "smape":
|
||||
precision = smape(
|
||||
actual_series=ticker_series, pred_series=historical_fcast_theta
|
||||
)
|
||||
|
||||
console.print(f"Theta Model obtains {metric.upper()}: {precision:.2f}% \n")
|
||||
|
||||
return (
|
||||
ticker_series,
|
||||
|
||||
@@ -34,6 +34,7 @@ def display_theta_forecast(
|
||||
end_date: Optional[datetime] = None,
|
||||
naive: bool = False,
|
||||
export_pred_raw: bool = False,
|
||||
metric: str = "mape",
|
||||
external_axes: Optional[List[plt.axes]] = None,
|
||||
):
|
||||
"""Display Theta forecast
|
||||
@@ -73,6 +74,10 @@ def display_theta_forecast(
|
||||
naive: bool
|
||||
Whether to show the naive baseline. This just assumes the closing price will be the same
|
||||
as the previous day's closing price. Defaults to False.
|
||||
export_pred_raw: bool
|
||||
Whether to export the raw predicted values. Defaults to False.
|
||||
metric: str
|
||||
The metric to use for backtesting. Defaults to "mape".
|
||||
external_axes:Optional[List[plt.axes]]
|
||||
External axes to plot on
|
||||
"""
|
||||
@@ -95,6 +100,7 @@ def display_theta_forecast(
|
||||
target_column=target_column,
|
||||
start_window=start_window,
|
||||
forecast_horizon=forecast_horizon,
|
||||
metric=metric,
|
||||
)
|
||||
if ticker_series == []:
|
||||
return
|
||||
@@ -119,6 +125,7 @@ def display_theta_forecast(
|
||||
naive=naive,
|
||||
export_pred_raw=export_pred_raw,
|
||||
external_axes=external_axes,
|
||||
metric=metric,
|
||||
)
|
||||
|
||||
if residuals:
|
||||
|
||||
@@ -40,6 +40,7 @@ def get_trans_data(
|
||||
model_save_name: str = "trans_model",
|
||||
force_reset: bool = True,
|
||||
save_checkpoints: bool = True,
|
||||
metric: str = "mape",
|
||||
) -> Tuple[
|
||||
Optional[List[TimeSeries]],
|
||||
Optional[List[TimeSeries]],
|
||||
@@ -94,6 +95,8 @@ def get_trans_data(
|
||||
discarded). Defaults to True.
|
||||
save_checkpoints: bool
|
||||
Whether or not to automatically save the untrained model and checkpoints from training. Defaults to True.
|
||||
metric: str
|
||||
Metric to use for model selection. Defaults to "mape".
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -176,4 +179,5 @@ def get_trans_data(
|
||||
train_split,
|
||||
forecast_horizon,
|
||||
n_predict,
|
||||
metric,
|
||||
)
|
||||
|
||||
@@ -47,6 +47,7 @@ def display_trans_forecast(
|
||||
end_date: Optional[datetime] = None,
|
||||
naive: bool = False,
|
||||
export_pred_raw: bool = False,
|
||||
metric: str = "mape",
|
||||
external_axes: Optional[List[plt.axes]] = None,
|
||||
):
|
||||
"""Display Transformer forecast
|
||||
@@ -112,6 +113,10 @@ def display_trans_forecast(
|
||||
naive: bool
|
||||
Whether to show the naive baseline. This just assumes the closing price will be the same
|
||||
as the previous day's closing price. Defaults to False.
|
||||
export_pred_raw: bool
|
||||
Whether to export the raw predicted values. Defaults to False.
|
||||
metric: str
|
||||
The metric to use for the model. Defaults to "mape".
|
||||
external_axes: Optional[List[plt.axes]]
|
||||
External axes to plot on
|
||||
"""
|
||||
@@ -152,6 +157,7 @@ def display_trans_forecast(
|
||||
model_save_name=model_save_name,
|
||||
force_reset=force_reset,
|
||||
save_checkpoints=save_checkpoints,
|
||||
metric=metric,
|
||||
)
|
||||
if ticker_series == []:
|
||||
return
|
||||
@@ -176,6 +182,7 @@ def display_trans_forecast(
|
||||
naive=naive,
|
||||
export_pred_raw=export_pred_raw,
|
||||
external_axes=external_axes,
|
||||
metric=metric,
|
||||
)
|
||||
if residuals:
|
||||
helpers.plot_residuals(
|
||||
|
||||
@@ -9,7 +9,7 @@ import pandas as pd
|
||||
|
||||
from openbb_terminal import config_terminal as cfg
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.helper_funcs import request
|
||||
from openbb_terminal.helper_funcs import get_user_timezone, request
|
||||
from openbb_terminal.rich_config import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -156,6 +156,7 @@ def get_historical(
|
||||
key = list(response_json.keys())[1]
|
||||
|
||||
df = pd.DataFrame.from_dict(response_json[key], orient="index")
|
||||
df.index = pd.to_datetime(df.index)
|
||||
|
||||
if start_date and resolution != "i":
|
||||
df = df[df.index > start_date]
|
||||
@@ -163,6 +164,10 @@ def get_historical(
|
||||
if end_date and resolution != "i":
|
||||
df = df[df.index < end_date]
|
||||
|
||||
if (df.index.hour != 0).any():
|
||||
# if intraday data, convert to local timezone
|
||||
df.index = df.index.tz_localize("UTC").tz_convert(get_user_timezone())
|
||||
|
||||
df = df.rename(
|
||||
columns={
|
||||
"1. open": "Open",
|
||||
|
||||
@@ -192,7 +192,7 @@ class ForexController(BaseController):
|
||||
console.print(
|
||||
"\n[red]No historical data loaded.\n\n"
|
||||
f"Make sure you have appropriate access for the '{ns_parser.source}' data source "
|
||||
f"and that '{ns_parser.source}' supports the requested range.[/red]\n"
|
||||
f"and that '{ns_parser.source}' supports the requested range.[/red]"
|
||||
)
|
||||
else:
|
||||
self.data.index.name = "date"
|
||||
|
||||
@@ -9,10 +9,8 @@ import os
|
||||
import random
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
import urllib.parse
|
||||
import webbrowser
|
||||
from collections.abc import Iterable
|
||||
from datetime import (
|
||||
date as d,
|
||||
datetime,
|
||||
@@ -23,7 +21,6 @@ from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple, Union
|
||||
|
||||
import dotenv
|
||||
import iso8601
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
@@ -45,12 +42,9 @@ from openbb_terminal import (
|
||||
config_terminal as cfg,
|
||||
feature_flags as obbff,
|
||||
)
|
||||
from openbb_terminal.core.config import paths
|
||||
from openbb_terminal.core.config.paths import (
|
||||
HOME_DIRECTORY,
|
||||
USER_ENV_FILE,
|
||||
USER_EXPORTS_DIRECTORY,
|
||||
load_dotenv_with_priority,
|
||||
)
|
||||
from openbb_terminal.rich_config import console
|
||||
|
||||
@@ -89,21 +83,6 @@ def set_command_location(cmd_loc: str):
|
||||
command_location = cmd_loc
|
||||
|
||||
|
||||
# pylint: disable=global-statement
|
||||
def set_user_data_folder(env_file: str = ".env", path_folder: str = ""):
|
||||
"""Set user data folder location.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
env_file : str
|
||||
Env file to be updated
|
||||
path_folder: str
|
||||
Path folder location
|
||||
"""
|
||||
dotenv.set_key(env_file, "OPENBB_USER_DATA_DIRECTORY", path_folder)
|
||||
paths.USER_DATA_DIRECTORY = Path(path_folder)
|
||||
|
||||
|
||||
def check_path(path: str) -> str:
|
||||
"""Check that path file exists.
|
||||
|
||||
@@ -596,6 +575,15 @@ def valid_date(s: str) -> datetime:
|
||||
raise argparse.ArgumentTypeError(f"Not a valid date: {s}") from value_error
|
||||
|
||||
|
||||
def is_valid_date(s: str) -> bool:
|
||||
"""Check if date is in valid format."""
|
||||
try:
|
||||
datetime.strptime(s, "%Y-%m-%d")
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def valid_repo(repo: str) -> str:
|
||||
"""Argparse type to check github repo is in valid format."""
|
||||
result = re.search(r"^[a-zA-Z0-9-_.]+\/[a-zA-Z0-9-_.]+$", repo) # noqa: W605
|
||||
@@ -619,85 +607,6 @@ def valid_hour(hr: str) -> int:
|
||||
return new_hr
|
||||
|
||||
|
||||
def plot_view_stock(df: pd.DataFrame, symbol: str, interval: str):
|
||||
"""Plot the loaded stock dataframe.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
df: Dataframe
|
||||
Dataframe of prices and volumes
|
||||
symbol: str
|
||||
Symbol of ticker
|
||||
interval: str
|
||||
Stock data resolution for plotting purposes
|
||||
"""
|
||||
df.sort_index(ascending=True, inplace=True)
|
||||
bar_colors = ["r" if x[1].Open < x[1].Close else "g" for x in df.iterrows()]
|
||||
|
||||
try:
|
||||
fig, ax = plt.subplots(
|
||||
2,
|
||||
1,
|
||||
gridspec_kw={"height_ratios": [3, 1]},
|
||||
figsize=plot_autoscale(),
|
||||
dpi=cfgPlot.PLOT_DPI,
|
||||
)
|
||||
except Exception as e:
|
||||
console.print(e)
|
||||
console.print(
|
||||
"Encountered an error trying to open a chart window. Check your X server configuration."
|
||||
)
|
||||
logging.exception("%s", type(e).__name__)
|
||||
return
|
||||
|
||||
# In order to make nice Volume plot, make the bar width = interval
|
||||
if interval == "1440min":
|
||||
bar_width = timedelta(days=1)
|
||||
title_string = "Daily"
|
||||
else:
|
||||
bar_width = timedelta(minutes=int(interval.split("m")[0]))
|
||||
title_string = f"{int(interval.split('m')[0])} min"
|
||||
|
||||
ax[0].yaxis.tick_right()
|
||||
if "Adj Close" in df.columns:
|
||||
ax[0].plot(df.index, df["Adj Close"], c=cfgPlot.VIEW_COLOR)
|
||||
else:
|
||||
ax[0].plot(df.index, df["Close"], c=cfgPlot.VIEW_COLOR)
|
||||
ax[0].set_xlim(df.index[0], df.index[-1])
|
||||
ax[0].set_xticks([])
|
||||
ax[0].yaxis.set_label_position("right")
|
||||
ax[0].set_ylabel("Share Price ($)")
|
||||
ax[0].grid(axis="y", color="gainsboro", linestyle="-", linewidth=0.5)
|
||||
|
||||
ax[0].spines["top"].set_visible(False)
|
||||
ax[0].spines["left"].set_visible(False)
|
||||
ax[1].bar(
|
||||
df.index, df.Volume / 1_000_000, color=bar_colors, alpha=0.8, width=bar_width
|
||||
)
|
||||
ax[1].set_xlim(df.index[0], df.index[-1])
|
||||
ax[1].yaxis.tick_right()
|
||||
ax[1].yaxis.set_label_position("right")
|
||||
ax[1].set_ylabel("Volume [1M]")
|
||||
ax[1].grid(axis="y", color="gainsboro", linestyle="-", linewidth=0.5)
|
||||
ax[1].spines["top"].set_visible(False)
|
||||
ax[1].spines["left"].set_visible(False)
|
||||
ax[1].set_xlabel("Time")
|
||||
fig.suptitle(
|
||||
symbol + " " + title_string,
|
||||
size=20,
|
||||
x=0.15,
|
||||
y=0.95,
|
||||
fontfamily="serif",
|
||||
fontstyle="italic",
|
||||
)
|
||||
if obbff.USE_ION:
|
||||
plt.ion()
|
||||
fig.tight_layout(pad=2)
|
||||
plt.setp(ax[1].get_xticklabels(), rotation=20, horizontalalignment="right")
|
||||
|
||||
plt.show()
|
||||
|
||||
|
||||
def us_market_holidays(years) -> list:
|
||||
"""Get US market holidays."""
|
||||
if isinstance(years, int):
|
||||
@@ -774,7 +683,12 @@ def lambda_long_number_format(num, round_decimal=3) -> str:
|
||||
return f"{num_str} {' KMBTP'[magnitude]}".strip()
|
||||
if isinstance(num, int):
|
||||
num = str(num)
|
||||
if isinstance(num, str) and num.lstrip("-").isdigit():
|
||||
if (
|
||||
isinstance(num, str)
|
||||
and num.lstrip("-").isdigit()
|
||||
and not num.lstrip("-").startswith("0")
|
||||
and not is_valid_date(num)
|
||||
):
|
||||
num = int(num)
|
||||
num /= 1.0
|
||||
magnitude = 0
|
||||
@@ -789,6 +703,58 @@ def lambda_long_number_format(num, round_decimal=3) -> str:
|
||||
return num
|
||||
|
||||
|
||||
def revert_lambda_long_number_format(num_str: str) -> Union[float, str]:
|
||||
"""
|
||||
Revert the formatting of a long number if the input is a formatted number, otherwise return the input as is.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
num_str : str
|
||||
The number to remove the formatting.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Union[float, str]
|
||||
The number as float (with no formatting) or the input as is.
|
||||
|
||||
"""
|
||||
magnitude_dict = {
|
||||
"K": 1000,
|
||||
"M": 1000000,
|
||||
"B": 1000000000,
|
||||
"T": 1000000000000,
|
||||
"P": 1000000000000000,
|
||||
}
|
||||
|
||||
# Ensure the input is a string and not empty
|
||||
if not num_str or not isinstance(num_str, str):
|
||||
return num_str
|
||||
|
||||
num_as_list = num_str.strip().split()
|
||||
|
||||
# If the input string is a number parse it as float
|
||||
if (
|
||||
len(num_as_list) == 1
|
||||
and num_as_list[0].replace(".", "").replace("-", "").isdigit()
|
||||
and not is_valid_date(num_str)
|
||||
):
|
||||
return float(num_str)
|
||||
|
||||
# If the input string is a formatted number with magnitude
|
||||
if (
|
||||
len(num_as_list) == 2
|
||||
and num_as_list[1] in magnitude_dict
|
||||
and num_as_list[0].replace(".", "").replace("-", "").isdigit()
|
||||
):
|
||||
num, unit = num_as_list
|
||||
magnitude = magnitude_dict.get(unit)
|
||||
if magnitude:
|
||||
return float(num) * magnitude
|
||||
|
||||
# Return the input string as is if it's not a formatted number
|
||||
return num_str
|
||||
|
||||
|
||||
def lambda_long_number_format_y_axis(df, y_column, ax):
|
||||
"""Format long number that goes onto Y axis."""
|
||||
max_values = df[y_column].values.max()
|
||||
@@ -1109,7 +1075,6 @@ def get_flair() -> str:
|
||||
else str(obbff.USE_FLAIR)
|
||||
)
|
||||
|
||||
set_default_timezone()
|
||||
if obbff.USE_DATETIME and get_user_timezone_or_invalid() != "INVALID":
|
||||
dtime = datetime.now(pytz.timezone(get_user_timezone())).strftime(
|
||||
"%Y %b %d, %H:%M"
|
||||
@@ -1124,14 +1089,6 @@ def get_flair() -> str:
|
||||
return flair
|
||||
|
||||
|
||||
def set_default_timezone() -> None:
|
||||
"""Set a default (America/New_York) timezone if one doesn't exist."""
|
||||
load_dotenv_with_priority()
|
||||
user_tz = os.getenv("OPENBB_TIMEZONE")
|
||||
if not user_tz:
|
||||
dotenv.set_key(USER_ENV_FILE, "OPENBB_TIMEZONE", "America/New_York")
|
||||
|
||||
|
||||
def is_timezone_valid(user_tz: str) -> bool:
|
||||
"""Check whether user timezone is valid.
|
||||
|
||||
@@ -1156,11 +1113,7 @@ def get_user_timezone() -> str:
|
||||
str
|
||||
user timezone based on .env file
|
||||
"""
|
||||
load_dotenv_with_priority()
|
||||
user_tz = os.getenv("OPENBB_TIMEZONE")
|
||||
if user_tz:
|
||||
return user_tz
|
||||
return ""
|
||||
return obbff.TIMEZONE
|
||||
|
||||
|
||||
def get_user_timezone_or_invalid() -> str:
|
||||
@@ -1177,21 +1130,6 @@ def get_user_timezone_or_invalid() -> str:
|
||||
return "INVALID"
|
||||
|
||||
|
||||
def replace_user_timezone(user_tz: str) -> None:
|
||||
"""Replace user timezone.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
user_tz: str
|
||||
User timezone to set
|
||||
"""
|
||||
if is_timezone_valid(user_tz):
|
||||
dotenv.set_key(USER_ENV_FILE, "OPENBB_TIMEZONE", user_tz)
|
||||
console.print("Timezone successfully updated", "\n")
|
||||
else:
|
||||
console.print("Timezone selected is not valid", "\n")
|
||||
|
||||
|
||||
def str_to_bool(value) -> bool:
|
||||
"""Match a string to a boolean value."""
|
||||
if isinstance(value, bool):
|
||||
@@ -1335,6 +1273,8 @@ def ask_file_overwrite(file_path: str) -> Tuple[bool, bool]:
|
||||
# Jeroen asked for a flag to overwrite no matter what
|
||||
if obbff.FILE_OVERWITE:
|
||||
return False, True
|
||||
if os.environ.get("TEST_MODE") == "True":
|
||||
return False, True
|
||||
if os.path.exists(file_path):
|
||||
overwrite = input("\nFile already exists. Overwrite? [y/n]: ").lower()
|
||||
if overwrite == "y":
|
||||
@@ -1403,6 +1343,8 @@ def export_data(
|
||||
regex=True,
|
||||
)
|
||||
|
||||
df = df.applymap(revert_lambda_long_number_format)
|
||||
|
||||
if exp_type.endswith("csv"):
|
||||
exists, overwrite = ask_file_overwrite(saved_path)
|
||||
if exists and not overwrite:
|
||||
@@ -1415,6 +1357,9 @@ def export_data(
|
||||
df.reset_index(drop=True, inplace=True)
|
||||
df.to_json(saved_path)
|
||||
elif exp_type.endswith("xlsx"):
|
||||
# since xlsx does not support datetimes with timezones we need to remove it
|
||||
df = remove_timezone_from_dataframe(df)
|
||||
|
||||
if sheet_name is None:
|
||||
exists, overwrite = ask_file_overwrite(saved_path)
|
||||
if exists and not overwrite:
|
||||
@@ -1461,9 +1406,9 @@ def export_data(
|
||||
return
|
||||
plt.savefig(saved_path)
|
||||
else:
|
||||
console.print("\nWrong export file specified.")
|
||||
console.print("Wrong export file specified.")
|
||||
|
||||
console.print(f"\nSaved file: {saved_path}")
|
||||
console.print(f"Saved file: {saved_path}")
|
||||
|
||||
|
||||
def get_rf() -> float:
|
||||
@@ -1593,69 +1538,6 @@ def camel_case_split(string: str) -> str:
|
||||
return " ".join(results).title()
|
||||
|
||||
|
||||
def choice_check_after_action(action=None, choices=None):
|
||||
"""Return an action class that checks choice after action call.
|
||||
|
||||
Does that for argument of argparse.ArgumentParser.add_argument function.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
action : Union[class, function]
|
||||
Action for set args before check choices.
|
||||
If action is class, it must implement argparse.Action methods
|
||||
If action is function, it takes 4 args(parser, namespace, values, option_string)
|
||||
and needs to return value to set dest
|
||||
|
||||
choices : Union[Iterable, function]
|
||||
A container of values that should be allowed.
|
||||
If choices is function, it takes 1 args(value) to check and
|
||||
return bool that value is allowed or not
|
||||
|
||||
Returns
|
||||
-------
|
||||
Class
|
||||
Class extended argparse.Action
|
||||
"""
|
||||
if isinstance(choices, Iterable):
|
||||
|
||||
def choice_checker(value):
|
||||
return value in choices
|
||||
|
||||
elif isinstance(choices, types.FunctionType):
|
||||
choice_checker = choices
|
||||
else:
|
||||
raise NotImplementedError("choices argument must be iterable or function")
|
||||
|
||||
if isinstance(action, type):
|
||||
|
||||
class ActionClass(action):
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
super().__call__(parser, namespace, values, option_string)
|
||||
if not choice_checker(getattr(namespace, self.dest)):
|
||||
raise ValueError(
|
||||
f"{getattr(namespace, self.dest)} is not in {choices}"
|
||||
)
|
||||
|
||||
elif isinstance(action, types.FunctionType):
|
||||
|
||||
class ActionClass(argparse.Action):
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
setattr(
|
||||
namespace,
|
||||
self.dest,
|
||||
action(parser, namespace, values, option_string),
|
||||
)
|
||||
if not choice_checker(getattr(namespace, self.dest)):
|
||||
raise ValueError(
|
||||
f"{getattr(namespace, self.dest)} is not in {choices}"
|
||||
)
|
||||
|
||||
else:
|
||||
raise NotImplementedError("action argument must be class or function")
|
||||
|
||||
return ActionClass
|
||||
|
||||
|
||||
def is_valid_axes_count(
|
||||
axes: List[plt.Axes],
|
||||
n: int,
|
||||
@@ -2017,3 +1899,45 @@ def request(url: str, method="GET", **kwargs) -> requests.Response:
|
||||
url, headers=headers, timeout=cfg.REQUEST_TIMEOUT, **kwargs
|
||||
)
|
||||
raise ValueError("Method must be GET or POST")
|
||||
|
||||
|
||||
def remove_timezone_from_dataframe(df: pd.DataFrame) -> pd.DataFrame:
|
||||
"""
|
||||
Remove timezone information from a dataframe.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
df : pd.DataFrame
|
||||
The dataframe to remove timezone information from
|
||||
|
||||
Returns
|
||||
-------
|
||||
pd.DataFrame
|
||||
The dataframe with timezone information removed
|
||||
"""
|
||||
|
||||
date_cols = []
|
||||
index_is_date = False
|
||||
|
||||
# Find columns and index containing date data
|
||||
if (
|
||||
df.index.dtype.kind == "M"
|
||||
and hasattr(df.index.dtype, "tz")
|
||||
and df.index.dtype.tz is not None
|
||||
):
|
||||
index_is_date = True
|
||||
|
||||
for col, dtype in df.dtypes.items():
|
||||
if dtype.kind == "M" and hasattr(df.index.dtype, "tz") and dtype.tz is not None:
|
||||
date_cols.append(col)
|
||||
|
||||
# Remove the timezone information
|
||||
for col in date_cols:
|
||||
df[col] = df[col].dt.date
|
||||
|
||||
if index_is_date:
|
||||
index_name = df.index.name
|
||||
df.index = df.index.date
|
||||
df.index.name = index_name
|
||||
|
||||
return df
|
||||
|
||||
@@ -7,20 +7,22 @@ import argparse
|
||||
import logging
|
||||
from typing import Dict, List
|
||||
|
||||
from tqdm import tqdm
|
||||
|
||||
from openbb_terminal import (
|
||||
feature_flags as obbff,
|
||||
keys_model,
|
||||
keys_view,
|
||||
)
|
||||
from openbb_terminal.core.config.paths import USER_ENV_FILE
|
||||
from openbb_terminal.custom_prompt_toolkit import NestedCompleter
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.helper_funcs import EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
from openbb_terminal.menu import session
|
||||
from openbb_terminal.parent_classes import BaseController
|
||||
from openbb_terminal.rich_config import MenuText, console, translate
|
||||
from openbb_terminal.rich_config import (
|
||||
MenuText,
|
||||
console,
|
||||
optional_rich_track,
|
||||
translate,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -38,11 +40,9 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
self,
|
||||
queue: List[str] = None,
|
||||
menu_usage: bool = True,
|
||||
env_file: str = str(USER_ENV_FILE),
|
||||
):
|
||||
"""Constructor"""
|
||||
super().__init__(queue)
|
||||
self.env_file = env_file
|
||||
if menu_usage:
|
||||
if session and obbff.USE_PROMPT_TOOLKIT:
|
||||
choices: dict = {c: {} for c in self.controller_choices}
|
||||
@@ -53,8 +53,15 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
|
||||
def check_keys_status(self) -> None:
|
||||
"""Check keys status"""
|
||||
for api in tqdm(self.API_LIST, desc="Checking keys status"):
|
||||
self.status_dict[api] = getattr(keys_model, "check_" + str(api) + "_key")()
|
||||
for api in optional_rich_track(self.API_LIST, desc="Checking keys status"):
|
||||
if api == "openbb":
|
||||
self.status_dict[api] = getattr(
|
||||
keys_model, "check_" + str(api) + "_personal_access_token"
|
||||
)()
|
||||
else:
|
||||
self.status_dict[api] = getattr(
|
||||
keys_model, "check_" + str(api) + "_key"
|
||||
)()
|
||||
|
||||
def print_help(self):
|
||||
"""Print help"""
|
||||
@@ -136,7 +143,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
if not other_args:
|
||||
console.print(
|
||||
"For your API Key, visit: https://www.alphavantage.co/support/#api-key\n"
|
||||
"For your API Key, visit: https://www.alphavantage.co/support/#api-key"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -165,9 +172,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print(
|
||||
"For your API Key, visit: https://financialmodelingprep.com\n"
|
||||
)
|
||||
console.print("For your API Key, visit: https://financialmodelingprep.com")
|
||||
return
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -195,7 +200,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://www.quandl.com\n")
|
||||
console.print("For your API Key, visit: https://www.quandl.com")
|
||||
return
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -223,7 +228,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://polygon.io\n")
|
||||
console.print("For your API Key, visit: https://polygon.io")
|
||||
return
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -234,6 +239,36 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
key=ns_parser.key, persist=True, show_output=True
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_intrinio(self, other_args: List[str]):
|
||||
"""Process polygon command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="intrinio",
|
||||
description="Set Intrinio API key.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-k",
|
||||
"--key",
|
||||
type=str,
|
||||
dest="key",
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print(
|
||||
"For your API Key, sign up for a subscription: https://intrinio.com/starter-plan\n"
|
||||
)
|
||||
return
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-k")
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
if ns_parser:
|
||||
self.status_dict["intrinio"] = keys_model.set_intrinio_key(
|
||||
key=ns_parser.key, persist=True, show_output=True
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_fred(self, other_args: List[str]):
|
||||
"""Process FRED command"""
|
||||
@@ -251,7 +286,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://fred.stlouisfed.org\n")
|
||||
console.print("For your API Key, visit: https://fred.stlouisfed.org")
|
||||
return
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -279,7 +314,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://newsapi.org\n")
|
||||
console.print("For your API Key, visit: https://newsapi.org")
|
||||
return
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -307,7 +342,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://developer.tradier.com\n")
|
||||
console.print("For your API Key, visit: https://developer.tradier.com")
|
||||
return
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -335,7 +370,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://coinmarketcap.com\n")
|
||||
console.print("For your API Key, visit: https://coinmarketcap.com")
|
||||
return
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-k")
|
||||
@@ -362,7 +397,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://finnhub.io\n")
|
||||
console.print("For your API Key, visit: https://finnhub.io")
|
||||
return
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-k")
|
||||
@@ -372,33 +407,6 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
key=ns_parser.key, persist=True, show_output=True
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_iex(self, other_args: List[str]):
|
||||
"""Process iex command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="iex",
|
||||
description="Set IEX Cloud API key.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-k",
|
||||
"--key",
|
||||
type=str,
|
||||
dest="key",
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://iexcloud.io\n")
|
||||
return
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-k")
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
if ns_parser:
|
||||
self.status_dict["iex"] = keys_model.set_iex_key(
|
||||
key=ns_parser.key, persist=True, show_output=True
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_reddit(self, other_args: List[str]):
|
||||
"""Process reddit command"""
|
||||
@@ -450,7 +458,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
nargs="+",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://www.reddit.com\n")
|
||||
console.print("For your API Key, visit: https://www.reddit.com")
|
||||
return
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
if ns_parser:
|
||||
@@ -503,7 +511,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
required="-h" not in other_args,
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://developer.twitter.com\n")
|
||||
console.print("For your API Key, visit: https://developer.twitter.com")
|
||||
return
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
if ns_parser:
|
||||
@@ -539,7 +547,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="password",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://robinhood.com/us/en/\n")
|
||||
console.print("For your API Key, visit: https://robinhood.com/us/en/")
|
||||
return
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
if ns_parser:
|
||||
@@ -584,7 +592,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
default="",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://www.degiro.fr\n")
|
||||
console.print("For your API Key, visit: https://www.degiro.fr")
|
||||
return
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
if ns_parser:
|
||||
@@ -627,7 +635,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="account type ('live' or 'practice')",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://developer.oanda.com\n")
|
||||
console.print("For your API Key, visit: https://developer.oanda.com")
|
||||
return
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
if ns_parser:
|
||||
@@ -665,7 +673,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
required="-h" not in other_args,
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://binance.com\n")
|
||||
console.print("For your API Key, visit: https://binance.com")
|
||||
return
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
if ns_parser:
|
||||
@@ -693,7 +701,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://bitquery.io/\n")
|
||||
console.print("For your API Key, visit: https://bitquery.io/")
|
||||
return
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-k")
|
||||
@@ -703,33 +711,6 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
key=ns_parser.key, persist=True, show_output=True
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_si(self, other_args: List[str]):
|
||||
"""Process si command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="si",
|
||||
description="Set Sentiment Investor API key.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-k",
|
||||
"--key",
|
||||
type=str,
|
||||
dest="key",
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://sentimentinvestor.com\n")
|
||||
return
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-k")
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
if ns_parser:
|
||||
self.status_dict["si"] = keys_model.set_si_key(
|
||||
key=ns_parser.key, persist=True, show_output=True
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_coinbase(self, other_args: List[str]):
|
||||
"""Process coinbase command"""
|
||||
@@ -764,7 +745,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
required="-h" not in other_args,
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://docs.pro.coinbase.com/\n")
|
||||
console.print("For your API Key, visit: https://docs.pro.coinbase.com/")
|
||||
return
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
if ns_parser:
|
||||
@@ -793,7 +774,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://docs.whale-alert.io/\n")
|
||||
console.print("For your API Key, visit: https://docs.whale-alert.io/")
|
||||
return
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-k")
|
||||
@@ -821,7 +802,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
if not other_args:
|
||||
console.print(
|
||||
"For your API Key, visit: https://docs.glassnode.com/basic-api/api-key#how-to-get-an-api-key/\n"
|
||||
"For your API Key, visit: https://docs.glassnode.com/basic-api/api-key#how-to-get-an-api-key/"
|
||||
)
|
||||
return
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -850,7 +831,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
if not other_args:
|
||||
console.print(
|
||||
"For your API Key, visit: https://coinglass.github.io/API-Reference/#api-key\n"
|
||||
"For your API Key, visit: https://coinglass.github.io/API-Reference/#api-key"
|
||||
)
|
||||
return
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -879,7 +860,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
if not other_args:
|
||||
console.print(
|
||||
"For your API Key, visit: https://cryptopanic.com/developers/api/\n"
|
||||
"For your API Key, visit: https://cryptopanic.com/developers/api/"
|
||||
)
|
||||
return
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -908,7 +889,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
if not other_args:
|
||||
console.print(
|
||||
"For your API Key, visit: https://github.com/EverexIO/Ethplorer/wiki/Ethplorer-API\n"
|
||||
"For your API Key, visit: https://github.com/EverexIO/Ethplorer/wiki/Ethplorer-API"
|
||||
)
|
||||
return
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -945,7 +926,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
required="-h" not in other_args,
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://www.smartstake.io\n")
|
||||
console.print("For your API Key, visit: https://www.smartstake.io")
|
||||
return
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
|
||||
@@ -975,7 +956,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
if not other_args:
|
||||
console.print(
|
||||
"For your API Key, visit: https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api\n"
|
||||
"For your API Key, visit: https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -1004,7 +985,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://messari.io/api/docs\n")
|
||||
console.print("For your API Key, visit: https://messari.io/api/docs")
|
||||
return
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -1033,7 +1014,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
if not other_args:
|
||||
console.print(
|
||||
"For your API Key, visit: https://eodhistoricaldata.com/r/?ref=869U7F4J\n"
|
||||
"For your API Key, visit: https://eodhistoricaldata.com/r/?ref=869U7F4J"
|
||||
)
|
||||
return
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -1063,7 +1044,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
if not other_args:
|
||||
console.print(
|
||||
"For your API Key, visit: "
|
||||
"https://academy.santiment.net/products-and-plans/create-an-api-key\n"
|
||||
"https://academy.santiment.net/products-and-plans/create-an-api-key"
|
||||
)
|
||||
return
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -1092,7 +1073,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
if not other_args:
|
||||
console.print(
|
||||
"For your API Key, visit: https://sdk.flipsidecrypto.xyz/shroomdk\n"
|
||||
"For your API Key, visit: https://sdk.flipsidecrypto.xyz/shroomdk"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -1121,7 +1102,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your API Key, visit: https://tokenterminal.com/\n")
|
||||
console.print("For your API Key, visit: https://tokenterminal.com/")
|
||||
return
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
@@ -1150,7 +1131,7 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
)
|
||||
if not other_args:
|
||||
console.print(
|
||||
"For your API Key, https://stocksera.pythonanywhere.com/accounts/developers\n"
|
||||
"For your API Key, https://stocksera.pythonanywhere.com/accounts/developers"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -1161,3 +1142,31 @@ class KeysController(BaseController): # pylint: disable=too-many-public-methods
|
||||
self.status_dict["stocksera"] = keys_model.set_stocksera_key(
|
||||
key=ns_parser.key, persist=True, show_output=True
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_openbb(self, other_args: List[str]):
|
||||
"""Process openbb command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="openbb",
|
||||
description="Set OpenBB Personal Access Token. ",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-k",
|
||||
"--key",
|
||||
type=str,
|
||||
dest="key",
|
||||
help="key",
|
||||
)
|
||||
if not other_args:
|
||||
console.print("For your Personal Access Token, visit: https://openbb.co/")
|
||||
return
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-k")
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
if ns_parser:
|
||||
self.status_dict["openbb"] = keys_model.set_openbb_personal_access_token(
|
||||
key=ns_parser.key, persist=True, show_output=True
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ __docformat__ = "numpy"
|
||||
|
||||
import contextlib
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
@@ -16,7 +17,6 @@ import dotenv
|
||||
import oandapyV20.endpoints.pricing
|
||||
import pandas as pd
|
||||
import praw
|
||||
import pyEX
|
||||
import quandl
|
||||
import requests
|
||||
import stocksera
|
||||
@@ -36,6 +36,9 @@ from openbb_terminal.cryptocurrency.coinbase_helpers import (
|
||||
from openbb_terminal.helper_funcs import request
|
||||
from openbb_terminal.portfolio.brokers.degiro.degiro_model import DegiroModel
|
||||
from openbb_terminal.rich_config import console
|
||||
from openbb_terminal.session.hub_model import BASE_URL, patch_user_configs
|
||||
from openbb_terminal.session.local_model import SESSION_FILE_PATH
|
||||
from openbb_terminal.session.user import User
|
||||
from openbb_terminal.terminal_helper import suppress_stdout
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -54,20 +57,20 @@ API_DICT: Dict = {
|
||||
"fmp": "FINANCIAL_MODELING_PREP",
|
||||
"quandl": "QUANDL",
|
||||
"polygon": "POLYGON",
|
||||
"intrinio": "INTRINIO",
|
||||
"fred": "FRED",
|
||||
"news": "NEWSAPI",
|
||||
"tradier": "TRADIER",
|
||||
"cmc": "COINMARKETCAP",
|
||||
"finnhub": "FINNHUB",
|
||||
"iex": "IEXCLOUD",
|
||||
"reddit": "REDDIT",
|
||||
"twitter": "TWITTER",
|
||||
"rh": "ROBINHOOD",
|
||||
"degiro": "DEGIRO",
|
||||
"oanda": "OANDA",
|
||||
"openbb": "OPENBB",
|
||||
"binance": "BINANCE",
|
||||
"bitquery": "BITQUERY",
|
||||
"si": "SENTIMENT_INVESTOR",
|
||||
"coinbase": "COINBASE",
|
||||
"walert": "WHALE_ALERT",
|
||||
"glassnode": "GLASSNODE",
|
||||
@@ -227,7 +230,7 @@ def set_key(env_var_name: str, env_var_value: str, persist: bool = False) -> Non
|
||||
If True, api key change will be global, i.e. it will affect terminal environment variables.
|
||||
By default, False.
|
||||
"""
|
||||
if persist:
|
||||
if persist and User.is_guest():
|
||||
os.environ[env_var_name] = env_var_value
|
||||
dotenv.set_key(str(USER_ENV_FILE), env_var_name, env_var_value)
|
||||
|
||||
@@ -238,6 +241,20 @@ def set_key(env_var_name: str, env_var_value: str, persist: bool = False) -> Non
|
||||
# Set cfg.env_var_name = env_var_value
|
||||
setattr(cfg, env_var_name, env_var_value)
|
||||
|
||||
# Send api key to server
|
||||
if (
|
||||
not User.is_guest()
|
||||
and User.is_sync_enabled()
|
||||
and env_var_name not in cfg.SENSITIVE_KEYS
|
||||
and (env_var_name.startswith("API_") or env_var_name.startswith("OPENBB_"))
|
||||
):
|
||||
patch_user_configs(
|
||||
key=env_var_name,
|
||||
value=env_var_value,
|
||||
type_="keys",
|
||||
auth_header=User.get_auth_header(),
|
||||
)
|
||||
|
||||
|
||||
def get_keys(show: bool = False) -> pd.DataFrame:
|
||||
"""Get currently set API keys.
|
||||
@@ -266,14 +283,17 @@ def get_keys(show: bool = False) -> pd.DataFrame:
|
||||
|
||||
# TODO: Refactor api variables without prefix API_ and extend API_SOURCE_KEY format
|
||||
|
||||
var_list = [v for v in dir(cfg) if v.startswith("API_")]
|
||||
var_list = [v for v in dir(cfg) if v.startswith("API_") or v.startswith("OPENBB_")]
|
||||
|
||||
current_keys = {}
|
||||
|
||||
for cfg_var_name in var_list:
|
||||
cfg_var_value = getattr(cfg, cfg_var_name)
|
||||
if cfg_var_value != "REPLACE_ME":
|
||||
current_keys[cfg_var_name[4:]] = cfg_var_value
|
||||
if cfg_var_name.startswith("OPENBB_"):
|
||||
current_keys[cfg_var_name] = cfg_var_value
|
||||
else:
|
||||
current_keys[cfg_var_name[4:]] = cfg_var_value
|
||||
|
||||
if current_keys:
|
||||
df = pd.DataFrame.from_dict(current_keys, orient="index")
|
||||
@@ -345,7 +365,7 @@ def check_av_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_PASSED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -412,7 +432,7 @@ def check_fmp_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_INCONCLUSIVE
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -474,7 +494,7 @@ def check_quandl_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -541,7 +561,7 @@ def check_polygon_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_INCONCLUSIVE
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -607,7 +627,7 @@ def check_fred_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_INCONCLUSIVE
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -673,7 +693,7 @@ def check_news_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_INCONCLUSIVE
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -744,7 +764,7 @@ def check_tradier_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_INCONCLUSIVE
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -806,7 +826,7 @@ def check_cmc_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -872,70 +892,7 @@ def check_finnhub_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_INCONCLUSIVE
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
|
||||
return str(status)
|
||||
|
||||
|
||||
def set_iex_key(key: str, persist: bool = False, show_output: bool = False) -> str:
|
||||
"""Set IEX Cloud key
|
||||
|
||||
Parameters
|
||||
----------
|
||||
key: str
|
||||
API key
|
||||
persist: bool, optional
|
||||
If False, api key change will be contained to where it was changed. For example, a Jupyter notebook session.
|
||||
If True, api key change will be global, i.e. it will affect terminal environment variables.
|
||||
By default, False.
|
||||
show_output: bool, optional
|
||||
Display status string or not. By default, False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Status of key set
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> openbb.keys.iex(key="example_key")
|
||||
"""
|
||||
|
||||
set_key("OPENBB_API_IEX_TOKEN", key, persist)
|
||||
return check_iex_key(show_output)
|
||||
|
||||
|
||||
def check_iex_key(show_output: bool = False) -> str:
|
||||
"""Check IEX Cloud key
|
||||
|
||||
Parameters
|
||||
----------
|
||||
show_output: bool, optional
|
||||
Display status string or not. By default, False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Status of key set
|
||||
"""
|
||||
|
||||
if cfg.API_IEX_TOKEN == "REPLACE_ME": # nosec
|
||||
logger.info("IEX Cloud key not defined")
|
||||
status = KeyStatus.NOT_DEFINED
|
||||
else:
|
||||
try:
|
||||
pyEX.Client( # pylint: disable=no-member
|
||||
api_token=cfg.API_IEX_TOKEN, version="v1"
|
||||
).quote(symbol="AAPL")
|
||||
logger.info("IEX Cloud key defined, test passed")
|
||||
status = KeyStatus.DEFINED_TEST_PASSED
|
||||
except Exception as _: # noqa: F841
|
||||
logger.warning("IEX Cloud key defined, test failed")
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -1051,7 +1008,7 @@ def check_reddit_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -1127,7 +1084,7 @@ def check_bitquery_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -1222,7 +1179,7 @@ def check_twitter_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_INCONCLUSIVE
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -1291,7 +1248,7 @@ def check_rh_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_NOT_TESTED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -1382,7 +1339,7 @@ def check_degiro_key(show_output: bool = False) -> str:
|
||||
del dg # ensure the object is destroyed explicitly
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -1468,7 +1425,7 @@ def check_oanda_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -1543,81 +1500,7 @@ def check_binance_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
|
||||
return str(status)
|
||||
|
||||
|
||||
def set_si_key(
|
||||
key: str,
|
||||
persist: bool = False,
|
||||
show_output: bool = False,
|
||||
) -> str:
|
||||
"""Set Sentimentinvestor key.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
key: str
|
||||
API key
|
||||
persist: bool, optional
|
||||
If False, api key change will be contained to where it was changed. For example, a Jupyter notebook session.
|
||||
If True, api key change will be global, i.e. it will affect terminal environment variables.
|
||||
By default, False.
|
||||
show_output: bool, optional
|
||||
Display status string or not. By default, False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Status of key set
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> openbb.keys.si(key="example_key")
|
||||
"""
|
||||
|
||||
set_key("OPENBB_API_SENTIMENTINVESTOR_TOKEN", key, persist)
|
||||
|
||||
return check_si_key(show_output)
|
||||
|
||||
|
||||
def check_si_key(show_output: bool = False) -> str:
|
||||
"""Check Sentimentinvestor key
|
||||
|
||||
Parameters
|
||||
----------
|
||||
show_output: bool, optional
|
||||
Display status string or not. By default, False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Status of key set
|
||||
"""
|
||||
|
||||
si_keys = [cfg.API_SENTIMENTINVESTOR_TOKEN]
|
||||
if "REPLACE_ME" in si_keys:
|
||||
logger.info("Sentiment Investor key not defined")
|
||||
status = KeyStatus.NOT_DEFINED
|
||||
else:
|
||||
try:
|
||||
account = request(
|
||||
f"https://api.sentimentinvestor.com/v1/trending"
|
||||
f"?token={cfg.API_SENTIMENTINVESTOR_TOKEN}"
|
||||
)
|
||||
if account.ok and account.json().get("success", False):
|
||||
logger.info("Sentiment Investor key defined, test passed")
|
||||
status = KeyStatus.DEFINED_TEST_PASSED
|
||||
else:
|
||||
logger.warning("Sentiment Investor key defined, test failed")
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
except Exception:
|
||||
logger.warning("Sentiment Investor key defined, test failed")
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -1706,7 +1589,7 @@ def check_coinbase_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_PASSED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -1775,7 +1658,7 @@ def check_walert_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -1848,7 +1731,7 @@ def check_glassnode_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -1919,7 +1802,7 @@ def check_coinglass_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_INCONCLUSIVE
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -1982,7 +1865,7 @@ def check_cpanic_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -2052,7 +1935,7 @@ def check_ethplorer_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -2143,7 +2026,7 @@ def check_smartstake_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -2200,7 +2083,7 @@ def check_github_key(show_output: bool = False) -> str:
|
||||
# only after certain amount of requests the user will get rate limited
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -2267,7 +2150,7 @@ def check_messari_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -2329,7 +2212,7 @@ def check_eodhd_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -2409,7 +2292,7 @@ def check_santiment_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -2481,7 +2364,7 @@ def check_shroom_key(show_output: bool = False) -> str:
|
||||
logger.warning("Shroom key defined, test failed")
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -2543,7 +2426,7 @@ def check_tokenterminal_key(show_output: bool = False) -> str:
|
||||
status = KeyStatus.DEFINED_TEST_PASSED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -2604,5 +2487,151 @@ def check_stocksera_key(show_output: bool = False):
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize() + "\n")
|
||||
console.print(status.colorize())
|
||||
return str(status)
|
||||
|
||||
|
||||
def set_openbb_personal_access_token(
|
||||
key: str, persist: bool = False, show_output: bool = False
|
||||
):
|
||||
"""Set OpenBB Personal Access Token.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
key: str
|
||||
Personal Access Token
|
||||
persist: bool, optional
|
||||
If False, api key change will be contained to where it was changed. For example, a Jupyter notebook session.
|
||||
If True, api key change will be global, i.e. it will affect terminal environment variables.
|
||||
By default, False.
|
||||
show_output: bool, optional
|
||||
Display status string or not. By default, False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Status of key set
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> openbb.keys.openbb(key="example_key")
|
||||
"""
|
||||
set_key("OPENBB_OPENBB_PERSONAL_ACCESS_TOKEN", key, persist)
|
||||
return check_openbb_personal_access_token(show_output)
|
||||
|
||||
|
||||
def check_openbb_personal_access_token(show_output: bool = False):
|
||||
"""Check OpenBB Personal Access Token
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Status of key set
|
||||
"""
|
||||
if cfg.OPENBB_PERSONAL_ACCESS_TOKEN == "REPLACE_ME":
|
||||
logger.info("OpenBB Personal Access Token not defined")
|
||||
status = KeyStatus.NOT_DEFINED
|
||||
else:
|
||||
try:
|
||||
access_token = ""
|
||||
|
||||
# TODO: is there a better way to test the key?
|
||||
# This requires a valid session file
|
||||
|
||||
if os.path.isfile(SESSION_FILE_PATH):
|
||||
with open(SESSION_FILE_PATH) as f:
|
||||
access_token = json.load(f).get("access_token")
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {access_token}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
response = request(
|
||||
url=f"{BASE_URL}sdk/token", method="GET", headers=headers
|
||||
)
|
||||
|
||||
token = response.json().get("token")
|
||||
|
||||
if (
|
||||
response.status_code == 200
|
||||
and token == cfg.OPENBB_PERSONAL_ACCESS_TOKEN
|
||||
):
|
||||
logger.info("OpenBB Personal Access Token defined, test passed")
|
||||
status = KeyStatus.DEFINED_TEST_PASSED
|
||||
else:
|
||||
logger.warning("OpenBB Personal Access Token. defined, test failed")
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
except requests.exceptions.RequestException:
|
||||
logger.warning("OpenBB Personal Access Token. defined, test failed")
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize())
|
||||
return str(status)
|
||||
|
||||
|
||||
def set_intrinio_key(key: str, persist: bool = False, show_output: bool = False) -> str:
|
||||
"""Set Intrinio key
|
||||
|
||||
Parameters
|
||||
----------
|
||||
key: str
|
||||
API key
|
||||
persist: bool, optional
|
||||
If False, api key change will be contained to where it was changed. For example, a Jupyter notebook session.
|
||||
If True, api key change will be global, i.e. it will affect terminal environment variables.
|
||||
By default, False.
|
||||
show_output: bool, optional
|
||||
Display status string or not. By default, False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Status of key set
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> openbb.keys.intrinio(key="example_key")
|
||||
"""
|
||||
|
||||
set_key("OPENBB_API_INTRINIO_KEY", key, persist)
|
||||
return check_intrinio_key(show_output)
|
||||
|
||||
|
||||
def check_intrinio_key(show_output: bool = False) -> str:
|
||||
"""Check Polygon key
|
||||
|
||||
Parameters
|
||||
----------
|
||||
show_output: bool
|
||||
Display status string or not. By default, False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Status of key set
|
||||
"""
|
||||
|
||||
if cfg.API_INTRINIO_KEY == "REPLACE_ME":
|
||||
logger.info("Intrinio key not defined")
|
||||
status = KeyStatus.NOT_DEFINED
|
||||
else:
|
||||
r = request(
|
||||
f"https://api-v2.intrinio.com/securities/AAPL/prices?api_key={cfg.API_INTRINIO_KEY}"
|
||||
)
|
||||
if r.status_code in [403, 401, 429]:
|
||||
logger.warning("Intrinio key defined, test failed")
|
||||
status = KeyStatus.DEFINED_TEST_FAILED
|
||||
elif r.status_code == 200:
|
||||
logger.info("Intrinio key defined, test passed")
|
||||
status = KeyStatus.DEFINED_TEST_PASSED
|
||||
else:
|
||||
logger.warning("Intrinio key defined, test inconclusive")
|
||||
status = KeyStatus.DEFINED_TEST_INCONCLUSIVE
|
||||
|
||||
if show_output:
|
||||
console.print(status.colorize())
|
||||
|
||||
return str(status)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
@@ -8,6 +7,8 @@ from prompt_toolkit import PromptSession
|
||||
from prompt_toolkit.eventloop.inputhook import set_eventloop_with_inputhook
|
||||
from prompt_toolkit.history import FileHistory
|
||||
|
||||
from openbb_terminal.core.config.paths import HIST_FILE_PATH
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -47,12 +48,10 @@ def inputhook(inputhook_context):
|
||||
return False
|
||||
|
||||
|
||||
history_file = os.path.join(os.path.expanduser("~"), ".openbb_terminal.his")
|
||||
|
||||
try:
|
||||
if not is_jupyter() and not is_papermill():
|
||||
session: Optional[PromptSession] = PromptSession(
|
||||
history=FileHistory(history_file)
|
||||
history=FileHistory(str(HIST_FILE_PATH))
|
||||
)
|
||||
set_eventloop_with_inputhook(inputhook)
|
||||
else:
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"stocks": {
|
||||
"search": ["FinanceDatabase"],
|
||||
"quote": ["FinancialModelingPrep", "YahooFinance"],
|
||||
"quote": ["FinancialModelingPrep"],
|
||||
"tob": ["CBOE"],
|
||||
"candle": [],
|
||||
"codes": ["Polygon"],
|
||||
"news": ["Feedparser", "NewsApi"],
|
||||
"load": ["YahooFinance", "IEXCloud", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"load": ["YahooFinance", "AlphaVantage", "Polygon", "EODHD", "Intrinio"],
|
||||
"th": {
|
||||
"symbol": ["FinanceDatabase"],
|
||||
"open": ["Bursa"],
|
||||
@@ -23,17 +23,18 @@
|
||||
"set": [],
|
||||
"scr": ["Syncretism"]
|
||||
},
|
||||
"load": ["YahooFinance", "Tradier", "Nasdaq"],
|
||||
"exp": ["YahooFinance", "Tradier", "Nasdaq"],
|
||||
"load": ["YahooFinance", "Tradier", "Nasdaq", "Intrinio"],
|
||||
"exp": ["YahooFinance", "Tradier", "Nasdaq", "Intrinio"],
|
||||
"pcr": ["AlphaQuery"],
|
||||
"info": ["Barchart"],
|
||||
"chains": ["YahooFinance", "Tradier", "Nasdaq"],
|
||||
"chains": ["YahooFinance", "Tradier", "Nasdaq", "Intrinio"],
|
||||
"eodchain":["Intrinio"],
|
||||
"oi": ["YahooFinance", "Tradier", "Nasdaq"],
|
||||
"vol": ["YahooFinance", "Tradier", "Nasdaq"],
|
||||
"voi": ["YahooFinance", "Tradier", "Nasdaq"],
|
||||
"hist": ["ChartExchange", "Tradier"],
|
||||
"hist": ["ChartExchange", "Tradier", "Intrinio"],
|
||||
"vsurf": ["YahooFinance"],
|
||||
"grhist": ["Syncretism"],
|
||||
"grhist": ["Syncretism", "Intrinio"],
|
||||
"plot": ["YahooFinance"],
|
||||
"parity": ["YahooFinance"],
|
||||
"binom": ["YahooFinance"],
|
||||
@@ -54,6 +55,7 @@
|
||||
}
|
||||
},
|
||||
"disc": {
|
||||
"filings": ["FinancialModelingPrep"],
|
||||
"pipo": ["Finnhub"],
|
||||
"fipo": ["Finnhub"],
|
||||
"gainers": ["YahooFinance"],
|
||||
@@ -91,7 +93,7 @@
|
||||
"vis": ["StockAnalysis"]
|
||||
},
|
||||
"dps": {
|
||||
"load": ["YahooFinance", "IEXCloud", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"load": ["YahooFinance", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"shorted": ["YahooFinance"],
|
||||
"ctb": ["InteractiveBrokers", "Stocksera"],
|
||||
"hsi": ["Shortinterest"],
|
||||
@@ -112,10 +114,11 @@
|
||||
"financial": ["Finviz"],
|
||||
"ownership": ["Finviz"],
|
||||
"performance": ["Finviz"],
|
||||
"technical": ["Finviz"]
|
||||
"technical": ["Finviz"],
|
||||
"arktrades": ["Cathiesark"]
|
||||
},
|
||||
"ins": {
|
||||
"load": ["YahooFinance", "IEXCloud", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"load": ["YahooFinance", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"view": [],
|
||||
"set": [],
|
||||
"filter": ["OpenInsider"],
|
||||
@@ -144,7 +147,7 @@
|
||||
"tispm": ["OpenInsider"]
|
||||
},
|
||||
"gov": {
|
||||
"load": ["YahooFinance", "IEXCloud", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"load": ["YahooFinance", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"lasttrades": ["QuiverQuant"],
|
||||
"topbuys": ["QuiverQuant"],
|
||||
"topsells": ["QuiverQuant"],
|
||||
@@ -157,7 +160,7 @@
|
||||
"lobbying": ["QuiverQuant"]
|
||||
},
|
||||
"ba": {
|
||||
"load": ["YahooFinance", "IEXCloud", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"load": ["YahooFinance", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"headlines": ["FinBrain"],
|
||||
"snews": ["Finnhub"],
|
||||
"wsb": ["Reddit"],
|
||||
@@ -184,7 +187,7 @@
|
||||
"jctr": ["JimCramer"]
|
||||
},
|
||||
"ca": {
|
||||
"load": ["YahooFinance", "IEXCloud", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"load": ["YahooFinance", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"tsne": ["YahooFinance"],
|
||||
"get": ["Finviz", "Polygon", "Finnhub"],
|
||||
"set": [],
|
||||
@@ -206,7 +209,7 @@
|
||||
"technical": ["Finviz"]
|
||||
},
|
||||
"fa": {
|
||||
"load": ["YahooFinance", "IEXCloud", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"load": ["YahooFinance", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"income": [
|
||||
"FinancialModelingPrep",
|
||||
"Polygon",
|
||||
@@ -237,14 +240,13 @@
|
||||
"metrics": ["FinancialModelingPrep", "AlphaVantage"],
|
||||
"ratios": ["FinancialModelingPrep"],
|
||||
"growth": ["FinancialModelingPrep"],
|
||||
"revfc": ["SeekingAlpha", "YahooFinance"],
|
||||
"revfc": ["SeekingAlpha"],
|
||||
"epsfc": ["SeekingAlpha"],
|
||||
"warnings": ["MarketWatch"],
|
||||
"dcf": ["StockAnalysis"],
|
||||
"dcfc": ["FinancialModelingPrep"],
|
||||
"info": ["YahooFinance"],
|
||||
"shrs": ["YahooFinance"],
|
||||
"sust": ["YahooFinance"],
|
||||
"cal": ["YahooFinance"],
|
||||
"divs": ["YahooFinance"],
|
||||
"splits": ["YahooFinance"],
|
||||
@@ -257,25 +259,24 @@
|
||||
"est": ["BusinessInsider"],
|
||||
"sec": ["MarketWatch", "FinancialModelingPrep"],
|
||||
"supplier": ["CSIMarket"],
|
||||
"customer": ["CSIMarket"],
|
||||
"arktrades": ["Cathiesark"]
|
||||
"customer": ["CSIMarket"]
|
||||
},
|
||||
"bt": {
|
||||
"load": ["YahooFinance", "IEXCloud", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"load": ["YahooFinance", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"whatif": ["YahooFinance"]
|
||||
},
|
||||
"ta": {
|
||||
"load": ["YahooFinance", "IEXCloud", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"load": ["YahooFinance", "AlphaVantage", "Polygon", "EODHD"],
|
||||
"tv": ["TradingView"],
|
||||
"recom": ["TradingView"],
|
||||
"view": ["Finviz"],
|
||||
"summary": ["FinBrain"]
|
||||
},
|
||||
"qa": {
|
||||
"load": ["YahooFinance", "IEXCloud", "AlphaVantage", "Polygon", "EODHD"]
|
||||
"load": ["YahooFinance", "AlphaVantage", "Polygon", "EODHD"]
|
||||
},
|
||||
"pred": {
|
||||
"load": ["YahooFinance", "IEXCloud", "AlphaVantage", "Polygon", "EODHD"]
|
||||
"load": ["YahooFinance", "AlphaVantage", "Polygon", "EODHD"]
|
||||
}
|
||||
},
|
||||
"crypto": {
|
||||
@@ -438,10 +439,8 @@
|
||||
"load": ["YahooFinance"],
|
||||
"overview": ["StockAnalysis"],
|
||||
"holdings": ["StockAnalysis"],
|
||||
"weights": ["YahooFinance"],
|
||||
"summary": ["YahooFinance"],
|
||||
"weights": ["FinancialModelingPrep"],
|
||||
"news": ["NewsApi"],
|
||||
"pir": ["ThePassiveInvestor"],
|
||||
"compare": ["StockAnalysis"],
|
||||
"disc": {
|
||||
"gainers": ["WallStreetJournal"],
|
||||
@@ -520,6 +519,24 @@
|
||||
"tips": ["FRED", "EconDB"],
|
||||
"tbffr": ["FRED"],
|
||||
"ty": ["YahooFinance"]
|
||||
"portfolio": {
|
||||
"po": {
|
||||
"maxsharpe": ["YahooFinance"],
|
||||
"minrisk": ["YahooFinance"],
|
||||
"maxutil": ["YahooFinance"],
|
||||
"maxret": ["YahooFinance"],
|
||||
"maxdiv": ["YahooFinance"],
|
||||
"maxdecorr": ["YahooFinance"],
|
||||
"blacklitterman": ["FinancialModelingPrep"],
|
||||
"ef": ["YahooFinance"],
|
||||
"riskparity": ["YahooFinance"],
|
||||
"relriskparity": ["YahooFinance"],
|
||||
"hrp": ["YahooFinance"],
|
||||
"herc": ["YahooFinance"],
|
||||
"nco": ["YahooFinance"],
|
||||
"equal": ["YahooFinance"],
|
||||
"mktcap": ["FinancialModelingPrep"]
|
||||
}
|
||||
},
|
||||
"alternative": {
|
||||
"oss": {
|
||||
|
||||
35
openbb_terminal/miscellaneous/i18n/en.yml
vendored
35
openbb_terminal/miscellaneous/i18n/en.yml
vendored
@@ -10,6 +10,7 @@ en:
|
||||
keys: set API keys and check their validity
|
||||
featflags: enable and disable feature flags
|
||||
sources: select your preferred data sources
|
||||
account: manage your OpenBB account
|
||||
settings: tune settings (export folder, timezone, language, plot size)
|
||||
_scripts_: Record and execute your own .openbb routine scripts
|
||||
record: start recording current session
|
||||
@@ -17,6 +18,17 @@ en:
|
||||
exe: execute .openbb routine scripts (use exe --example for an example)
|
||||
_configure_: Configure your own terminal
|
||||
_main_menu_: Main menu
|
||||
account/_authentication_: Authentication
|
||||
account/logout: Log out from OpenBB account
|
||||
account/_info_: Cloud storage of keys, settings and feature flags
|
||||
account/sync: Turns the cloud synchronization on/off
|
||||
account/pull: Pull data from cloud
|
||||
account/clear: Clear data from cloud
|
||||
account/_routines_: Cloud storage of routines
|
||||
account/list: List available routines
|
||||
account/upload: Upload routine
|
||||
account/download: Download routine
|
||||
account/delete: Delete routine
|
||||
keys/_keys_: This menu allows you to set your own API keys. Type 'about' for more information
|
||||
keys/mykeys: get your defined keys
|
||||
keys/_status_: Status
|
||||
@@ -73,7 +85,7 @@ en:
|
||||
settings/_timezone: Timezone
|
||||
settings/source: specify data source file
|
||||
settings/_data_source: Data sources
|
||||
_others_: Others
|
||||
_toolkits_: OpenBB Toolkits
|
||||
stocks: access historical pricing data, options, sector and industry, and overall due diligence
|
||||
crypto: dive into onchain data, tokenomics, circulation supply, nfts and more
|
||||
etf: exchange traded funds. Historical pricing, compare holdings and screening
|
||||
@@ -134,6 +146,7 @@ en:
|
||||
stocks/options/parity: shows whether options are above or below expected price
|
||||
stocks/options/binom: shows the value of an option using binomial options pricing
|
||||
stocks/options/greeks: shows the greeks for a given option
|
||||
stocks/options/eodchain: gets option chain at a specific date
|
||||
stocks/options/pricing: shows options pricing and risk neutral valuation
|
||||
stocks/options/hedge: shows portfolio weights in order to neutralise delta
|
||||
stocks/options/screen/view: view available presets (or one in particular)
|
||||
@@ -166,6 +179,7 @@ en:
|
||||
stocks/th/all: show all markets
|
||||
stocks/th/exchange: show one exchange
|
||||
stocks/th/holidays: show all dates on which there is a market holiday
|
||||
stocks/disc/filings: the most-recent form submissions to the SEC
|
||||
stocks/disc/pipo: past IPOs dates
|
||||
stocks/disc/fipo: future IPOs dates
|
||||
stocks/disc/gainers: show latest top gainers
|
||||
@@ -232,6 +246,7 @@ en:
|
||||
stocks/scr/ownership: ownership (e.g. Float, Insider Own, Short Ratio)
|
||||
stocks/scr/performance: performance (e.g. Perf Week, Perf YTD, Volatility M)
|
||||
stocks/scr/technical: technical (e.g. Beta, SMA50, 52W Low, RSI, Change)
|
||||
stocks/scr/arktrades: get ARK trades for a chosen ticker
|
||||
stocks/scr/_screened_tickers: Last screened tickers
|
||||
stocks/scr/ca: take these to comparison analysis menu
|
||||
stocks/ins/view: view available presets
|
||||
@@ -336,8 +351,6 @@ en:
|
||||
stocks/fa/rot: number of analyst ratings over time on a monthly basis
|
||||
stocks/fa/score: value investing scores for any time period
|
||||
stocks/fa/warnings: company warnings according to Sean Seah book
|
||||
stocks/fa/sust: sustainability values (environment, social and governance)
|
||||
stocks/fa/arktrades: get ARK trades for ticker
|
||||
stocks/fa/_management_shareholders: Management and Ownership
|
||||
stocks/fa/mgmt: management team of the company
|
||||
stocks/fa/shrs: shareholders (insiders, institutions and mutual funds)
|
||||
@@ -387,8 +400,8 @@ en:
|
||||
stocks/bt/ema: buy when price exceeds EMA(l)
|
||||
stocks/bt/emacross: buy when EMA(short) > EMA(long)
|
||||
stocks/bt/rsi: buy when RSI < low and sell when RSI > high
|
||||
stocks/bt/load: load a specific stock ticker for analysis
|
||||
stocks/ta/_ticker: Ticker
|
||||
stocks/ta/tv: open interactive chart on
|
||||
stocks/ta/view: view historical data and trendlines
|
||||
stocks/ta/summary: technical summary report
|
||||
stocks/ta/recom: recommendation based on technical indicators
|
||||
@@ -676,10 +689,8 @@ en:
|
||||
etf/overview: get overview
|
||||
etf/holdings: top company holdings
|
||||
etf/weights: sector weights allocation
|
||||
etf/summary: summary description of the ETF
|
||||
etf/candle: view a candle chart for ETF
|
||||
etf/news: latest news of the company
|
||||
etf/pir: create (multiple) passive investor excel report(s)
|
||||
etf/compare: compare multiple different ETFs
|
||||
etf/ta: technical analysis, ema, macd, rsi, adx, bbands, obv
|
||||
etf/disc/gainers: top gainers
|
||||
@@ -1003,18 +1014,18 @@ en:
|
||||
portfolio/bro/cb/history: show history of your account
|
||||
portfolio/bro/cb/deposits: show all your deposits or internal transfers
|
||||
portfolio/bro/cb/orders: show all your orders
|
||||
portfolio/po/file: file that contains portfolio risk parameters
|
||||
portfolio/po/file: file that contains portfolio risk parameters (use about for the parameter template)
|
||||
portfolio/po/load: load tickers and categories from .xlsx or .csv file (use load --example for an example)
|
||||
portfolio/po/_loaded: Portfolio loaded
|
||||
portfolio/po/_tickers: Tickers
|
||||
portfolio/po/_categories: Categories
|
||||
portfolio/po/params/load: select portfolio parameter file
|
||||
portfolio/po/params/load: select portfolio parameter file (use about for the parameter template)
|
||||
portfolio/po/params: specify and show portfolio risk parameters
|
||||
portfolio/po/_parameter: Parameter file
|
||||
portfolio/po/_mean_risk_optimization_: Mean Risk Optimization
|
||||
portfolio/po/maxsharpe: maximal Sharpe ratio portfolio (a.k.a the tangency portfolio)
|
||||
portfolio/po/minrisk: minimum risk portfolio
|
||||
portfolio/po/maxutil: maximal risk averse utility function, given some risk aversion parameter
|
||||
portfolio/po/maxutil: maximal risk averse utility function
|
||||
portfolio/po/maxret: maximal return portfolio
|
||||
portfolio/po/maxdiv: maximum diversification portfolio
|
||||
portfolio/po/maxdecorr: maximum decorrelation portfolio
|
||||
@@ -1028,10 +1039,8 @@ en:
|
||||
portfolio/po/herc: hierarchical equal risk contribution
|
||||
portfolio/po/nco: nested clustering optimization
|
||||
portfolio/po/_other_optimization_techniques_: Other Optimization Techniques
|
||||
portfolio/po/equal: equally weighted
|
||||
portfolio/po/mktcap: weighted according to market cap (property marketCap)
|
||||
portfolio/po/dividend: weighted according to dividend yield (property dividendYield)
|
||||
portfolio/po/property: weight according to selected info property
|
||||
portfolio/po/equal: equal weighted portfolio
|
||||
portfolio/po/mktcap: weighted according to market cap
|
||||
portfolio/po/_optimized_portfolio: Optimized portfolio
|
||||
portfolio/po/rpf: remove portfolios from the list of saved portfolios
|
||||
portfolio/po/show: show selected portfolios and categories from the list of saved portfolios
|
||||
|
||||
7
openbb_terminal/miscellaneous/integration_tests_scripts/account/test_account.openbb
vendored
Normal file
7
openbb_terminal/miscellaneous/integration_tests_scripts/account/test_account.openbb
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# GUEST
|
||||
|
||||
whoami
|
||||
account
|
||||
|
||||
# AUTHENTICATED USER
|
||||
# Not implemented
|
||||
@@ -3,13 +3,11 @@ load spy
|
||||
overview
|
||||
holdings
|
||||
weights
|
||||
weights -m 10
|
||||
summary
|
||||
weights --raw
|
||||
candle
|
||||
candle --raw
|
||||
candle --sort adjclose --reverse --raw
|
||||
candle -t --ma 4
|
||||
news
|
||||
pir
|
||||
compare spy,ivv
|
||||
exit
|
||||
|
||||
@@ -33,7 +33,7 @@ rsi -l 20 -u 80 -p 12
|
||||
rsi --spy
|
||||
rsi --no_bench --no_short
|
||||
..
|
||||
load ${ticker=tsla} --source IEXCloud
|
||||
load ${ticker=tsla}
|
||||
bt
|
||||
whatif
|
||||
whatif -n 2.34
|
||||
|
||||
@@ -9,6 +9,7 @@ disc
|
||||
help
|
||||
h
|
||||
?
|
||||
filings
|
||||
pipo
|
||||
fipo
|
||||
gainers
|
||||
|
||||
@@ -27,7 +27,6 @@ enterprise
|
||||
shrs
|
||||
shrs major
|
||||
shrs mutualfund
|
||||
sust
|
||||
divs
|
||||
divs -p
|
||||
splits
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
script: test_stocks_filings.openbb
|
||||
- - - - - - - - - -
|
||||
|
||||
stocks
|
||||
filings
|
||||
filings --limit 200
|
||||
filings --today
|
||||
filings --pages 30 --limit 30000
|
||||
exit
|
||||
@@ -237,13 +237,7 @@ econometrics.pols,openbb_terminal.econometrics.regression_model.get_pols,
|
||||
econometrics.re,openbb_terminal.econometrics.regression_model.get_re,
|
||||
econometrics.get_regression_data,openbb_terminal.econometrics.regression_model.get_regression_data,
|
||||
econometrics.panel,openbb_terminal.econometrics.regression_model.get_regressions_results,openbb_terminal.econometrics.regression_view.display_panel
|
||||
economy.cpi,openbb_terminal.economy.alphavantage_model.get_cpi,openbb_terminal.economy.alphavantage_view.display_cpi
|
||||
economy.gdpc,openbb_terminal.economy.alphavantage_model.get_gdp_capita,openbb_terminal.economy.alphavantage_view.display_gdp_capita
|
||||
economy.inf,openbb_terminal.economy.alphavantage_model.get_inflation,openbb_terminal.economy.alphavantage_view.display_inflation
|
||||
economy.gdp,openbb_terminal.economy.alphavantage_model.get_real_gdp,openbb_terminal.economy.alphavantage_view.display_real_gdp
|
||||
economy.rtps,openbb_terminal.economy.alphavantage_model.get_sector_data,openbb_terminal.economy.alphavantage_view.realtime_performance_sector
|
||||
economy.tyld,openbb_terminal.economy.alphavantage_model.get_treasury_yield,openbb_terminal.economy.alphavantage_view.display_treasury_yield
|
||||
economy.unemp,openbb_terminal.economy.alphavantage_model.get_unemployment,openbb_terminal.economy.alphavantage_view.display_unemployment
|
||||
economy.macro,openbb_terminal.economy.econdb_model.get_aggregated_macro_data,openbb_terminal.economy.econdb_view.show_macro_data
|
||||
economy.macro_parameters,openbb_terminal.economy.econdb_model.get_macro_parameters,
|
||||
economy.macro_countries,openbb_terminal.economy.econdb_model.get_macro_countries,
|
||||
@@ -278,8 +272,7 @@ etf.holdings,openbb_terminal.etf.stockanalysis_model.get_etf_holdings,
|
||||
etf.symbols,openbb_terminal.etf.stockanalysis_model.get_all_names_symbols,
|
||||
etf.overview,openbb_terminal.etf.stockanalysis_model.get_etf_overview,
|
||||
etf.etf_by_name,openbb_terminal.etf.stockanalysis_model.get_etfs_by_name,
|
||||
etf.weights,openbb_terminal.etf.yfinance_model.get_etf_sector_weightings,
|
||||
etf.summary,openbb_terminal.etf.yfinance_model.get_etf_summary_description,
|
||||
etf.weights,openbb_terminal.etf.fmp_model.get_etf_sector_weightings,
|
||||
etf.compare,openbb_terminal.etf.stockanalysis_model.compare_etfs,
|
||||
forex.get_currency_list,openbb_terminal.forex.av_model.get_currency_list,
|
||||
forex.quote,openbb_terminal.forex.sdk_helpers.quote,
|
||||
@@ -390,7 +383,7 @@ stocks.disc.gtech,openbb_terminal.stocks.discovery.yahoofinance_model.get_gtech,
|
||||
stocks.disc.losers,openbb_terminal.stocks.discovery.yahoofinance_model.get_losers,
|
||||
stocks.disc.ugs,openbb_terminal.stocks.discovery.yahoofinance_model.get_ugs,
|
||||
stocks.disc.ulc,openbb_terminal.stocks.discovery.yahoofinance_model.get_ulc,
|
||||
stocks.fa.arktrades,openbb_terminal.stocks.fundamental_analysis.ark_model.get_ark_trades_by_ticker,
|
||||
stocks.screener.arktrades,openbb_terminal.stocks.screener.ark_model.get_ark_trades_by_ticker,
|
||||
stocks.fa.est,openbb_terminal.stocks.fundamental_analysis.business_insider_model.get_estimates,
|
||||
stocks.fa.pt,openbb_terminal.stocks.fundamental_analysis.business_insider_model.get_price_target_from_analysts,openbb_terminal.stocks.fundamental_analysis.business_insider_view.price_target_from_analysts
|
||||
stocks.fa.customer,openbb_terminal.stocks.fundamental_analysis.csimarket_model.get_customers,
|
||||
@@ -420,7 +413,7 @@ stocks.fa.growth,openbb_terminal.stocks.fundamental_analysis.fmp_model.get_finan
|
||||
stocks.fa.metrics,openbb_terminal.stocks.fundamental_analysis.fmp_model.get_key_metrics,
|
||||
stocks.fa.ratios,openbb_terminal.stocks.fundamental_analysis.fmp_model.get_key_ratios,
|
||||
stocks.fa.profile,openbb_terminal.stocks.fundamental_analysis.fmp_model.get_profile,
|
||||
stocks.quote_fmp,openbb_terminal.stocks.fundamental_analysis.fmp_model.get_quote,
|
||||
stocks.quote,openbb_terminal.stocks.stocks_model.get_quote,
|
||||
stocks.fa.score,openbb_terminal.stocks.fundamental_analysis.fmp_model.get_score,
|
||||
stocks.fa.data,openbb_terminal.stocks.fundamental_analysis.finviz_model.get_data,
|
||||
stocks.fa.cal,openbb_terminal.stocks.fundamental_analysis.yahoo_finance_model.get_calendar_earnings,
|
||||
@@ -429,7 +422,6 @@ stocks.fa.info,openbb_terminal.stocks.fundamental_analysis.yahoo_finance_model.g
|
||||
stocks.fa.mktcap,openbb_terminal.stocks.fundamental_analysis.yahoo_finance_model.get_mktcap,openbb_terminal.stocks.fundamental_analysis.yahoo_finance_view.display_mktcap
|
||||
stocks.fa.shrs,openbb_terminal.stocks.fundamental_analysis.yahoo_finance_model.get_shareholders,
|
||||
stocks.fa.splits,openbb_terminal.stocks.fundamental_analysis.yahoo_finance_model.get_splits,openbb_terminal.stocks.fundamental_analysis.yahoo_finance_view.display_splits
|
||||
stocks.fa.sust,openbb_terminal.stocks.fundamental_analysis.yahoo_finance_model.get_sustainability,
|
||||
stocks.fa.revfc,openbb_terminal.stocks.fundamental_analysis.seeking_alpha_model.get_estimates_rev,
|
||||
stocks.fa.epsfc,openbb_terminal.stocks.fundamental_analysis.seeking_alpha_model.get_estimates_eps,
|
||||
stocks.gov.qtrcontracts,openbb_terminal.stocks.government.quiverquant_model.get_qtr_contracts,openbb_terminal.stocks.government.quiverquant_view.display_qtr_contracts
|
||||
@@ -460,21 +452,20 @@ stocks.ins.blis,openbb_terminal.stocks.insider.sdk_helper.blis,
|
||||
stocks.ins.blos,openbb_terminal.stocks.insider.sdk_helper.blos,
|
||||
stocks.ins.blcs,openbb_terminal.stocks.insider.sdk_helper.blcs,
|
||||
stocks.options.pcr,openbb_terminal.stocks.options.alphaquery_model.get_put_call_ratio,openbb_terminal.stocks.options.alphaquery_view.display_put_call_ratio
|
||||
stocks.options.info,openbb_terminal.stocks.options.yfinance_model.get_info,openbb_terminal.stocks.options.barchart_view.print_options_data
|
||||
stocks.options.hist_ce,openbb_terminal.stocks.options.chartexchange_model.get_option_history,openbb_terminal.stocks.options.chartexchange_view.display_raw
|
||||
stocks.options.info,openbb_terminal.stocks.options.barchart_model.get_options_info,openbb_terminal.stocks.options.barchart_view.print_options_data
|
||||
stocks.options.unu,openbb_terminal.stocks.options.fdscanner_model.unusual_options,openbb_terminal.stocks.options.fdscanner_view.display_options
|
||||
stocks.options.screen.check_presets,openbb_terminal.stocks.options.screen.syncretism_model.check_presets,
|
||||
stocks.options.grhist,openbb_terminal.stocks.options.screen.syncretism_model.get_historical_greeks,openbb_terminal.stocks.options.screen.syncretism_view.view_historical_greeks
|
||||
stocks.options.screen.screener_output,openbb_terminal.stocks.options.screen.syncretism_model.get_screener_output,openbb_terminal.stocks.options.screen.syncretism_view.view_screener_output
|
||||
stocks.options.hist,openbb_terminal.stocks.options.options_sdk_helper.hist,
|
||||
stocks.options.chains,openbb_terminal.stocks.options.options_sdk_helper.get_full_option_chain,
|
||||
stocks.options.eodchain,openbb_terminal.stocks.options.intrinio_model.get_full_chain_eod,
|
||||
stocks.options.price,openbb_terminal.stocks.options.options_sdk_helper.get_option_current_price,
|
||||
stocks.options.vol,openbb_terminal.stocks.options.options_view.plot_vol,
|
||||
stocks.options.oi,openbb_terminal.stocks.options.options_view.plot_oi,
|
||||
stocks.options.voi,openbb_terminal.stocks.options.options_view.plot_voi,
|
||||
stocks.options.last_price,openbb_terminal.stocks.options.tradier_model.get_last_price,
|
||||
stocks.options.expirations,openbb_terminal.stocks.options.options_sdk_helper.get_option_expirations,
|
||||
stocks.options.process_chains,openbb_terminal.stocks.options.tradier_model.process_chains,
|
||||
stocks.options.generate_data,openbb_terminal.stocks.options.yfinance_model.generate_data,
|
||||
stocks.options.dte,openbb_terminal.stocks.options.yfinance_model.get_dte,
|
||||
stocks.options.vsurf,openbb_terminal.stocks.options.yfinance_model.get_iv_surface,openbb_terminal.stocks.options.yfinance_view.display_vol_surface
|
||||
@@ -508,7 +499,6 @@ stocks.load,openbb_terminal.stocks.stocks_helper.load,
|
||||
etf.load,openbb_terminal.stocks.stocks_helper.load,
|
||||
stocks.process_candle,openbb_terminal.stocks.stocks_helper.process_candle,
|
||||
stocks.search,openbb_terminal.stocks.stocks_helper.search,
|
||||
stocks.quote_yf,openbb_terminal.stocks.stocks_model.load_quote,
|
||||
stocks.tob,openbb_terminal.stocks.cboe_model.get_top_of_book,
|
||||
stocks.candle,openbb_terminal.stocks.stocks_helper.display_candle,
|
||||
crypto.load,openbb_terminal.cryptocurrency.cryptocurrency_helpers.load,
|
||||
@@ -554,8 +544,13 @@ keys.santiment,openbb_terminal.keys_model.set_santiment_key,
|
||||
keys.tokenterminal,openbb_terminal.keys_model.set_tokenterminal_key,
|
||||
keys.shroom,openbb_terminal.keys_model.set_shroom_key,
|
||||
keys.stocksera,openbb_terminal.keys_model.set_stocksera_key,
|
||||
keys.openbb,openbb_terminal.keys_model.set_openbb_personal_access_token,
|
||||
futures.search,openbb_terminal.futures.yfinance_model.get_search_futures,
|
||||
futures.historical,openbb_terminal.futures.yfinance_model.get_historical_futures,openbb_terminal.futures.yfinance_view.display_historical
|
||||
futures.curve,openbb_terminal.futures.yfinance_model.get_curve_futures,openbb_terminal.futures.yfinance_view.display_curve
|
||||
economy.get_groups,openbb_terminal.economy.finviz_model.get_groups,
|
||||
stocks.filings,openbb_terminal.stocks.fundamental_analysis.fmp_model.get_filings,openbb_terminal.stocks.fundamental_analysis.fmp_view.display_filings
|
||||
stocks.disc.filings,openbb_terminal.stocks.fundamental_analysis.fmp_model.get_filings,openbb_terminal.stocks.discovery.fmp_view.display_filings
|
||||
stocks.fa.sec_fmp,openbb_terminal.stocks.fundamental_analysis.fmp_model.get_filings,openbb_terminal.stocks.fundamental_analysis.fmp_view.display_filings
|
||||
login,openbb_terminal.session.sdk_session.login,
|
||||
logout,openbb_terminal.session.sdk_session.logout,
|
||||
whoami,openbb_terminal.session.sdk_session.whoami,
|
||||
|
@@ -1,10 +1,7 @@
|
||||
trail,model,view
|
||||
portfolio.po.blacklitterman,openbb_terminal.portfolio.portfolio_optimization.po_model.get_blacklitterman,
|
||||
portfolio.po.dividend,openbb_terminal.portfolio.portfolio_optimization.po_model.get_dividend,
|
||||
portfolio.po.ef,openbb_terminal.portfolio.portfolio_optimization.po_model.get_ef,openbb_terminal.portfolio.portfolio_optimization.po_view.display_ef
|
||||
portfolio.po.equal,openbb_terminal.portfolio.portfolio_optimization.po_model.get_equal,
|
||||
portfolio.po.file,openbb_terminal.portfolio.portfolio_optimization.po_model.load_parameters_file,
|
||||
portfolio.po.get_properties,openbb_terminal.portfolio.portfolio_optimization.optimizer_model.get_properties,
|
||||
portfolio.po.herc,openbb_terminal.portfolio.portfolio_optimization.po_model.get_herc,
|
||||
portfolio.po.hrp,openbb_terminal.portfolio.portfolio_optimization.po_model.get_hrp,
|
||||
portfolio.po.load,openbb_terminal.portfolio.portfolio_optimization.po_model.generate_portfolio,
|
||||
@@ -15,10 +12,8 @@ portfolio.po.maxret,openbb_terminal.portfolio.portfolio_optimization.po_model.ge
|
||||
portfolio.po.maxsharpe,openbb_terminal.portfolio.portfolio_optimization.po_model.get_maxsharpe,
|
||||
portfolio.po.maxutil,openbb_terminal.portfolio.portfolio_optimization.po_model.get_maxutil,
|
||||
portfolio.po.minrisk,openbb_terminal.portfolio.portfolio_optimization.po_model.get_minrisk,
|
||||
portfolio.po.mktcap,openbb_terminal.portfolio.portfolio_optimization.po_model.get_mktcap,
|
||||
portfolio.po.nco,openbb_terminal.portfolio.portfolio_optimization.po_model.get_nco,
|
||||
portfolio.po.plot,openbb_terminal.portfolio.portfolio_optimization.po_view.display_plot,openbb_terminal.portfolio.portfolio_optimization.po_view.display_plot
|
||||
portfolio.po.property,openbb_terminal.portfolio.portfolio_optimization.po_model.get_property,
|
||||
portfolio.po.relriskparity,openbb_terminal.portfolio.portfolio_optimization.po_model.get_relriskparity,
|
||||
portfolio.po.riskparity,openbb_terminal.portfolio.portfolio_optimization.po_model.get_riskparity,
|
||||
portfolio.po.show,openbb_terminal.portfolio.portfolio_optimization.po_model.show,
|
||||
|
||||
|
@@ -1,240 +0,0 @@
|
||||
"""Investpy Model"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Tuple
|
||||
|
||||
import investiny
|
||||
import investpy
|
||||
import pandas as pd
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.rich_config import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def search_funds(by: str = "name", value: str = "") -> pd.DataFrame:
|
||||
"""Search investpy for matching funds
|
||||
|
||||
Parameters
|
||||
----------
|
||||
by : str
|
||||
Field to match on. Can be name, issuer, isin or symbol
|
||||
value : str
|
||||
String that will be searched for
|
||||
|
||||
Returns
|
||||
-------
|
||||
pd.DataFrame
|
||||
Dataframe containing matches
|
||||
"""
|
||||
try:
|
||||
return investpy.funds.search_funds(by=by, value=value)
|
||||
except RuntimeError as e:
|
||||
logger.exception(str(e))
|
||||
return pd.DataFrame()
|
||||
except RequestException:
|
||||
console.print("[red]The request to the website failed[/red]\n")
|
||||
return pd.DataFrame()
|
||||
except ConnectionError:
|
||||
console.print("[red]The request to the website failed[/red]\n")
|
||||
return pd.DataFrame()
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_overview(country: str = "united states", limit: int = 20) -> pd.DataFrame:
|
||||
"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
country: str
|
||||
Country to get overview for
|
||||
limit: int
|
||||
Number of results to get
|
||||
|
||||
Returns
|
||||
-------
|
||||
pd.DataFrame
|
||||
Dataframe containing overview
|
||||
"""
|
||||
try:
|
||||
return investpy.funds.get_funds_overview(
|
||||
country=country, as_json=False, n_results=limit
|
||||
)
|
||||
except RequestException:
|
||||
console.print("[red]The request to the website failed[/red]\n")
|
||||
return pd.DataFrame()
|
||||
except ConnectionError:
|
||||
console.print("[red]The request to the website failed[/red]\n")
|
||||
return pd.DataFrame()
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_fund_symbol_from_name(name: str) -> Tuple[str, str]:
|
||||
"""Get fund symbol from name through investpy
|
||||
|
||||
Parameters
|
||||
----------
|
||||
Name: str
|
||||
Name to get fund symbol of
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple[str, str]
|
||||
Name of Symbol matching provided name, Country in which matching symbol was found
|
||||
"""
|
||||
try:
|
||||
name_search_results = investpy.search_funds(by="name", value=name)
|
||||
except RequestException:
|
||||
console.print("[red]The request to the website failed[/red]\n")
|
||||
return "", ""
|
||||
except ConnectionError:
|
||||
console.print("[red]The request to the website failed[/red]\n")
|
||||
return "", ""
|
||||
if name_search_results.empty:
|
||||
return "", ""
|
||||
symbol = name_search_results.loc[:, "symbol"][0]
|
||||
country = name_search_results.country.values[0]
|
||||
console.print(
|
||||
f"Name: [cyan][italic]{symbol.upper()}[/italic][/cyan] found for {name} in country: {country.title()}."
|
||||
)
|
||||
return symbol, country
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_fund_name_from_symbol(symbol: str) -> Tuple[str, str]:
|
||||
"""Get fund name from symbol from investpy
|
||||
|
||||
Parameters
|
||||
----------
|
||||
symbol: str
|
||||
Symbol to get fund name of
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple[str, str]
|
||||
Name of fund matching provided symbol, Country matching symbol
|
||||
"""
|
||||
try:
|
||||
symbol_search_results = investpy.search_funds(by="symbol", value=symbol)
|
||||
except RequestException:
|
||||
console.print("[red]The request to the website failed[/red]\n")
|
||||
return "", ""
|
||||
except ConnectionError:
|
||||
console.print("[red]The request to the website failed[/red]\n")
|
||||
return "", ""
|
||||
if symbol_search_results.empty:
|
||||
return "", ""
|
||||
name = symbol_search_results.loc[:, "name"][0]
|
||||
country = symbol_search_results.loc[:, "country"][0]
|
||||
console.print(
|
||||
f"Name: [cyan][italic]{name.title()}[/italic][/cyan] found for {symbol} in country: {country.title()}."
|
||||
)
|
||||
return name, country
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_fund_info(name: str, country: str = "united states") -> pd.DataFrame:
|
||||
"""
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: str
|
||||
Name of fund (not symbol) to get information
|
||||
country: str
|
||||
Country of fund
|
||||
|
||||
Returns
|
||||
-------
|
||||
pd.DataFrame
|
||||
Dataframe of fund information
|
||||
"""
|
||||
try:
|
||||
return investpy.funds.get_fund_information(name, country).T
|
||||
except RequestException:
|
||||
console.print("[red]The request to the website failed[/red]\n")
|
||||
return pd.DataFrame()
|
||||
except ConnectionError:
|
||||
console.print("[red]The request to the website failed[/red]\n")
|
||||
return pd.DataFrame()
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_fund_historical(
|
||||
name: str,
|
||||
country: str = "united states",
|
||||
by_name: bool = False,
|
||||
start_date: datetime = (datetime.now() - timedelta(days=366)),
|
||||
end_date: datetime = datetime.now(),
|
||||
) -> Tuple[pd.DataFrame, str, str, str]:
|
||||
"""Get historical fund data
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: str
|
||||
Fund to get data for. If using fund name, include `name=True`
|
||||
country: str
|
||||
Country of fund
|
||||
by_name : bool
|
||||
Flag to search by name instead of symbol
|
||||
start_date: datetime
|
||||
Start date of data in format YYYY-MM-DD
|
||||
end_date: datetime
|
||||
End date of data in format YYYY-MM-DD
|
||||
|
||||
Returns
|
||||
-------
|
||||
Tuple[pd.DataFrame, str, str, str]
|
||||
Dataframe of OHLC prices, Fund name, Fund symbol, Country that matches search results
|
||||
"""
|
||||
try:
|
||||
if by_name:
|
||||
fund_name = name
|
||||
fund_symbol, matching_country = get_fund_symbol_from_name(name)
|
||||
else:
|
||||
fund_symbol = name
|
||||
fund_name, matching_country = get_fund_name_from_symbol(name)
|
||||
|
||||
except RuntimeError as e:
|
||||
logger.exception(str(e))
|
||||
return pd.DataFrame(), name, "", country
|
||||
except RequestException:
|
||||
console.print("[red]The request to the website failed[/red]\n")
|
||||
return pd.DataFrame(), "", "", ""
|
||||
except ConnectionError:
|
||||
console.print("[red]The request to the website failed[/red]\n")
|
||||
return pd.DataFrame(), "", "", ""
|
||||
|
||||
# Note that dates for investpy need to be in the format mm/dd/yyyy
|
||||
from_date = start_date.strftime("%m/%d/%Y")
|
||||
to_date = end_date.strftime("%m/%d/%Y")
|
||||
search_country = matching_country if matching_country else country
|
||||
|
||||
try:
|
||||
# Using investiny to get around the 403 error with investment.com api
|
||||
# We need to get an ID number
|
||||
id_number = int(investiny.search_assets(name, limit=1)[0]["ticker"])
|
||||
data = investiny.historical_data(
|
||||
id_number, from_date=from_date, to_date=to_date
|
||||
)
|
||||
df = pd.DataFrame.from_dict(data).set_index("date")
|
||||
df.columns = [col.title() for col in df.columns]
|
||||
df.index = pd.to_datetime(df.index)
|
||||
return (df, fund_name, fund_symbol, matching_country)
|
||||
|
||||
except RuntimeError as e:
|
||||
console.print("[red]Error connecting to the data source.[/red]\n")
|
||||
logger.exception(str(e))
|
||||
return pd.DataFrame(), fund_name, fund_symbol, search_country
|
||||
except RequestException as e:
|
||||
console.print("[red]Error connecting to the data source.[/red]\n")
|
||||
logger.exception(str(e))
|
||||
return pd.DataFrame(), fund_name, fund_symbol, search_country
|
||||
except ConnectionError as e:
|
||||
console.print("[red]Error connecting to the data source.[/red]\n")
|
||||
logger.exception(str(e))
|
||||
return pd.DataFrame(), fund_name, fund_symbol, search_country
|
||||
@@ -1,184 +0,0 @@
|
||||
"""Investpy View"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import List, Optional
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from openbb_terminal.config_plot import PLOT_DPI
|
||||
from openbb_terminal.config_terminal import theme
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.helper_funcs import export_data, plot_autoscale, print_rich_table
|
||||
from openbb_terminal.mutual_funds import investpy_model
|
||||
from openbb_terminal.rich_config import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def display_search(
|
||||
by: str = "name",
|
||||
value: str = "",
|
||||
country: str = "united states",
|
||||
limit: int = 10,
|
||||
sortby: str = "",
|
||||
ascend: bool = False,
|
||||
):
|
||||
"""Display results of searching for Mutual Funds
|
||||
|
||||
Parameters
|
||||
----------
|
||||
by : str
|
||||
Field to match on. Can be name, issuer, isin or symbol
|
||||
value : str
|
||||
String that will be searched for
|
||||
country: str
|
||||
Country to filter on
|
||||
limit: int
|
||||
Number to show
|
||||
sortby: str
|
||||
Column to sort by
|
||||
ascend: bool
|
||||
Flag to sort in ascending order
|
||||
"""
|
||||
searches = investpy_model.search_funds(by, value)
|
||||
if searches.empty:
|
||||
console.print("No matches found.\n")
|
||||
return
|
||||
if country:
|
||||
searches = searches[searches.country == country]
|
||||
if searches.empty:
|
||||
console.print(f"No matches found in {country}.\n")
|
||||
return
|
||||
searches = searches.drop(columns=["country", "underlying"])
|
||||
|
||||
if sortby:
|
||||
searches = searches.sort_values(by=sortby, ascending=ascend)
|
||||
|
||||
print_rich_table(
|
||||
searches.head(limit),
|
||||
show_index=False,
|
||||
title=f"[bold]Mutual Funds with {by} matching {value}[/bold]",
|
||||
)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def display_overview(
|
||||
country: str = "united states",
|
||||
limit: int = 10,
|
||||
export: str = "",
|
||||
sheet_name: str = None,
|
||||
):
|
||||
"""Displays an overview of the main funds from a country.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
country: str
|
||||
Country to get overview for
|
||||
limit: int
|
||||
Number to show
|
||||
export : str
|
||||
Format to export data
|
||||
"""
|
||||
overview = investpy_model.get_overview(country=country, limit=limit)
|
||||
if overview.empty:
|
||||
return
|
||||
overview["Assets (1B)"] = overview.total_assets / 1_000_000_000
|
||||
overview = overview.drop(columns=["country", "total_assets"])
|
||||
print_rich_table(
|
||||
overview, title=f"[bold]Fund overview for {country.title()}[/bold]"
|
||||
)
|
||||
export_data(
|
||||
export,
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
f"overview_{country.replace(' ','_')}",
|
||||
overview,
|
||||
sheet_name,
|
||||
)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def display_fund_info(name: str, country: str = "united states"):
|
||||
"""Display fund information. Finds name from symbol first if name is false
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: str
|
||||
Fund name to get info for
|
||||
country : str
|
||||
Country of fund
|
||||
"""
|
||||
info = (
|
||||
investpy_model.get_fund_info(name, country)
|
||||
.reset_index(drop=False)
|
||||
.applymap(lambda x: np.nan if not x else x)
|
||||
.dropna()
|
||||
)
|
||||
if info.empty:
|
||||
return
|
||||
|
||||
# redact inception date if it appears castable to a float
|
||||
try:
|
||||
float(info[0].loc[info["index"] == "Inception Date"].values[0])
|
||||
info.loc[info["index"] == "Inception Date", 0] = "-"
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
print_rich_table(
|
||||
info,
|
||||
title=f"[bold]{name.title()} Information[/bold]",
|
||||
show_index=False,
|
||||
headers=["Info", "Value"],
|
||||
)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def display_historical(
|
||||
data: pd.DataFrame,
|
||||
name: str = "",
|
||||
export: str = "",
|
||||
sheet_name: str = None,
|
||||
external_axes: Optional[List[plt.Axes]] = None,
|
||||
):
|
||||
"""Display historical fund price
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data: pd.DataFrame
|
||||
Dataframe containing historical data
|
||||
name: str
|
||||
Fund symbol or name
|
||||
sheet_name: str
|
||||
Optionally specify the name of the sheet the data is exported to.
|
||||
export: str
|
||||
Format to export data
|
||||
external_axes:Optional[List[plt.Axes]]:
|
||||
External axes to plot on
|
||||
"""
|
||||
console.print()
|
||||
if external_axes is None:
|
||||
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
else:
|
||||
ax = external_axes[0]
|
||||
if data.empty:
|
||||
return
|
||||
ax.plot(data.index, data.Close)
|
||||
ax.set_xlim([data.index[0], data.index[-1]])
|
||||
ax.set_xlabel("Date")
|
||||
ax.set_ylabel("Close Price")
|
||||
ax.set_title(f"{name.title()} Price History")
|
||||
theme.style_primary_axis(ax)
|
||||
if external_axes is None:
|
||||
theme.visualize_output()
|
||||
|
||||
export_data(
|
||||
export,
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"historical",
|
||||
data,
|
||||
sheet_name,
|
||||
)
|
||||
@@ -45,8 +45,9 @@ from openbb_terminal.helper_funcs import (
|
||||
)
|
||||
from openbb_terminal.menu import session
|
||||
from openbb_terminal.rich_config import console, get_ordered_list_sources
|
||||
from openbb_terminal.session.user import User
|
||||
from openbb_terminal.stocks import stocks_helper
|
||||
from openbb_terminal.terminal_helper import open_openbb_documentation
|
||||
from openbb_terminal.terminal_helper import is_auth_enabled, open_openbb_documentation
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -96,6 +97,9 @@ class BaseController(metaclass=ABCMeta):
|
||||
"screenshot",
|
||||
]
|
||||
|
||||
if is_auth_enabled():
|
||||
CHOICES_COMMON += ["whoami"]
|
||||
|
||||
CHOICES_COMMANDS: List[str] = []
|
||||
CHOICES_MENUS: List[str] = []
|
||||
SUPPORT_CHOICES: dict = {}
|
||||
@@ -220,6 +224,15 @@ class BaseController(metaclass=ABCMeta):
|
||||
old_class = controllers[class_ins.PATH]
|
||||
old_class.queue = self.queue
|
||||
return old_class.menu()
|
||||
# Add another case so options data is saved
|
||||
if (
|
||||
class_ins.PATH == "/stocks/options/"
|
||||
and obbff.REMEMBER_CONTEXTS
|
||||
and "/stocks/options/" in controllers
|
||||
):
|
||||
old_class = controllers[class_ins.PATH]
|
||||
old_class.queue = self.queue
|
||||
return old_class.menu()
|
||||
return class_ins(*args, **kwargs).menu()
|
||||
|
||||
def save_class(self) -> None:
|
||||
@@ -275,13 +288,14 @@ class BaseController(metaclass=ABCMeta):
|
||||
|
||||
def log_queue(self) -> None:
|
||||
"""Log command queue."""
|
||||
joined_queue = self.COMMAND_SEPARATOR.join(self.queue)
|
||||
if self.queue and not self.contains_keys(joined_queue):
|
||||
logger.info(
|
||||
"QUEUE: {'path': '%s', 'queue': '%s'}",
|
||||
self.PATH,
|
||||
joined_queue,
|
||||
)
|
||||
if self.queue:
|
||||
joined_queue = self.COMMAND_SEPARATOR.join(self.queue)
|
||||
if not self.contains_keys(joined_queue):
|
||||
logger.info(
|
||||
"QUEUE: {'path': '%s', 'queue': '%s'}",
|
||||
self.PATH,
|
||||
joined_queue,
|
||||
)
|
||||
|
||||
def log_cmd_and_queue(
|
||||
self, known_cmd: str, other_args_str: str, the_input: str
|
||||
@@ -371,6 +385,9 @@ class BaseController(metaclass=ABCMeta):
|
||||
|
||||
self.log_queue()
|
||||
|
||||
if not self.queue or (self.queue and self.queue[0] not in ("quit", "help")):
|
||||
console.print()
|
||||
|
||||
return self.queue
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -661,6 +678,20 @@ class BaseController(metaclass=ABCMeta):
|
||||
if ns_parser:
|
||||
screenshot()
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_whoami(self, other_args: List[str]) -> None:
|
||||
"""Process whoami command."""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="whoami",
|
||||
description="Show current user",
|
||||
)
|
||||
ns_parser = self.parse_simple_args(parser, other_args)
|
||||
|
||||
if ns_parser:
|
||||
User.whoami()
|
||||
|
||||
@staticmethod
|
||||
def parse_simple_args(parser: argparse.ArgumentParser, other_args: List[str]):
|
||||
"""Parse list of arguments into the supplied parser.
|
||||
@@ -912,10 +943,9 @@ class BaseController(metaclass=ABCMeta):
|
||||
|
||||
# Process the input command
|
||||
self.queue = self.switch(an_input)
|
||||
if not self.queue or (
|
||||
self.queue and self.queue[0] not in ("quit", "help")
|
||||
):
|
||||
console.print()
|
||||
|
||||
if an_input == "logout":
|
||||
return ["logout"]
|
||||
|
||||
except SystemExit:
|
||||
if not self.contains_keys(an_input):
|
||||
@@ -1048,15 +1078,6 @@ class StockBaseController(BaseController, metaclass=ABCMeta):
|
||||
help="Load weekly data",
|
||||
dest="weekly",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--iexrange",
|
||||
dest="iexrange",
|
||||
help="Range for using the iexcloud api. Longer range requires more tokens in account",
|
||||
choices=["ytd", "1y", "2y", "5y", "6m"],
|
||||
type=str,
|
||||
default="ytd",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--exchange",
|
||||
dest="exchange",
|
||||
@@ -1144,9 +1165,7 @@ class StockBaseController(BaseController, metaclass=ABCMeta):
|
||||
self.ticker = ns_parser.ticker.upper()
|
||||
self.suffix = ""
|
||||
|
||||
if ns_parser.source == "IEXCloud":
|
||||
self.start = self.stock.index[0].to_pydatetime()
|
||||
elif ns_parser.source == "EODHD":
|
||||
if ns_parser.source == "EODHD":
|
||||
self.start = self.stock.index[0].to_pydatetime()
|
||||
elif ns_parser.source == "eodhd":
|
||||
self.start = self.stock.index[0].to_pydatetime()
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import logging
|
||||
from typing import Dict, Tuple
|
||||
from typing import Tuple
|
||||
|
||||
import pandas as pd
|
||||
import yfinance as yf
|
||||
from tqdm import tqdm
|
||||
|
||||
from openbb_terminal.decorators import log_start_end
|
||||
from openbb_terminal.etf import fmp_model, stockanalysis_model
|
||||
from openbb_terminal.helper_funcs import request
|
||||
from openbb_terminal.rich_config import console
|
||||
|
||||
@@ -17,7 +18,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_allocation(
|
||||
category: str, benchmark_info: Dict, portfolio_trades: pd.DataFrame
|
||||
category: str, benchmark_ticker: str, portfolio_trades: pd.DataFrame
|
||||
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
||||
"""Get category allocation for benchmark and portfolio
|
||||
|
||||
@@ -25,8 +26,8 @@ def get_allocation(
|
||||
----------
|
||||
category: str
|
||||
Chosen category: Asset, Sector, Country or Region
|
||||
benchmark_info: Dict
|
||||
Dictionary containing Yahoo Finance information
|
||||
benchmark_ticker: str
|
||||
The ticker of the benchmark
|
||||
portfolio_trades: pd.DataFrame
|
||||
Object containing trades made within the portfolio
|
||||
|
||||
@@ -39,13 +40,13 @@ def get_allocation(
|
||||
"""
|
||||
|
||||
if category == "Asset":
|
||||
return get_assets_allocation(benchmark_info, portfolio_trades)
|
||||
return get_assets_allocation(benchmark_ticker, portfolio_trades)
|
||||
if category == "Sector":
|
||||
return get_sectors_allocation(benchmark_info, portfolio_trades)
|
||||
return get_sectors_allocation(benchmark_ticker, portfolio_trades)
|
||||
if category == "Country":
|
||||
return get_countries_allocation(benchmark_info, portfolio_trades)
|
||||
return get_countries_allocation(benchmark_ticker, portfolio_trades)
|
||||
if category == "Region":
|
||||
return get_regions_allocation(benchmark_info, portfolio_trades)
|
||||
return get_regions_allocation(benchmark_ticker, portfolio_trades)
|
||||
console.print(
|
||||
"Category not available. Choose from: Asset, Sector, Country or Region."
|
||||
)
|
||||
@@ -54,14 +55,14 @@ def get_allocation(
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_assets_allocation(
|
||||
benchmark_info: Dict, portfolio_trades: pd.DataFrame
|
||||
benchmark_ticker: str, portfolio_trades: pd.DataFrame
|
||||
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
||||
"""Get assets allocation for benchmark and portfolio [Source: Yahoo Finance]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
benchmark_info: Dict
|
||||
Dictionary containing Yahoo Finance information
|
||||
benchmark_ticker: str
|
||||
The ticker of the benchmark
|
||||
portfolio_trades: pd.DataFrame
|
||||
Object containing trades made within the portfolio
|
||||
|
||||
@@ -72,16 +73,20 @@ def get_assets_allocation(
|
||||
pd.DataFrame
|
||||
DataFrame with the portfolio's asset allocations
|
||||
"""
|
||||
if "holdings" in benchmark_info:
|
||||
benchmark_assets_allocation = pd.DataFrame(benchmark_info["holdings"])
|
||||
benchmark_assets_allocation.rename(
|
||||
columns={"symbol": "Symbol", "holdingPercent": "Benchmark"}, inplace=True
|
||||
)
|
||||
benchmark_assets_allocation.drop(columns=["holdingName"], inplace=True)
|
||||
else:
|
||||
benchmark_assets_allocation = pd.DataFrame(
|
||||
columns=["Symbol", "Benchmark", "Sector", "Country"]
|
||||
)
|
||||
|
||||
benchmark_assets_allocation = stockanalysis_model.get_etf_holdings(
|
||||
benchmark_ticker
|
||||
).reset_index()
|
||||
benchmark_assets_allocation.rename(
|
||||
columns={"symbol": "Symbol", "% Of Etf": "Benchmark"}, inplace=True
|
||||
)
|
||||
benchmark_assets_allocation.drop(columns=["Name", "Shares"], inplace=True)
|
||||
|
||||
benchmark_values_formatted = []
|
||||
for benchmark_value in benchmark_assets_allocation["Benchmark"].values:
|
||||
benchmark_values_formatted.append(float(benchmark_value.strip("%")) / 100)
|
||||
|
||||
benchmark_assets_allocation["Benchmark"] = benchmark_values_formatted
|
||||
|
||||
portfolio_assets_allocation = (
|
||||
portfolio_trades[portfolio_trades["Type"] != "CASH"]
|
||||
@@ -100,14 +105,14 @@ def get_assets_allocation(
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_sectors_allocation(
|
||||
benchmark_info: Dict, portfolio_trades: pd.DataFrame
|
||||
benchmark_ticker: str, portfolio_trades: pd.DataFrame
|
||||
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
||||
"""Get sector allocation for benchmark and portfolio [Source: Yahoo Finance]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
benchmark_info: Dict
|
||||
Dictionary containing Yahoo Finance information
|
||||
benchmark_ticker: str
|
||||
The ticker of the benchmark
|
||||
portfolio_trades: pd.DataFrame
|
||||
Object containing trades made within the portfolio
|
||||
|
||||
@@ -119,32 +124,20 @@ def get_sectors_allocation(
|
||||
DataFrame with portfolio allocations
|
||||
"""
|
||||
|
||||
if "sectorWeightings" in benchmark_info:
|
||||
benchmark_sectors_allocation = (
|
||||
pd.DataFrame.from_dict(
|
||||
data={
|
||||
sector_name: allocation
|
||||
for sector in benchmark_info["sectorWeightings"]
|
||||
for sector_name, allocation in sector.items()
|
||||
},
|
||||
orient="index",
|
||||
)
|
||||
.squeeze()
|
||||
.sort_values(ascending=False)
|
||||
sectors = fmp_model.get_etf_sector_weightings(benchmark_ticker)
|
||||
|
||||
sector_weights_formatted = {}
|
||||
for sector_weight in sectors:
|
||||
sector_weights_formatted[sector_weight["sector"]] = (
|
||||
float(sector_weight["weightPercentage"].strip("%")) / 100
|
||||
)
|
||||
|
||||
# Prettify sector allocations of benchmark to align with Portfolio Excel
|
||||
prettified = [
|
||||
sector.replace("_", " ").title()
|
||||
for sector in benchmark_sectors_allocation.index
|
||||
]
|
||||
|
||||
benchmark_sectors_allocation.index = prettified
|
||||
benchmark_sectors_allocation = pd.DataFrame(benchmark_sectors_allocation)
|
||||
benchmark_sectors_allocation.reset_index(inplace=True)
|
||||
benchmark_sectors_allocation.columns = ["Sector", "Benchmark"]
|
||||
else:
|
||||
benchmark_sectors_allocation = pd.DataFrame(columns=["Sector", "Benchmark"])
|
||||
benchmark_sectors_allocation = pd.DataFrame(
|
||||
[sector_weights_formatted]
|
||||
).T.reset_index()
|
||||
benchmark_sectors_allocation.rename(
|
||||
columns={"index": "Sector", 0: "Benchmark"}, inplace=True
|
||||
)
|
||||
|
||||
# Define portfolio sector allocation
|
||||
# Aggregate sector value for stocks and crypto
|
||||
@@ -237,14 +230,14 @@ def get_sectors_allocation(
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_countries_allocation(
|
||||
benchmark_info: Dict, portfolio_trades: pd.DataFrame
|
||||
benchmark_ticker: str, portfolio_trades: pd.DataFrame
|
||||
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
||||
"""Get countries allocation for benchmark and portfolio [Source: Yahoo Finance]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
benchmark_info: Dict
|
||||
Dictionary containing Yahoo Finance information
|
||||
benchmark_ticker: str
|
||||
The ticker of the benchmark
|
||||
portfolio_trades: pd.DataFrame
|
||||
Object containing trades made within the portfolio
|
||||
|
||||
@@ -257,7 +250,7 @@ def get_countries_allocation(
|
||||
"""
|
||||
|
||||
benchmark_allocation = get_symbol_allocation(
|
||||
symbol=benchmark_info["symbol"], category="Country", col_name="Benchmark"
|
||||
symbol=benchmark_ticker, category="Country", col_name="Benchmark"
|
||||
)
|
||||
|
||||
portfolio_allocation = get_portfolio_allocation(
|
||||
@@ -269,14 +262,14 @@ def get_countries_allocation(
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_regions_allocation(
|
||||
benchmark_info: Dict, portfolio_trades: pd.DataFrame
|
||||
benchmark_ticker: str, portfolio_trades: pd.DataFrame
|
||||
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
||||
"""Get regions allocation for benchmark and portfolio [Source: Yahoo Finance]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
benchmark_info: Dict
|
||||
Dictionary containing Yahoo Finance information
|
||||
benchmark_ticker: str
|
||||
The ticker of the benchmark
|
||||
portfolio_trades: pd.DataFrame
|
||||
Object containing trades made within the portfolio
|
||||
|
||||
@@ -288,7 +281,7 @@ def get_regions_allocation(
|
||||
DataFrame with country allocations
|
||||
"""
|
||||
benchmark_allocation = get_symbol_allocation(
|
||||
symbol=benchmark_info["symbol"], category="Region", col_name="Benchmark"
|
||||
symbol=benchmark_ticker, category="Region", col_name="Benchmark"
|
||||
)
|
||||
|
||||
portfolio_allocation = get_portfolio_allocation(
|
||||
|
||||
@@ -72,7 +72,9 @@ def get_spy_sector_contributions(
|
||||
try:
|
||||
weight_data = yf.Ticker(sectors_ticker).info["sectorWeightings"]
|
||||
except Exception as _: # noqa
|
||||
console.print("[red]Could not get info for S&P 500.")
|
||||
console.print(
|
||||
"[red]This functionality is currently unstable and will be removed in the near future."
|
||||
)
|
||||
return pd.DataFrame()
|
||||
|
||||
# reformat Data
|
||||
|
||||
@@ -179,7 +179,7 @@ class PortfolioController(BaseController):
|
||||
mt.add_cmd("bench")
|
||||
mt.add_raw("\n")
|
||||
mt.add_param("_loaded", self.portfolio_name)
|
||||
mt.add_param("_riskfreerate", self.portfolio_name)
|
||||
mt.add_param("_riskfreerate", f"{self.risk_free_rate}%")
|
||||
mt.add_param("_benchmark", self.benchmark_name)
|
||||
mt.add_raw("\n")
|
||||
|
||||
@@ -197,8 +197,6 @@ class PortfolioController(BaseController):
|
||||
mt.add_cmd("rbeta", self.portfolio_name and self.benchmark_name)
|
||||
|
||||
mt.add_info("_metrics_")
|
||||
mt.add_cmd("alloc", self.portfolio_name and self.benchmark_name)
|
||||
mt.add_cmd("attrib", self.portfolio_name and self.benchmark_name)
|
||||
mt.add_cmd("summary", self.portfolio_name and self.benchmark_name)
|
||||
mt.add_cmd("alloc", self.portfolio_name and self.benchmark_name)
|
||||
mt.add_cmd("attrib", self.portfolio_name and self.benchmark_name)
|
||||
@@ -210,51 +208,7 @@ class PortfolioController(BaseController):
|
||||
mt.add_cmd("es", self.portfolio_name and self.benchmark_name)
|
||||
mt.add_cmd("os", self.portfolio_name and self.benchmark_name)
|
||||
|
||||
port = bool(self.portfolio_name)
|
||||
port_bench = bool(self.portfolio_name) and bool(self.benchmark_name)
|
||||
|
||||
help_text = f"""[menu]
|
||||
> bro brokers holdings, \t\t supports: robinhood, ally, degiro, coinbase
|
||||
> po portfolio optimization, \t optimize your portfolio weights efficiently[/menu]
|
||||
[cmds]
|
||||
load load transactions into the portfolio (use load --example for an example)
|
||||
show show existing transactions
|
||||
bench define the benchmark
|
||||
[/cmds]
|
||||
[param]Loaded transactions file:[/param] {self.portfolio_name}
|
||||
[param]Risk Free Rate: [/param] {self.risk_free_rate:.2%}
|
||||
[param]Benchmark:[/param] {self.benchmark_name}
|
||||
|
||||
[info]Graphs:[/info]{("[unvl]", "[cmds]")[port_bench]}
|
||||
holdv holdings of assets (absolute value)
|
||||
holdp portfolio holdings of assets (in percentage)
|
||||
yret yearly returns
|
||||
mret monthly returns
|
||||
dret daily returns
|
||||
distr distribution of daily returns
|
||||
maxdd maximum drawdown
|
||||
rvol rolling volatility
|
||||
rsharpe rolling sharpe
|
||||
rsort rolling sortino
|
||||
rbeta rolling beta
|
||||
{("[/unvl]", "[/cmds]")[port_bench]}
|
||||
[info]Metrics:[/info]{("[unvl]", "[cmds]")[port_bench]}
|
||||
summary all portfolio vs benchmark metrics for a certain period of choice
|
||||
alloc allocation on an asset, sector, countries or regions basis
|
||||
attrib display sector attribution of the portfolio compared to the S&P 500
|
||||
metric portfolio vs benchmark metric for all different periods
|
||||
perf performance of the portfolio versus benchmark{("[/unvl]", "[/cmds]")[port_bench]}
|
||||
|
||||
[info]Risk Metrics:[/info]{("[unvl]", "[cmds]")[port]}
|
||||
var display value at risk
|
||||
es display expected shortfall
|
||||
om display omega ratio{("[/unvl]", "[/cmds]")[port]}
|
||||
"""
|
||||
# TODO: Clean up the reports inputs
|
||||
# TODO: Edit the allocation to allow the different asset classes
|
||||
# [info]Reports:[/info]
|
||||
# ar annual report for performance of a given portfolio
|
||||
console.print(text=help_text, menu="Portfolio")
|
||||
console.print(text=mt.menu_text, menu="Portfolio - Portfolio Optimization")
|
||||
self.update_choices()
|
||||
|
||||
def custom_reset(self):
|
||||
@@ -279,13 +233,49 @@ class PortfolioController(BaseController):
|
||||
if OPTIMIZATION_TOOLKIT_ENABLED:
|
||||
if self.portfolio is None:
|
||||
tickers = []
|
||||
categories = None
|
||||
else:
|
||||
tickers = self.portfolio.tickers_list
|
||||
transactions = self.portfolio.get_transactions()
|
||||
|
||||
categories = {
|
||||
"ASSET_CLASS": {},
|
||||
"SECTOR": {},
|
||||
"INDUSTRY": {},
|
||||
"COUNTRY": {},
|
||||
"CURRENT_INVESTED_AMOUNT": {},
|
||||
"CURRENCY": {},
|
||||
}
|
||||
for _, transaction in transactions.iterrows():
|
||||
if transaction["Ticker"] not in categories["ASSET_CLASS"]:
|
||||
categories["ASSET_CLASS"][transaction["Ticker"]] = transaction[
|
||||
"Type"
|
||||
]
|
||||
categories["SECTOR"][transaction["Ticker"]] = transaction[
|
||||
"Sector"
|
||||
]
|
||||
categories["INDUSTRY"][transaction["Ticker"]] = transaction[
|
||||
"Industry"
|
||||
]
|
||||
categories["COUNTRY"][transaction["Ticker"]] = transaction[
|
||||
"Country"
|
||||
]
|
||||
categories["CURRENT_INVESTED_AMOUNT"][
|
||||
transaction["Ticker"]
|
||||
] = transactions[
|
||||
transactions["Ticker"] == transaction["Ticker"]
|
||||
][
|
||||
"Investment"
|
||||
].sum()
|
||||
categories["CURRENCY"][transaction["Ticker"]] = transaction[
|
||||
"Currency"
|
||||
]
|
||||
|
||||
self.queue = self.load_class(
|
||||
po_controller.PortfolioOptimizationController,
|
||||
tickers,
|
||||
None,
|
||||
None,
|
||||
categories,
|
||||
self.queue,
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -118,7 +118,6 @@ class PortfolioEngine:
|
||||
self.tickers_list = None
|
||||
self.tickers: Dict[Any, Any] = {}
|
||||
self.benchmark_ticker: str = ""
|
||||
self.benchmark_info: Dict[Any, Any] = {}
|
||||
self.historical_trade_data = pd.DataFrame()
|
||||
|
||||
# Portfolio
|
||||
@@ -559,19 +558,6 @@ class PortfolioEngine:
|
||||
p_bar.n += 1
|
||||
p_bar.refresh()
|
||||
|
||||
try:
|
||||
self.benchmark_info = dict(yf.Ticker(symbol).info)
|
||||
self.benchmark_info["symbol"] = symbol
|
||||
except Exception as _: # noqa
|
||||
console.print(
|
||||
f"[red]\n\nCould not get info for {symbol}."
|
||||
" This affects 'alloc' command.[/red]\n"
|
||||
)
|
||||
return False
|
||||
|
||||
p_bar.n += 1
|
||||
p_bar.refresh()
|
||||
|
||||
return True
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -931,10 +917,10 @@ class PortfolioEngine:
|
||||
Flag to force recalculate allocation if already exists
|
||||
"""
|
||||
|
||||
if not self.benchmark_info:
|
||||
return
|
||||
|
||||
if self.portfolio_trades.empty:
|
||||
if not self.benchmark_ticker or self.portfolio_trades.empty:
|
||||
console.print(
|
||||
"Please load in a portfolio holdings first with `load --file` or obtain an example with `load --example`"
|
||||
)
|
||||
return
|
||||
|
||||
if category == "Asset":
|
||||
@@ -946,7 +932,9 @@ class PortfolioEngine:
|
||||
(
|
||||
self.benchmark_assets_allocation,
|
||||
self.portfolio_assets_allocation,
|
||||
) = get_allocation(category, self.benchmark_info, self.portfolio_trades)
|
||||
) = get_allocation(
|
||||
category, self.benchmark_ticker, self.portfolio_trades
|
||||
)
|
||||
elif category == "Sector":
|
||||
if (
|
||||
self.benchmark_sectors_allocation.empty
|
||||
@@ -956,7 +944,9 @@ class PortfolioEngine:
|
||||
(
|
||||
self.benchmark_sectors_allocation,
|
||||
self.portfolio_sectors_allocation,
|
||||
) = get_allocation(category, self.benchmark_info, self.portfolio_trades)
|
||||
) = get_allocation(
|
||||
category, self.benchmark_ticker, self.portfolio_trades
|
||||
)
|
||||
elif category == "Country":
|
||||
if (
|
||||
self.benchmark_countries_allocation.empty
|
||||
@@ -966,7 +956,9 @@ class PortfolioEngine:
|
||||
(
|
||||
self.benchmark_countries_allocation,
|
||||
self.portfolio_countries_allocation,
|
||||
) = get_allocation(category, self.benchmark_info, self.portfolio_trades)
|
||||
) = get_allocation(
|
||||
category, self.benchmark_ticker, self.portfolio_trades
|
||||
)
|
||||
elif category == "Region":
|
||||
if (
|
||||
self.benchmark_regions_allocation.empty
|
||||
@@ -976,7 +968,9 @@ class PortfolioEngine:
|
||||
(
|
||||
self.benchmark_regions_allocation,
|
||||
self.portfolio_regions_allocation,
|
||||
) = get_allocation(category, self.benchmark_info, self.portfolio_trades)
|
||||
) = get_allocation(
|
||||
category, self.benchmark_ticker, self.portfolio_trades
|
||||
)
|
||||
else:
|
||||
console.print(
|
||||
"Category not available. Choose from: Asset, Sector, Country or Region"
|
||||
|
||||
@@ -52,7 +52,7 @@ def generate_portfolio(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
"""
|
||||
|
||||
transactions = PortfolioEngine.read_transactions(transactions_file_path)
|
||||
@@ -82,7 +82,7 @@ def get_transactions(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.show(p)
|
||||
"""
|
||||
|
||||
@@ -114,7 +114,7 @@ def set_benchmark(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.bench(p, symbol="SPY")
|
||||
"""
|
||||
|
||||
@@ -154,7 +154,7 @@ def get_holdings_value(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.holdv(p)
|
||||
"""
|
||||
|
||||
@@ -188,7 +188,7 @@ def get_holdings_percentage(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.holdp(p)
|
||||
"""
|
||||
|
||||
@@ -227,7 +227,7 @@ def get_yearly_returns(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.yret(p)
|
||||
"""
|
||||
|
||||
@@ -291,7 +291,7 @@ def get_monthly_returns(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.mret(p)
|
||||
"""
|
||||
|
||||
@@ -426,7 +426,7 @@ def get_daily_returns(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.dret(p)
|
||||
"""
|
||||
|
||||
@@ -501,7 +501,7 @@ def get_distribution_returns(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.distr(p)
|
||||
"""
|
||||
|
||||
@@ -545,7 +545,7 @@ def get_maximum_drawdown(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.maxdd(p)
|
||||
"""
|
||||
|
||||
@@ -581,7 +581,7 @@ def get_rolling_volatility(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.rvol(p)
|
||||
"""
|
||||
|
||||
@@ -627,7 +627,7 @@ def get_rolling_sharpe(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.rsharpe(p)
|
||||
"""
|
||||
|
||||
@@ -675,7 +675,7 @@ def get_rolling_sortino(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.rsort(p)
|
||||
"""
|
||||
|
||||
@@ -721,7 +721,7 @@ def get_rolling_beta(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.rbeta(p)
|
||||
"""
|
||||
|
||||
@@ -757,7 +757,7 @@ def get_summary(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.summary(p)
|
||||
"""
|
||||
|
||||
@@ -849,7 +849,7 @@ def get_assets_allocation(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.alloc.assets(p)
|
||||
"""
|
||||
|
||||
@@ -893,7 +893,7 @@ def get_sectors_allocation(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.alloc.sectors(p)
|
||||
"""
|
||||
|
||||
@@ -937,7 +937,7 @@ def get_countries_allocation(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.alloc.countries(p)
|
||||
"""
|
||||
|
||||
@@ -981,7 +981,7 @@ def get_regions_allocation(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.alloc.regions(p)
|
||||
"""
|
||||
|
||||
@@ -1015,7 +1015,7 @@ def get_r2_score(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.rsquare(p)
|
||||
"""
|
||||
|
||||
@@ -1053,7 +1053,7 @@ def get_skewness(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.skew(p)
|
||||
"""
|
||||
|
||||
@@ -1100,7 +1100,7 @@ def get_kurtosis(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.kurtosis(p)
|
||||
"""
|
||||
|
||||
@@ -1179,7 +1179,7 @@ def get_volatility(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.volatility(p)
|
||||
"""
|
||||
|
||||
@@ -1232,7 +1232,7 @@ def get_sharpe_ratio(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.sharpe(p)
|
||||
"""
|
||||
|
||||
@@ -1285,7 +1285,7 @@ def get_sortino_ratio(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.sortino(p)
|
||||
"""
|
||||
|
||||
@@ -1334,7 +1334,7 @@ def get_maximum_drawdown_ratio(portfolio_engine: PortfolioEngine) -> pd.DataFram
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.maxdrawdown(p)
|
||||
"""
|
||||
|
||||
@@ -1381,7 +1381,7 @@ def get_gaintopain_ratio(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.gaintopain(p)
|
||||
"""
|
||||
|
||||
@@ -1418,7 +1418,7 @@ def get_tracking_error(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.trackerr(p)
|
||||
"""
|
||||
|
||||
@@ -1447,7 +1447,7 @@ def get_information_ratio(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.information(p)
|
||||
"""
|
||||
|
||||
@@ -1487,7 +1487,7 @@ def get_tail_ratio(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.tail(p)
|
||||
"""
|
||||
|
||||
@@ -1516,7 +1516,7 @@ def get_common_sense_ratio(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.commonsense(p)
|
||||
"""
|
||||
|
||||
@@ -1556,7 +1556,7 @@ def get_jensens_alpha(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.jensens(p)
|
||||
"""
|
||||
|
||||
@@ -1596,7 +1596,7 @@ def get_calmar_ratio(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.calmar(p)
|
||||
"""
|
||||
|
||||
@@ -1629,7 +1629,7 @@ def get_kelly_criterion(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.kelly(p)
|
||||
"""
|
||||
|
||||
@@ -1652,7 +1652,7 @@ def get_payoff_ratio(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.payoff(p)
|
||||
During some time periods there were no losing trades. Thus some values could not be calculated.
|
||||
"""
|
||||
@@ -1680,7 +1680,7 @@ def get_profit_factor(portfolio_engine: PortfolioEngine) -> pd.DataFrame:
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.metric.profitfactor(p)
|
||||
During some time periods there were no losing trades. Thus some values could not be calculated.
|
||||
"""
|
||||
@@ -1713,7 +1713,7 @@ def get_performance_vs_benchmark(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.perf(p)
|
||||
"""
|
||||
|
||||
@@ -1833,7 +1833,7 @@ def get_var(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.var(p)
|
||||
"""
|
||||
|
||||
@@ -1876,7 +1876,7 @@ def get_es(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.es(p)
|
||||
"""
|
||||
|
||||
@@ -1915,7 +1915,7 @@ def get_omega(
|
||||
Examples
|
||||
--------
|
||||
>>> from openbb_terminal.sdk import openbb
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio_examples/holdings/example.csv")
|
||||
>>> p = openbb.portfolio.load("openbb_terminal/miscellaneous/portfolio/holdings_example.xlsx")
|
||||
>>> output = openbb.portfolio.om(p)
|
||||
"""
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Optimization helpers"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import argparse
|
||||
from typing import Any
|
||||
|
||||
import pandas as pd
|
||||
@@ -13,109 +12,6 @@ from openbb_terminal.portfolio.portfolio_optimization.statics import (
|
||||
)
|
||||
from openbb_terminal.rich_config import console
|
||||
|
||||
# These are all the possible yfinance properties
|
||||
valid_property_infos = [
|
||||
"previousClose",
|
||||
"regularMarketOpen",
|
||||
"twoHundredDayAverage",
|
||||
"trailingAnnualDividendYield",
|
||||
"payoutRatio",
|
||||
"volume24Hr",
|
||||
"regularMarketDayHigh",
|
||||
"navPrice",
|
||||
"averageDailyVolume10Day",
|
||||
"totalAssets",
|
||||
"regularMarketPreviousClose",
|
||||
"fiftyDayAverage",
|
||||
"trailingAnnualDividendRate",
|
||||
"open",
|
||||
"toCurrency",
|
||||
"averageVolume10days",
|
||||
"expireDate",
|
||||
"yield",
|
||||
"algorithm",
|
||||
"dividendRate",
|
||||
"exDividendDate",
|
||||
"beta",
|
||||
"circulatingSupply",
|
||||
"regularMarketDayLow",
|
||||
"priceHint",
|
||||
"currency",
|
||||
"trailingPE",
|
||||
"regularMarketVolume",
|
||||
"lastMarket",
|
||||
"maxSupply",
|
||||
"openInterest",
|
||||
"marketCap",
|
||||
"volumeAllCurrencies",
|
||||
"strikePrice",
|
||||
"averageVolume",
|
||||
"priceToSalesTrailing12Months",
|
||||
"dayLow",
|
||||
"ask",
|
||||
"ytdReturn",
|
||||
"askSize",
|
||||
"volume",
|
||||
"fiftyTwoWeekHigh",
|
||||
"forwardPE",
|
||||
"fromCurrency",
|
||||
"fiveYearAvgDividendYield",
|
||||
"fiftyTwoWeekLow",
|
||||
"bid",
|
||||
"dividendYield",
|
||||
"bidSize",
|
||||
"dayHigh",
|
||||
"annualHoldingsTurnover",
|
||||
"enterpriseToRevenue",
|
||||
"beta3Year",
|
||||
"profitMargins",
|
||||
"enterpriseToEbitda",
|
||||
"52WeekChange",
|
||||
"morningStarRiskRating",
|
||||
"forwardEps",
|
||||
"revenueQuarterlyGrowth",
|
||||
"sharesOutstanding",
|
||||
"fundInceptionDate",
|
||||
"annualReportExpenseRatio",
|
||||
"bookValue",
|
||||
"sharesShort",
|
||||
"sharesPercentSharesOut",
|
||||
"fundFamily",
|
||||
"lastFiscalYearEnd",
|
||||
"heldPercentInstitutions",
|
||||
"netIncomeToCommon",
|
||||
"trailingEps",
|
||||
"lastDividendValue",
|
||||
"SandP52WeekChange",
|
||||
"priceToBook",
|
||||
"heldPercentInsiders",
|
||||
"shortRatio",
|
||||
"sharesShortPreviousMonthDate",
|
||||
"floatShares",
|
||||
"enterpriseValue",
|
||||
"threeYearAverageReturn",
|
||||
"lastSplitFactor",
|
||||
"legalType",
|
||||
"lastDividendDate",
|
||||
"morningStarOverallRating",
|
||||
"earningsQuarterlyGrowth",
|
||||
"pegRatio",
|
||||
"lastCapGain",
|
||||
"shortPercentOfFloat",
|
||||
"sharesShortPriorMonth",
|
||||
"impliedSharesOutstanding",
|
||||
"fiveYearAverageReturn",
|
||||
"regularMarketPrice",
|
||||
]
|
||||
|
||||
|
||||
def check_valid_property_type(check_property: str) -> str:
|
||||
"""Check that the property selected is valid"""
|
||||
if check_property in valid_property_infos:
|
||||
return check_property
|
||||
|
||||
raise argparse.ArgumentTypeError(f"{check_property} is not a valid info")
|
||||
|
||||
|
||||
def dict_to_df(d: dict) -> pd.DataFrame:
|
||||
"""Convert a dictionary to a DataFrame
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user