summaryrefslogtreecommitdiffstats
path: root/cli/CONTRIBUTING.md
diff options
context:
space:
mode:
Diffstat (limited to 'cli/CONTRIBUTING.md')
-rw-r--r--cli/CONTRIBUTING.md1724
1 files changed, 1724 insertions, 0 deletions
diff --git a/cli/CONTRIBUTING.md b/cli/CONTRIBUTING.md
new file mode 100644
index 00000000000..49314e4a8e4
--- /dev/null
+++ b/cli/CONTRIBUTING.md
@@ -0,0 +1,1724 @@
+# CONTRIBUTING
+
+First off, thanks for taking the time to contribute (or at least read the Contributing Guidelines)! ๐Ÿš€
+
+The following is a set of guidelines for contributing to OpenBB Terminal. These are mostly guidelines, not rules.
+Use your best judgment, and feel free to propose changes to this document in a pull request.
+
+- [CONTRIBUTING](#contributing)
+- [BASIC](#basic)
+ - [Adding a new command](#adding-a-new-command)
+ - [Select Feature](#select-feature)
+ - [Model](#model)
+ - [Data sources](#data-sources)
+ - [View](#view)
+ - [Controller](#controller)
+ - [Add SDK endpoint (V3)](#add-sdk-endpoint-v3)
+ - [Add OpenBB Platform endpoint (V4)](#add-openbb-platform-endpoint-v4)
+ - [Add Unit Tests](#add-unit-tests)
+ - [Open a Pull Request](#open-a-pull-request)
+ - [Review Process](#review-process)
+ - [Understand Code Structure](#understand-code-structure)
+ - [Backend](#backend)
+ - [Frontend](#frontend)
+ - [Follow Coding Guidelines](#follow-coding-guidelines)
+ - [General Code Requirements](#general-code-requirements)
+ - [File Specific Requirements](#file-specific-requirements)
+ - [Coding Style](#coding-style)
+ - [OpenBB Style Guide](#openbb-style-guide)
+ - [Flags](#flags)
+ - [Output format](#output-format)
+ - [Time-related](#time-related)
+ - [Data selection and manipulation](#data-selection-and-manipulation)
+ - [Financial instrument characteristics](#financial-instrument-characteristics)
+ - [Naming Convention](#naming-convention)
+ - [Docstrings](#docstrings)
+ - [Linters](#linters)
+ - [Command names](#command-names)
+ - [UI and UX](#ui-and-ux)
+ - [External API Keys](#external-api-keys)
+ - [Creating API key](#creating-api-key)
+ - [Setting and checking API key](#setting-and-checking-api-key)
+- [ADVANCED](#advanced)
+ - [Important functions and classes](#important-functions-and-classes)
+ - [Base controller class](#base-controller-class)
+ - [Default Data Sources](#default-data-sources)
+ - [Export Data](#export-data)
+ - [Queue and pipeline](#queue-and-pipeline)
+ - [Auto Completer](#auto-completer)
+ - [Logging](#logging)
+ - [Internationalization](#internationalization)
+ - [Settings](#settings)
+ - [Write Code and Commit](#write-code-and-commit)
+ - [Pre Commit Hooks](#pre-commit-hooks)
+ - [Coding](#coding)
+ - [Git Process](#git-process)
+ - [Branch Naming Conventions](#branch-naming-conventions)
+ - [Installers](#installers)
+
+# BASIC
+
+## Adding a new command
+
+Before implementing a new command we highly recommend that you go through [Understand Code Structure](#understand-code-structure) and [Follow Coding Guidelines](#follow-coding-guidelines). This will allow you to get your PR merged faster and maintain consistency in our code base.
+
+In the next sections we describe the process to add a new command.
+We will be adding a function to get price targets from the Financial Modeling Prep API. Note that there already exists a function to get price targets from the Business Insider website, `stocks/fa/pt`, so we will be adding a new function to get price targets from the Financial Modeling Prep API, and go through adding sources.
+
+### Select Feature
+
+- Pick a feature you want to implement or a bug you want to fix from [our issues](https://github.com/OpenBB-finance/OpenBBTerminal/issues).
+- Feel free to discuss what you'll be working on either directly on [the issue](https://github.com/OpenBB-finance/OpenBBTerminal/issues) or on [our Discord](https://openbb.co/discord).
+ - This ensures someone from the team can help you and there isn't duplicated work.
+
+Before writing any code, it is good to understand what the data will look like. In this case, we will be getting the price targets from the Financial Modeling Prep API, and the data will look like this:
+
+```json
+[
+ {
+ "symbol": "AAPL",
+ "publishedDate": "2023-02-03T16:19:00.000Z",
+ "newsURL": "https://pulse2.com/apple-stock-receives-a-195-price-target-aapl/",
+ "newsTitle": "Apple Stock Receives A $195 Price Target (AAPL)",
+ "analystName": "Cowen Cowen",
+ "priceTarget": 195,
+ "adjPriceTarget": 195,
+ "priceWhenPosted": 154.5,
+ "newsPublisher": "Pulse 2.0",
+ "newsBaseURL": "pulse2.com",
+ "analystCompany": "Cowen & Co."
+ }
+```
+
+### Model
+
+1. Create a file with the source of data as the name followed by `_model` if it doesn't exist. In this case, the file `openbb_terminal/stocks/fundamental_analysis/fmp_model.py` already exists, so we will add the function to that file.
+2. Add the documentation header
+3. Do the necessary imports to get the data
+4. Define a function starting with `get_`
+5. In this function:
+ 1. Use type hinting
+ 2. Write a descriptive description where at the end the source is specified.
+ 3. Utilize an official API, get and return the data.
+
+```python
+""" Financial Modeling Prep Model """
+__docformat__ = "numpy"
+
+import logging
+
+import pandas as pd
+
+from src.current_user import get_current_user
+from openbb_terminal.decorators import check_api_key, log_start_end
+from openbb_terminal.helpers import request
+
+logger = logging.getLogger(__name__)
+
+
+@check_api_key(["API_KEY_FINANCIALMODELINGPREP"])
+def get_price_targets(cls, symbol: str) -> pd.DataFrame:
+ """Get price targets for a company [Source: Financial Modeling Prep]
+
+ Parameters
+ ----------
+ symbol : str
+ Symbol to get data for
+
+ Returns
+ -------
+ pd.DataFrame
+ DataFrame of price targets
+ """
+ current_user = get_current_user()
+
+ url = f"https://financialmodelingprep.com/api/v4/price-target?symbol={symbol}&apikey={current_user.credentials.API_KEY_FINANCIALMODELINGPREP}"
+ response = request(url)
+
+ # Check if the response is valid
+ if response.status_code != 200 or "Error Message" in response.json():
+ message = f"Error, Status Code: {response.status_code}."
+ message = (
+ message
+ if "Error Message" not in response.json()
+ else message + "\n" + response.json()["Error Message"] + ".\n"
+ )
+ console.print(message)
+ return pd.DataFrame()
+
+ return pd.DataFrame(response.json())
+```
+
+In this function:
+
+- We import the current user object and, consequently, preferences using the `get_current_user` function. API keys are stored in `current_user.credentials`
+- We use the `@log_start_end` decorator to add the function to our logs for debugging purposes.
+- We add the `@check_api_key` decorator to confirm the API key is valid.
+- We have type hinting and a docstring describing the function.
+- We use the openbb_terminal helper function `request`, which is an abstracted version of the requests library, which allows us to add user agents, timeouts, caches, etc. to any HTTP request in the terminal.
+- We check for different error messages. This will depend on the API provider and usually requires some trial and error. With the FMP API, if there is an invalid symbol, we get a response code of 200, but the json response has an error message field. Same with an invalid API key.
+- When an error is caught, we still return an empty dataframe.
+- We return the json response as a pandas dataframe. Most functions in the terminal should return a dataframe, but if not, make sure that the return type is specified.
+
+Note:
+
+1. If the function is applicable to many asset classes, it is possible that this file needs to be created under `common/` directory rather than `stocks/`, which means the function should be written in a generic way, i.e. not mentioning stocks or a specific context.
+2. If the model requires an API key, make sure to handle the error and output relevant message.
+3. If the data provider is not yet supported, you'll most likely need to do some extra steps in order to add it to the `keys` menu. See [this section](#external-api-keys) for more details.
+
+Some of the most common error messages are:
+
+- Error in the request (HTTP error)
+- Invalid API Keys
+- API Keys not authorized for Premium feature
+- Empty return payload
+- Invalid arguments (Optional)
+
+In the example below, you can see that we explicitly handle some of them.
+It's not always possible to distinguish error types using `status_code`. So depending on the API provider, you can use either error messages or exceptions.
+
+```python
+
+@check_api_key(["API_NEWS_TOKEN"])
+def get_news(
+ query: str,
+ limit: int = 10,
+ start_date: Optional[str] = None,
+ show_newest: bool = True,
+ sources: str = "",
+) -> pd.DataFrame:
+
+ ...
+
+ link += f"&apiKey={get_current_user().credentials.API_NEWS_TOKEN}"
+ response = request(link)
+ articles = {}
+
+ if response.status_code == 200:
+ response_json = response.json()
+ articles = (response_json["articles"] if show_newest else response_json["articles"][::-1])
+
+ elif response.status_code == 426:
+ console.print(f"Error in request: {response.json()['message']}", "\n")
+ elif response.status_code == 401:
+ console.print("[red]Invalid API Key[/red]\n")
+ elif response.status_code == 429:
+ console.print("[red]Exceeded number of calls per minute[/red]\n")
+ else:
+ console.print(f"Error in request: {response.json()['message']}", "\n")
+
+ ...
+```
+
+> Click [here](openbb_terminal/common/newsapi_model.py) to see the example in detail.
+
+### Data sources
+
+Now that we have added the model function getting, we need to specify that this is an available data source. To do so, we edit the `openbb_terminal/miscellaneous/sources/openbb_default.json` file. This file, described below, uses a dictionary structure to identify available sources. Since we are adding FMP to `stocks/fa/pt`, we find that entry and append it:
+
+```json
+ "fa": {
+ "pt": ["BusinessInsider", "FinancialModelingPrep"],
+```
+
+If you are adding a new function with a new data source, make a new value in the file. If the data source requires an
+API key, please refer to the guide below for adding them. Instructions for obtaining the new api key
+should be included in the file `OpenBBTerminal/website/content/terminal/usage/data/api-keys.md`.
+
+### View
+
+1. Create a file with the source of data as the name followed by `_view` if it doesn't exist, e.g. `fmp_view`
+2. Add the documentation header
+3. Do the necessary imports to display the data. One of these is the `_model` associated with this `_view`. I.e. from same data source.
+4. Define a function starting with `display_`
+5. In this function:
+ - Use typing hints
+ - Write a descriptive description where at the end the source is specified
+ - Get the data from the `_model` and parse it to be output in a more meaningful way.
+ - Do not degrade the main data dataframe coming from model if there's an export flag. This is so that the export can have all the data rather than the short amount of information we may show to the user. Thus, in order to do so `df_data = df.copy()` can be useful as if you change `df_data`, `df` remains intact.
+6. If the source requires an API Key or some sort of tokens, add `check_api_key` decorator on that specific view. This will throw a warning if users forget to set their API Keys
+7. Finally, call `export_data` where the variables are export variable, current filename, command name, and dataframe.
+
+```python
+
+@check_api_key(["API_KEY_FINANCIALMODELINGPREP"])
+def display_price_targets(
+ symbol: str, limit: int = 10, export: str = "", sheet_name: Optional[str] = None
+):
+ """Display price targets for a given ticker. [Source: Financial Modeling Prep]
+
+ Parameters
+ ----------
+ symbol : str
+ Symbol
+ limit: int
+ Number of last days ratings to display
+ export: str
+ Export dataframe data to csv,json,xlsx file
+ sheet_name: str
+ Optionally specify the name of the sheet the data is exported to.
+ """
+ columns_to_show = [
+ "publishedDate",
+ "analystCompany",
+ "adjPriceTarget",
+ "priceWhenPosted",
+ ]
+ price_targets = fmp_model.get_price_targets(symbol)
+ if price_targets.empty:
+ console.print(f"[red]No price targets found for {symbol}[/red]\n")
+ return
+ price_targets["publishedDate"] = price_targets["publishedDate"].apply(
+ lambda x: datetime.strptime(x, "%Y-%m-%dT%H:%M:%S.%fZ").strftime("%Y-%m-%d %H:%M")
+ )
+ export_data(
+ export,
+ os.path.dirname(os.path.abspath(__file__)),
+ "pt",
+ price_targets,
+ sheet_name,
+ )
+
+ print_rich_table(
+ price_targets[columns_to_show].head(limit),
+ headers=["Date", "Company", "Target", "Posted Price"],
+ show_index=False,
+ title=f"{symbol.upper()} Price Targets",
+ )
+```
+
+In this function:
+
+- We use the same log and API decorators as in the model.
+- We define the columns we want to show to the user.
+- We get the data from the fmp_model function
+- We check if there is data. If something went wrong, we don't want to show it, so we print a message and return. Note that because we have error messages in both the model and view, there will be two print outs. If you wish to just show one, it is better to handle in the model.
+- We do some parsing of the data to make it more readable. In this case, the output from FMP is not very clear at quick glance, we we put it into something more readable.
+- We export the data. In this function, I decided to export after doing the manipulation. If we do any removing of columns, we should copy the dataframe before exporting.
+- We print the data in table form using our `print_rich_table`. This provides a nice console print using the rich library. Note that here I show the top `limit` rows of the dataframe. Care should be taken to make sure that things are sorted. If a sort is required, there is a `reverse` argument that can be added to sort in reverse order.
+
+### Controller
+
+Now that we have the model and views, it is time to add to the controller.
+
+1. Import the associated `_view` function to the controller.
+2. Add command name to variable `CHOICES_COMMANDS` from `FundamentalAnalysisController` class.
+3. Add command and source to `print_help()`.
+
+ ```python
+ def print_help(self):
+ """Print help."""
+ mt = MenuText("stocks/fa/")
+ mt.add_cmd("load")
+ mt.add_raw("\n")
+ mt.add_param("_ticker", self.ticker.upper())
+ mt.add_raw("\n")
+ mt.add_info("_company_overview")
+ mt.add_cmd("mktcap")
+ mt.add_cmd("overview")
+ mt.add_cmd("divs", not self.suffix)
+
+ ...
+
+ mt.add_cmd("pt")
+ mt.add_cmd("dcf")
+ mt.add_cmd("dcfc")
+ console.print(text=mt.menu_text, menu="Stocks - Fundamental Analysis")
+ ```
+
+4. If there is a condition to display or not the command, this is something that can be leveraged through the `add_cmd` method, e.g. `mt.add_cmd("divs", not self.suffix)`.
+
+5. Add command description to file `i18n/en.yml`. Use the path and command name as key, e.g. `stocks/fa/pt` and the value as description. Please fill in other languages if this is something that you know.
+
+6. Add a method to `FundamentalAnalysisController` class with name: `call_` followed by command name.
+ - This method must start defining a parser with arguments `add_help=False` and
+ `formatter_class=argparse.ArgumentDefaultsHelpFormatter`. In addition `prog` must have the same name as the command, and `description` should be self-explanatory ending with a mention of the data source.
+ - Add parser arguments after defining parser. One important argument to add is the export capability. All commands should be able to export data.
+ - If there is a single or even a main argument, a block of code must be used to insert a fake argument on the list of args provided by the user. This makes the terminal usage being faster.
+
+ ```python
+ if other_args and "-" not in other_args[0][0]:
+ other_args.insert(0, "-l")
+ ```
+
+ - Parse known args from list of arguments and values provided by the user.
+ - Call the function contained in a `_view.py` file with the arguments parsed by argparse.
+
+Note that the function self.parse_known_args_and_warn() has some additional options we can add. If the function is showing a chart, but we want the option to show raw data, we can add the `raw=True` keyword and the resulting namespace will have the `raw` attribute.
+Same with limit, we can pass limit=10 to add the `-l` flag with default=10. Here we also specify the export, and whether it is data only, plots only or anything else. This function also adds the `source` attribute to the namespace. In our example, this is important because we added an additional source.
+
+Our new function will be:
+
+```python
+
+ def call_pt(self, other_args: List[str]):
+ """Process pt command"""
+ parser = argparse.ArgumentParser(
+ add_help=False,
+ prog="pt",
+ description="""Prints price target from analysts. [Source: Business Insider and Financial Modeling Prep]""",
+ )
+ parser.add_argument(
+ "-t",
+ "--ticker",
+ dest="ticker",
+ help="Ticker to analyze",
+ type=str,
+ default=None,
+ )
+ if other_args and "-" not in other_args[0][0]:
+ other_args.insert(0, "-t")
+ ns_parser = self.parse_known_args_and_warn(
+ parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES, raw=True, limit=10
+ )
+ if ns_parser:
+ if ns_parser.ticker:
+ self.ticker = ns_parser.ticker
+ self.custom_load_wrapper([self.ticker])
+
+ if ns_parser.source == "BusinessInsider":
+ business_insider_view.display_price_target_from_analysts(
+ symbol=self.ticker,
+ data=self.stock,
+ start_date=self.start,
+ limit=ns_parser.limit,
+ raw=ns_parser.raw,
+ export=ns_parser.export,
+ sheet_name=" ".join(ns_parser.sheet_name)
+ if ns_parser.sheet_name
+ else None,
+ )
+ elif ns_parser.source == "FinancialModelingPrep":
+ fmp_view.display_price_targets(
+ symbol=self.ticker,
+ limit=ns_parser.limit,
+ export=ns_parser.export,
+ sheet_name=" ".join(ns_parser.sheet_name)
+ if ns_parser.sheet_name
+ else None,
+ )
+```
+
+Here, we make the parser, add the arguments, and then parse the arguments. In order to use the fact that we had a new source, we add the logic to access the correct view function. In this specific menu, we also allow the user to specify the symbol with -t, which is what the first block is doing.
+
+Note that in the `fa` submenu, we allow the function to be run by specifying a ticker, ie `pt -t AAPL`. In this submenu we do a `load` behind the scenes with the ticker selected so that other functions can be run without specifying the ticker.
+
+Now from the terminal, this function can be run as desired:
+
+```bash
+2023 Mar 03, 11:37 (๐Ÿฆ‹) /stocks/fa/ $ pt -t aapl --source FinancialModelingPrep
+
+ AAPL Price Targets
+โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”ณโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”“
+โ”ƒ Date โ”ƒ Company โ”ƒ Target โ”ƒ Posted Price โ”ƒ
+โ”กโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ•‡โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”ฉ
+โ”‚ 2023-02-03 16:19 โ”‚ Cowen & Co. โ”‚ 195.00 โ”‚ 154.50 โ”‚
+โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
+โ”‚ 2023-02-03 09:31 โ”‚ D.A. Davidson โ”‚ 173.00 โ”‚ 157.09 โ”‚
+โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
+โ”‚ 2023-02-03 08:30 โ”‚ Rosenblatt Securities โ”‚ 173.00 โ”‚ 150.82 โ”‚
+โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
+โ”‚ 2023-02-03 08:29 โ”‚ Wedbush โ”‚ 180.00 โ”‚ 150.82 โ”‚
+โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
+โ”‚ 2023-02-03 07:21 โ”‚ Raymond James โ”‚ 170.00 โ”‚ 150.82 โ”‚
+โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
+โ”‚ 2023-02-03 07:05 โ”‚ Barclays โ”‚ 145.00 โ”‚ 150.82 โ”‚
+โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
+โ”‚ 2023-02-03 03:08 โ”‚ KeyBanc โ”‚ 177.00 โ”‚ 150.82 โ”‚
+โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
+โ”‚ 2023-02-02 02:08 โ”‚ Rosenblatt Securities โ”‚ 165.00 โ”‚ 145.43 โ”‚
+โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
+โ”‚ 2023-02-02 02:08 โ”‚ Deutsche Bank โ”‚ 160.00 โ”‚ 145.43 โ”‚
+โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
+โ”‚ 2023-02-02 02:08 โ”‚ J.P. Morgan โ”‚ 180.00 โ”‚ 145.43 โ”‚
+โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
+```
+
+When adding a new menu, the code looks like this:
+
+```python
+
+def call_fa(self, _):
+ """Process fa command"""
+ from openbb_terminal.stocks.fundamental_analysis.fa_controller import (
+ FundamentalAnalysisController,
+ )
+
+ self.queue = self.load_class(
+ FundamentalAnalysisController, self.ticker, self.start, self.stock, self.queue
+ )
+```
+
+The **import only occurs inside this menu call**, this is so that the loading time only happens here and not at the terminal startup. This is to avoid slow loading times for users that are not interested in `stocks/fa` menu.
+
+In addition, note the `self.load_class` which allows to not create a new `FundamentalAnalysisController` instance but re-load the previous created one. Unless the arguments `self.ticker`, `self.start` or `self.stock` have changed since. The `self.queue` list of commands is passed around as it contains the commands that the terminal must perform.
+
+### Add SDK endpoint (V3)
+
+In order to add a command to the SDK, follow these steps:
+
+1. If you've created a new model or view file, add the import with an alias to `openbb_terminal/core/sdk/sdk_init.py` following this structure:
+
+ ```python
+ # Stocks - Fundamental Analysis
+ from openbb_terminal.stocks.fundamental_analysis import (
+ finviz_model as stocks_fa_finviz_model,
+ finnhub_model as stocks_fa_finnhub_model,
+ finnhub_view as stocks_fa_finnhub_view,
+ )
+ ```
+
+2. Go to the `trail_map.csv` file located in `openbb_terminal/core/sdk`, which should look like this:
+
+ ```csv
+ trail,model,view
+ stocks.fa.analyst,stocks_fa_finviz_model.get_analyst_data,
+ stocks.fa.rot,stocks_fa_finnhub_model.get_rating_over_time,stocks_fa_finnhub_view.rating_over_time
+
+ ...
+
+ ```
+
+ In this file, the trail represents the path to the function to be called. The model represents the import alias we gave to the `_model` file. The view represents the import alias we gave to the `_view` file.
+
+3. Add your new function to this structure. In the below example of the `pt` function, our trail would be `stocks.fa.pt`.
+
+ Our naming convention is such that the data source should not be included in the trail. In this example, calling a new function `pt_fmp` would not be allowed.
+ For functions with multiple sources, there should be a single `pt` function that takes in the source as an argument.
+ In the following example, we will stick with showing how the business_insider was initially added to the sdk.
+
+ The model is the import alias to the `_model` function that was written:
+
+ - `stocks_fa_business_insider_model.get_price_target_from_analysts`
+
+ The view is the import alias to the `_view` function that was written:
+
+ - `stocks_fa_business_insider_view.display_price_target_from_analysts`
+
+ The added line of the file should look like this:
+
+ ```csv
+ stocks.fa.pt,stocks_fa_business_insider_model.get_price_target_from_analysts,stocks_fa_business_insider_view.display_price_target_from_analysts
+ ```
+
+4. Generate the SDK files by running `python generate_sdk.py` from the root of the project. This will automatically generate the SDK `openbb_terminal/sdk.py`, corresponding `openbb_terminal/core/sdk/controllers/` and `openbb_terminal/core/sdk/models/` class files.
+
+ To sort the `trail_map.csv` file and generate the SDK files, run the following command
+
+ ```bash
+ python generate_sdk.py sort
+ ```
+
+### Add OpenBB Platform endpoint (V4)
+
+Refer to the documentation [here](https://docs.openbb.co/platform/development).
+
+### Add Unit Tests
+
+This is a vital part of the contribution process. We have a set of unit tests that are run on every Pull Request. These tests are located in the `tests` folder.
+
+Unit tests minimize errors in code and quickly find errors when they do arise. Integration tests are standard usage examples, which are also used to identify errors.
+
+A thorough introduction on the usage of unit tests and integration tests in OpenBBTerminal can be found on the following page:
+
+[Unit Test README](tests/README.md)
+
+[Integration Test README](openbb_terminal/miscellaneous/integration_tests_scripts/README.MD)
+
+Any new features that do not contain unit tests will not be accepted.
+
+### Open a Pull Request
+
+For starters, you should ensure that your branch is up to date with the `develop` branch. To do that, one can run the following commands:
+
+```bash
+git fetch upstream
+git checkout develop
+git merge upstream/develop
+```
+
+After that, you can create a new branch for your feature. E.g. `git checkout -b 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. To learn more, please refer to [Branch Naming Conventions](#branch-naming-conventions).
+
+A user may create a **Draft Pull Request** when there is the intention to discuss implementation with the team.
+
+### 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.
+
+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.
+
+## Understand Code Structure
+
+### Backend
+
+CLI :computer: โ†’ `_controller.py` :robot: โ†’&nbsp;`_view.py` :art: &nbsp;&nbsp;&nbsp; โ†’ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`_model.py` :brain:<br />
+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ &nbsp;&nbsp;&nbsp;
+`chart=True`
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+`chart=False`
+<br/>
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
+ &#8593;
+ &nbsp;&nbsp;
+`sdk.py` :factory:
+ &nbsp;
+ &#8593;
+
+| **File&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;** | **Role** | **Description** |
+| :------------------------- | :------------- | :----------------------------------------------------- |
+| **_controller.py** :robot: | The router/input validator | The controller file should hold the least amount of logic possible. Its role is to be a stupid (no logic) router and redirect the command correctly while checking the input with argparser. |
+| **_view.py** :art: | The artist | The view file should only output or visualize the data it gets from the `_model` file! The `_view` can limit the data coming from the `_model`, otherwise the data object should be identical in the `_view` and the `_model` files. |
+| **_model.py** ๐Ÿง  |The brain | The model file is where everything fun happens. The data is gathered (external APIs), processed and returned here. |
+| **sdk.py** ๐Ÿญ |The SDK Factory | The SDK file is where the callable functions are created for the SDK. There is only one SDK file in the openbb_terminal folder. |
+
+### Frontend
+
+| **Item** | **Description** | **Example** |
+| :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------- |
+| **CONTEXT** | Specific instrument _world_ to analyse. | `stocks`, `crypto`, `economy` |
+| **CATEGORY** | Group of similar COMMANDS to do on the instrument <br /> There are specialized categories, specific to each CONTEXT and there are common categories which are not specific to one CONTEXT. | `due_diligence`, `technical_analysis`, `insider` |
+| **COMMAND** | Operation on one or no instrument that retrieves data in form of string, table or plot. | `rating`, `supplier`, `sentiment` |
+
+The following layout is expected: `/<context>/<category>/<command_files>`
+
+If there are sub-categories, the layout will be: `/<context>/<category>/<sub-category>/<command_files>`
+
+**Example:**
+
+```text
+openbb_terminal/stocks/stocks_controller.py
+ /stocks_helper.py
+ /technical_analysis/ta_controller.py
+ /tradingview_view.py
+ /tradingview_model.py
+ /common/technical_analysis/overlap_view.py
+ /overlap_model.py
+ /crypto/crypto_controller.py
+ /crypto_helper.py
+ /due_diligence/dd_controller.py
+ /binance_view.py
+ /binance_model.py
+ /technical_analysis/ta_controller.py
+```
+
+With:
+
+| **Context** | **Category** | **File** | **Description** |
+| :---------- | :-------------------- | :--------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `stocks/` | | `stocks_controller.py` | Manages **stocks** _context_ from a user perspective, i.e. routing _commands_ and arguments to output data, or, more importantly, redirecting to the selected _category_. |
+| `stocks/` | | `stocks_helper.py` | Helper to `stocks` menu. This file is meant to hold generic purpose `stocks` functionalities. |
+| `stocks/` | `technical_analysis/` | `ta_controller.py` | Manages **technical_analysis** _category_ from **stocks** _context_ from a user perspective, i.e. routing _commands_ and arguments to output data.
+| `stocks/` | `technical_analysis/` | `tradingview_view.py` | This file contains functions that rely on **TradingView** data. These functions represent _commands_ that belong to **technical_analysis** _category_ from **stocks** _context_. These functions are called by `ta_controller.py` using the arguments given by the user and will output either a string, t