Merge branch 'develop' into feature/fixed_income

This commit is contained in:
northern-64bit
2023-02-14 19:27:34 +01:00
committed by GitHub
519 changed files with 428768 additions and 617342 deletions

View File

@@ -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
View 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 }}`.

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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
View File

@@ -61,3 +61,6 @@ darts_logs/
# User data
custom_imports/*.csv
custom_imports/*/*.csv
# lightning logs
lightning_logs/

View File

@@ -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
View File

@@ -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.

View File

@@ -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

View File

@@ -4,4 +4,5 @@ channels:
dependencies:
- python=3.8
- pip
- poetry=1.1.13
- pip:
- poetry==1.3.2

View File

@@ -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

View File

@@ -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
View File

@@ -0,0 +1,8 @@
channels:
- conda-forge
- defaults
dependencies:
- python=3.9
- poetry==1.3.2
- setuptools=64.0.2
- pip

View File

@@ -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
View File

@@ -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"

View File

@@ -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",

View File

@@ -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')
```

View 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()

View 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

View 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="#",
)

View File

@@ -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)

View File

@@ -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")

View File

@@ -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"

View File

@@ -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"

View File

@@ -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(

View File

@@ -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):

View 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

View 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()

View File

@@ -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

View File

@@ -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()

View 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))

View File

@@ -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(

View File

@@ -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
),
)

View File

@@ -230,7 +230,7 @@ def show_treasuries(
export,
os.path.dirname(os.path.abspath(__file__)),
"treasuries_data",
treasury_data,
treasury_data / 100,
sheet_name,
)

View File

@@ -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")

View File

@@ -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

View File

@@ -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,
)

View File

@@ -638,6 +638,7 @@ def get_index(
interval=interval,
progress=False,
show_errors=False,
ignore_tz=True,
)
if column not in index_data.columns:

View File

@@ -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"""

View File

@@ -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",
)

View 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

View File

@@ -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")

View File

@@ -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 ""

View File

@@ -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
)

View File

@@ -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(

View File

@@ -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,
)

View File

@@ -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:

View File

@@ -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,

View File

@@ -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:

View File

@@ -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)

View File

@@ -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):

View File

@@ -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,
)

View File

@@ -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:

View File

@@ -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,
)

View File

@@ -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:

View File

@@ -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,
)

View File

@@ -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:

View File

@@ -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,
)

View File

@@ -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:

View File

@@ -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,
)

View File

@@ -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:

View File

@@ -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,
)

View File

@@ -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(

View File

@@ -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,
)

View File

@@ -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:

View File

@@ -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,

View File

@@ -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:

View File

@@ -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,
)

View File

@@ -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(

View File

@@ -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",

View File

@@ -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"

View File

@@ -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

View File

@@ -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
)

View File

@@ -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)

View File

@@ -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:

View File

@@ -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": {

View File

@@ -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

View File

@@ -0,0 +1,7 @@
# GUEST
whoami
account
# AUTHENTICATED USER
# Not implemented

View File

@@ -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

View File

@@ -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

View File

@@ -9,6 +9,7 @@ disc
help
h
?
filings
pipo
fipo
gainers

View File

@@ -27,7 +27,6 @@ enterprise
shrs
shrs major
shrs mutualfund
sust
divs
divs -p
splits

View File

@@ -1,9 +0,0 @@
script: test_stocks_filings.openbb
- - - - - - - - - -
stocks
filings
filings --limit 200
filings --today
filings --pages 30 --limit 30000
exit

View File

@@ -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 trail model view
237 econometrics.re openbb_terminal.econometrics.regression_model.get_re
238 econometrics.get_regression_data openbb_terminal.econometrics.regression_model.get_regression_data
239 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
240 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
241 economy.macro openbb_terminal.economy.econdb_model.get_aggregated_macro_data openbb_terminal.economy.econdb_view.show_macro_data
242 economy.macro_parameters openbb_terminal.economy.econdb_model.get_macro_parameters
243 economy.macro_countries openbb_terminal.economy.econdb_model.get_macro_countries
272 etf.symbols openbb_terminal.etf.stockanalysis_model.get_all_names_symbols
273 etf.overview openbb_terminal.etf.stockanalysis_model.get_etf_overview
274 etf.etf_by_name openbb_terminal.etf.stockanalysis_model.get_etfs_by_name
275 etf.weights openbb_terminal.etf.yfinance_model.get_etf_sector_weightings openbb_terminal.etf.fmp_model.get_etf_sector_weightings
etf.summary openbb_terminal.etf.yfinance_model.get_etf_summary_description
276 etf.compare openbb_terminal.etf.stockanalysis_model.compare_etfs
277 forex.get_currency_list openbb_terminal.forex.av_model.get_currency_list
278 forex.quote openbb_terminal.forex.sdk_helpers.quote
383 stocks.disc.losers openbb_terminal.stocks.discovery.yahoofinance_model.get_losers
384 stocks.disc.ugs openbb_terminal.stocks.discovery.yahoofinance_model.get_ugs
385 stocks.disc.ulc openbb_terminal.stocks.discovery.yahoofinance_model.get_ulc
386 stocks.fa.arktrades stocks.screener.arktrades openbb_terminal.stocks.fundamental_analysis.ark_model.get_ark_trades_by_ticker openbb_terminal.stocks.screener.ark_model.get_ark_trades_by_ticker
387 stocks.fa.est openbb_terminal.stocks.fundamental_analysis.business_insider_model.get_estimates
388 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
389 stocks.fa.customer openbb_terminal.stocks.fundamental_analysis.csimarket_model.get_customers
413 stocks.fa.metrics openbb_terminal.stocks.fundamental_analysis.fmp_model.get_key_metrics
414 stocks.fa.ratios openbb_terminal.stocks.fundamental_analysis.fmp_model.get_key_ratios
415 stocks.fa.profile openbb_terminal.stocks.fundamental_analysis.fmp_model.get_profile
416 stocks.quote_fmp stocks.quote openbb_terminal.stocks.fundamental_analysis.fmp_model.get_quote openbb_terminal.stocks.stocks_model.get_quote
417 stocks.fa.score openbb_terminal.stocks.fundamental_analysis.fmp_model.get_score
418 stocks.fa.data openbb_terminal.stocks.fundamental_analysis.finviz_model.get_data
419 stocks.fa.cal openbb_terminal.stocks.fundamental_analysis.yahoo_finance_model.get_calendar_earnings
422 stocks.fa.mktcap openbb_terminal.stocks.fundamental_analysis.yahoo_finance_model.get_mktcap openbb_terminal.stocks.fundamental_analysis.yahoo_finance_view.display_mktcap
423 stocks.fa.shrs openbb_terminal.stocks.fundamental_analysis.yahoo_finance_model.get_shareholders
424 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
425 stocks.fa.revfc openbb_terminal.stocks.fundamental_analysis.seeking_alpha_model.get_estimates_rev
426 stocks.fa.epsfc openbb_terminal.stocks.fundamental_analysis.seeking_alpha_model.get_estimates_eps
427 stocks.gov.qtrcontracts openbb_terminal.stocks.government.quiverquant_model.get_qtr_contracts openbb_terminal.stocks.government.quiverquant_view.display_qtr_contracts
452 stocks.ins.blos openbb_terminal.stocks.insider.sdk_helper.blos
453 stocks.ins.blcs openbb_terminal.stocks.insider.sdk_helper.blcs
454 stocks.options.pcr openbb_terminal.stocks.options.alphaquery_model.get_put_call_ratio openbb_terminal.stocks.options.alphaquery_view.display_put_call_ratio
455 stocks.options.info openbb_terminal.stocks.options.yfinance_model.get_info openbb_terminal.stocks.options.barchart_model.get_options_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
456 stocks.options.unu openbb_terminal.stocks.options.fdscanner_model.unusual_options openbb_terminal.stocks.options.fdscanner_view.display_options
457 stocks.options.screen.check_presets openbb_terminal.stocks.options.screen.syncretism_model.check_presets
458 stocks.options.grhist openbb_terminal.stocks.options.screen.syncretism_model.get_historical_greeks openbb_terminal.stocks.options.screen.syncretism_view.view_historical_greeks
459 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
460 stocks.options.hist openbb_terminal.stocks.options.options_sdk_helper.hist
461 stocks.options.chains openbb_terminal.stocks.options.options_sdk_helper.get_full_option_chain
462 stocks.options.eodchain openbb_terminal.stocks.options.intrinio_model.get_full_chain_eod
463 stocks.options.price openbb_terminal.stocks.options.options_sdk_helper.get_option_current_price
464 stocks.options.vol openbb_terminal.stocks.options.options_view.plot_vol
465 stocks.options.oi openbb_terminal.stocks.options.options_view.plot_oi
466 stocks.options.voi openbb_terminal.stocks.options.options_view.plot_voi
467 stocks.options.last_price openbb_terminal.stocks.options.tradier_model.get_last_price
468 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
469 stocks.options.generate_data openbb_terminal.stocks.options.yfinance_model.generate_data
470 stocks.options.dte openbb_terminal.stocks.options.yfinance_model.get_dte
471 stocks.options.vsurf openbb_terminal.stocks.options.yfinance_model.get_iv_surface openbb_terminal.stocks.options.yfinance_view.display_vol_surface
499 etf.load openbb_terminal.stocks.stocks_helper.load
500 stocks.process_candle openbb_terminal.stocks.stocks_helper.process_candle
501 stocks.search openbb_terminal.stocks.stocks_helper.search
stocks.quote_yf openbb_terminal.stocks.stocks_model.load_quote
502 stocks.tob openbb_terminal.stocks.cboe_model.get_top_of_book
503 stocks.candle openbb_terminal.stocks.stocks_helper.display_candle
504 crypto.load openbb_terminal.cryptocurrency.cryptocurrency_helpers.load
544 keys.tokenterminal openbb_terminal.keys_model.set_tokenterminal_key
545 keys.shroom openbb_terminal.keys_model.set_shroom_key
546 keys.stocksera openbb_terminal.keys_model.set_stocksera_key
547 keys.openbb openbb_terminal.keys_model.set_openbb_personal_access_token
548 futures.search openbb_terminal.futures.yfinance_model.get_search_futures
549 futures.historical openbb_terminal.futures.yfinance_model.get_historical_futures openbb_terminal.futures.yfinance_view.display_historical
550 futures.curve openbb_terminal.futures.yfinance_model.get_curve_futures openbb_terminal.futures.yfinance_view.display_curve
551 economy.get_groups openbb_terminal.economy.finviz_model.get_groups
552 stocks.filings stocks.disc.filings openbb_terminal.stocks.fundamental_analysis.fmp_model.get_filings openbb_terminal.stocks.fundamental_analysis.fmp_view.display_filings openbb_terminal.stocks.discovery.fmp_view.display_filings
553 stocks.fa.sec_fmp openbb_terminal.stocks.fundamental_analysis.fmp_model.get_filings openbb_terminal.stocks.fundamental_analysis.fmp_view.display_filings
554 login openbb_terminal.session.sdk_session.login
555 logout openbb_terminal.session.sdk_session.logout
556 whoami openbb_terminal.session.sdk_session.whoami

View File

@@ -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 trail model view
2 portfolio.po.blacklitterman openbb_terminal.portfolio.portfolio_optimization.po_model.get_blacklitterman
portfolio.po.dividend openbb_terminal.portfolio.portfolio_optimization.po_model.get_dividend
3 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
4 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
5 portfolio.po.herc openbb_terminal.portfolio.portfolio_optimization.po_model.get_herc
6 portfolio.po.hrp openbb_terminal.portfolio.portfolio_optimization.po_model.get_hrp
7 portfolio.po.load openbb_terminal.portfolio.portfolio_optimization.po_model.generate_portfolio
12 portfolio.po.maxsharpe openbb_terminal.portfolio.portfolio_optimization.po_model.get_maxsharpe
13 portfolio.po.maxutil openbb_terminal.portfolio.portfolio_optimization.po_model.get_maxutil
14 portfolio.po.minrisk openbb_terminal.portfolio.portfolio_optimization.po_model.get_minrisk
portfolio.po.mktcap openbb_terminal.portfolio.portfolio_optimization.po_model.get_mktcap
15 portfolio.po.nco openbb_terminal.portfolio.portfolio_optimization.po_model.get_nco
16 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
17 portfolio.po.relriskparity openbb_terminal.portfolio.portfolio_optimization.po_model.get_relriskparity
18 portfolio.po.riskparity openbb_terminal.portfolio.portfolio_optimization.po_model.get_riskparity
19 portfolio.po.show openbb_terminal.portfolio.portfolio_optimization.po_model.show

View File

@@ -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

View File

@@ -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,
)

View File

@@ -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()

View File

@@ -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(

View File

@@ -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

View File

@@ -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:

View File

@@ -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"

View File

@@ -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)
"""

View File

@@ -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