summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorChavithra PARANA <chavithra@gmail.com>2022-11-28 19:21:48 +0100
committerChavithra PARANA <chavithra@gmail.com>2022-11-28 19:21:48 +0100
commit80aa4768c6e1c00c02aba9bbb8168e401fc32c9f (patch)
tree96b6171a3e97ee16233a35a4fa961dbe5d5a58e3
parenta4fce79bf7f151f25d32ac4f26c10430f888e22d (diff)
parentc7d0c98aa8d804fb5063b41e91ba2489a5e165e4 (diff)
Merge branch 'main' of github.com:OpenBB-finance/OpenBBTerminal
-rw-r--r--build/docker/README.md55
-rw-r--r--build/docker/build.sh3
-rw-r--r--build/docker/poetry.dockerfile10
-rw-r--r--build/pyinstaller/hooks/hook-inspect.py39
-rw-r--r--build/pyinstaller/terminal.spec4
-rw-r--r--openbb_terminal/etf/discovery/disc_controller.py19
-rw-r--r--openbb_terminal/etf/etf_controller.py104
-rw-r--r--openbb_terminal/etf/screener/screener_controller.py33
-rw-r--r--openbb_terminal/etf/technical_analysis/ta_controller.py183
-rw-r--r--openbb_terminal/portfolio/portfolio_optimization/parameters/params_helpers.py41
-rw-r--r--openbb_terminal/portfolio/portfolio_optimization/parameters/params_view.py11
-rw-r--r--openbb_terminal/reports/reports_controller.py4
-rw-r--r--pyproject.toml6
-rw-r--r--tests/openbb_terminal/etf/technical_analysis/test_ta_controller.py4
14 files changed, 214 insertions, 302 deletions
diff --git a/build/docker/README.md b/build/docker/README.md
deleted file mode 100644
index be6d097e4ea..00000000000
--- a/build/docker/README.md
+++ /dev/null
@@ -1,55 +0,0 @@
-# OpenBBTerminal Docker
-
-These files create our docker images. To use docker read [this](openbb_terminal/DOCKER_README.md).
-
-## Building the Docker Image
-
-Building the Docker image can be done easily. The following steps allow users to create a docker
-image on their local machine:
-
-1. Enter the OpenBB directory `cd ~/OpenbbTerminal`
-1. Open `docker/compose.env` and set `OPENBBTERMINAL_DOCKER_RELEASE_VERSION` to the desired version
-1. Load the environment variables `bash compose/env`
-1. Run the build script `bash docker/build.sh`
-
-### Important Notes
-
-1. The `build.sh` script requires bash 5.0 or newer.
-1. We currently build without developer dependnencies such as pytest. If you want to develop using
- this image please open `poetry.dockerfile` and replace `RUN poetry install --no-dev` with
- `RUN poetry install`
-1. If you have issues building on MacOs run the following commands:
-
- - `export DOCKER_BUILDKIT=0`
- - `export COMPOSE_DOCKER_CLI_BUILD=0`
-
-## Testing the Docker Image
-
-Integration and unit tests can be run on the new docker image. Before running either set of tests
-you need to start up an image with:
-
-- `docker run -it --rm ghcr.io/[BUILD_NAME]`
-
-Leave the terminal open and then navigate docker desktop. Find the currently running image and
-select `open in terminal`. Now:
-
-- For integration tests run: `poetry run python terminal.py scripts -t`
-- For unit tests run `poetry run pytest tests/`
-
-## Setup Explanation
-
-The `compose.env` file is responsible for loading in the correct environment variables before a
-session begins. Once this is done the `build.sh` file creates a docker image with the appropriate
-tag. The `poetry.dockerfile` is the file used to create the image. It has enhanced caching by
-building in three stages. The stages are described below.
-
-- python: this stage creates a new `python:3.9-slim-bullseye` image. Slim-buster is a small linux
- distro that comes with a lot of the functionality we need. We then set environment variables.
- This image will only be reran when there is a new version of `python:3.9-slim-bullseye` or when
- the environment variables are updated (there is almost never a reason to update them).
-- poetry-deps: this stage installs necessary apt packages like curl and git, and then installs
- poetry. This will be reran whenever dependencies change, or whenever `python` has been rerun.
-- poetry: this takes the build from `poetry-deps` and then copies the terminal files into it. Then
- this image runs the terminal. This image will be rebuilt every time files are changed in the
- terminal or when `poetry-deps` has been rerun, however; this is not an issue because it builds
- quickly.
diff --git a/build/docker/build.sh b/build/docker/build.sh
index 97d49fcc173..2b1451944ee 100644
--- a/build/docker/build.sh
+++ b/build/docker/build.sh
@@ -16,4 +16,5 @@ echo "OPENBBTERMINAL_DOCKER_RELEASE_VERSION = $OPENBBTERMINAL_DOCKER_RELEASE_VER
# DISPLAY IMAGES NAMES
echo "OPENBBTERMINAL_DOCKER_POETRY_IMAGE = $OPENBBTERMINAL_DOCKER_POETRY_IMAGE"
-docker build -f docker/poetry.dockerfile -t "${OPENBBTERMINAL_DOCKER_POETRY_IMAGE}" .
+# Docker command to build image
+docker build -f build/docker/poetry.dockerfile -t "${OPENBBTERMINAL_DOCKER_POETRY_IMAGE}" . \ No newline at end of file
diff --git a/build/docker/poetry.dockerfile b/build/docker/poetry.dockerfile
index 2028cb4c6a1..218c53079bc 100644
--- a/build/docker/poetry.dockerfile
+++ b/build/docker/poetry.dockerfile
@@ -45,16 +45,15 @@ RUN curl -sSL https://install.python-poetry.org | python3 -
# copy project requirement files here to ensure they will be cached.
WORKDIR $PYSETUP_PATH
-# Copy poetry files and install git
-COPY pyproject.toml openbb_terminal/SDK_README.md poetry.lock ./
+# Copy poetry files
+COPY pyproject.toml website/content/sdk/quickstart/installation.md poetry.lock ./
RUN mkdir $PYSETUP_PATH/openbb_terminal
-RUN mv SDK_README.md ./openbb_terminal
+RUN mv installation.md ./website/content/sdk/quickstart
RUN touch openbb_terminal/__init__.py
# install runtime deps - uses $POETRY_VIRTUALENVS_IN_PROJECT internally
-RUN poetry install
-RUN poetry install -E optimization
+RUN poetry install --no-dev --no-interaction -E optimization -E prediction
###############################################
# Production Image openbb poetry build
@@ -66,7 +65,6 @@ COPY --from=poetry-deps $PYSETUP_PATH $PYSETUP_PATH
WORKDIR $PYSETUP_PATH
COPY . .
-RUN pip install "u8darts[torch]"
RUN echo "OPENBB_LOGGING_APP_NAME=gst_docker" > .env
diff --git a/build/pyinstaller/hooks/hook-inspect.py b/build/pyinstaller/hooks/hook-inspect.py
new file mode 100644
index 00000000000..718972baf50
--- /dev/null
+++ b/build/pyinstaller/hooks/hook-inspect.py
@@ -0,0 +1,39 @@
+# Runtime hook copied from pyinstaller
+
+
+import inspect
+import os
+import sys
+
+# pylint:disable=W0622,W0212,E1101
+
+_orig_inspect_getsourcefile = inspect.getsourcefile
+
+
+# Provide custom implementation of inspect.getsourcefile() for frozen applications that properly resolves relative
+# filenames obtained from object (e.g., inspect stack-frames). See #5963.
+def _pyi_getsourcefile(object):
+ filename = inspect.getfile(object)
+ if not os.path.isabs(filename):
+ # Check if given filename matches the basename of __main__'s __file__.
+ if hasattr(sys.modules["__main__"], "__file__"):
+ main_file = sys.modules["__main__"].__file__
+ if filename == os.path.basename(main_file):
+ return main_file
+
+ # If filename ends with .py suffix and does not correspond to frozen entry-point script, convert it to
+ # corresponding .pyc in sys._MEIPASS.
+ if filename.endswith(".py"):
+ filename = os.path.normpath(os.path.join(sys._MEIPASS, filename + "c"))
+ # Ensure the relative path did not try to jump out of sys._MEIPASS, just in case...
+ if filename.startswith(sys._MEIPASS):
+ return filename
+ elif filename.startswith(sys._MEIPASS) and filename.endswith(".pyc"):
+ # If filename is already PyInstaller-compatible, prevent any further processing (i.e., with original
+ # implementation).
+ return filename
+ # Use original implementation as a fallback.
+ return _orig_inspect_getsourcefile(object)
+
+
+inspect.getsourcefile = _pyi_getsourcefile
diff --git a/build/pyinstaller/terminal.spec b/build/pyinstaller/terminal.spec
index 1d2dc508569..926e10f69f2 100644
--- a/build/pyinstaller/terminal.spec
+++ b/build/pyinstaller/terminal.spec
@@ -44,6 +44,8 @@ added_files = [
(os.path.join(pathex, "user_agent"), "user_agent"),
(os.path.join(pathex, "vaderSentiment"), "vaderSentiment"),
(os.path.join(pathex, "prophet"), "prophet"),
+ (os.path.join(pathex, "riskfolio"), "riskfolio"),
+ (os.path.join(pathex, "astropy"), "astropy"),
(os.path.join(pathex, "frozendict", "VERSION"), "frozendict"),
(
os.path.join(pathex, "linearmodels", "datasets"),
@@ -91,6 +93,8 @@ hidden_imports = [
"_sysconfigdata__darwin_darwin",
"prophet",
"debugpy",
+ "riskfolio",
+ "astropy",
]
diff --git a/openbb_terminal/etf/discovery/disc_controller.py b/openbb_terminal/etf/discovery/disc_controller.py
index e4af43d3165..0c8ee2a9205 100644
--- a/openbb_terminal/etf/discovery/disc_controller.py
+++ b/openbb_terminal/etf/discovery/disc_controller.py
@@ -29,29 +29,14 @@ class DiscoveryController(BaseController):
"active",
]
PATH = "/etf/disc/"
+ CHOICES_GENERATION = True
def __init__(self, queue: List[str] = None):
"""Constructor"""
super().__init__(queue)
if session and obbff.USE_PROMPT_TOOLKIT:
- choices: dict = {c: {} for c in self.controller_choices}
-
- choices["gainers"] = {
- "--limit": None,
- "-l": "--limit",
- }
- choices["decliners"] = {
- "--limit": None,
- "-l": "--limit",
- }
- choices["active"] = {
- "--limit": None,
- "-l": "--limit",
- }
-
- choices["support"] = self.SUPPORT_CHOICES
- choices["about"] = self.ABOUT_CHOICES
+ choices: dict = self.choices_default
self.completer = NestedCompleter.from_nested_dict(choices)
diff --git a/openbb_terminal/etf/etf_controller.py b/openbb_terminal/etf/etf_controller.py
index 626d92ce1d3..a131e4ca428 100644
--- a/openbb_terminal/etf/etf_controller.py
+++ b/openbb_terminal/etf/etf_controller.py
@@ -28,7 +28,6 @@ from openbb_terminal.etf.technical_analysis import ta_controller
from openbb_terminal.helper_funcs import (
EXPORT_BOTH_RAW_DATA_AND_FIGURES,
EXPORT_ONLY_RAW_DATA_ALLOWED,
- check_non_negative_float,
check_positive,
export_data,
valid_date,
@@ -37,7 +36,7 @@ from openbb_terminal.helper_funcs import (
)
from openbb_terminal.menu import session
from openbb_terminal.parent_classes import BaseController
-from openbb_terminal.rich_config import console, MenuText, get_ordered_list_sources
+from openbb_terminal.rich_config import console, MenuText
from openbb_terminal.stocks import stocks_helper
from openbb_terminal.stocks.comparison_analysis import ca_controller
@@ -82,6 +81,7 @@ class ETFController(BaseController):
PATH = "/etf/"
FILE_PATH = os.path.join(os.path.dirname(__file__), "README.md")
+ CHOICES_GENERATION = True
def __init__(self, queue: List[str] = None):
"""Constructor"""
@@ -93,58 +93,7 @@ class ETFController(BaseController):
self.TRY_RELOAD = True
if session and obbff.USE_PROMPT_TOOLKIT:
- choices: dict = {c: {} for c in self.controller_choices}
- one_to_hundred: dict = {str(c): {} for c in range(1, 100)}
- choices["search"] = {
- "--name": None,
- "-n": "--name",
- "--description": None,
- "-d": "--description",
- "--source": {
- c: {} for c in get_ordered_list_sources(f"{self.PATH}search")
- },
- "--limit": None,
- "-l": "--limit",
- }
- choices["load"] = {
- "--ticker": None,
- "-t": "--ticker",
- "--start": None,
- "-s": "--start",
- "--end": None,
- "-e": "--end",
- "--limit": None,
- "-l": "--limit",
- }
- choices["weights"] = {"--min": one_to_hundred, "-m": "--min", "--raw": {}}
-
- choices["candle"] = {
- "--sort": {c: {} for c in self.CANDLE_COLUMNS},
- "-s": "--sort",
- "--plotly": {},
- "-p": "--plotly",
- "--ma": None,
- "--reverse": {},
- "-r": "--reverse",
- "--trend": {},
- "-t": "--trend",
- "--raw": {},
- "--num": one_to_hundred,
- "-n": "--num",
- }
- choices["pir"] = {
- "--etfs": None,
- "-e": "--etfs",
- "--filename": None,
- "--folder": None,
- }
- choices["compare"] = {
- "--etfs": None,
- "-e": "--etfs",
- }
-
- choices["support"] = self.SUPPORT_CHOICES
- choices["about"] = self.ABOUT_CHOICES
+ choices: dict = self.choices_default
self.completer = NestedCompleter.from_nested_dict(choices)
@@ -372,9 +321,6 @@ class ETFController(BaseController):
help="Number of holdings to get",
default=10,
)
- if not self.etf_name:
- console.print("Please load a ticker using <load name>. \n")
- return
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-l")
@@ -382,12 +328,15 @@ class ETFController(BaseController):
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
- stockanalysis_view.view_holdings(
- symbol=self.etf_name,
- limit=ns_parser.limit,
- export=ns_parser.export,
- )
- console.print()
+ if self.etf_name:
+ stockanalysis_view.view_holdings(
+ symbol=self.etf_name,
+ limit=ns_parser.limit,
+ export=ns_parser.export,
+ )
+ console.print()
+ else:
+ console.print("Please load a ticker using <load name>. \n")
@log_start_end(log=logger)
def call_news(self, other_args: List[str]):
@@ -498,19 +447,14 @@ class ETFController(BaseController):
),
)
parser.add_argument(
- "--raw",
- action="store_true",
- dest="raw",
- default=False,
- help="Shows raw data instead of chart",
- )
- parser.add_argument(
"-n",
"--num",
type=check_positive,
help="Number to show if raw selected",
dest="num",
default=20,
+ choices=range(1, 100),
+ metavar="NUM",
)
parser.add_argument(
"-t",
@@ -532,7 +476,10 @@ class ETFController(BaseController):
)
ns_parser = self.parse_known_args_and_warn(
- parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES
+ parser,
+ other_args,
+ EXPORT_BOTH_RAW_DATA_AND_FIGURES,
+ raw=True,
)
if ns_parser:
if not self.etf_name:
@@ -655,22 +602,21 @@ class ETFController(BaseController):
parser.add_argument(
"-m",
"--min",
- type=check_non_negative_float,
+ type=check_positive,
dest="min",
help="Minimum positive float to display sector",
default=5,
- )
- parser.add_argument(
- "--raw",
- action="store_true",
- dest="raw",
- help="Only output raw data",
+ choices=range(1, 100),
+ metavar="MIN",
)
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-l")
ns_parser = self.parse_known_args_and_warn(
- parser, other_args, export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES
+ parser,
+ other_args,
+ export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES,
+ raw=True,
)
if ns_parser:
yfinance_view.display_etf_weightings(
diff --git a/openbb_terminal/etf/screener/screener_controller.py b/openbb_terminal/etf/screener/screener_controller.py
index 42480232603..8536bdf1420 100644
--- a/openbb_terminal/etf/screener/screener_controller.py
+++ b/openbb_terminal/etf/screener/screener_controller.py
@@ -33,7 +33,8 @@ class ScreenerController(BaseController):
"sbc",
]
- preset_choices = screener_model.get_preset_choices()
+ PRESET_CHOICES = screener_model.get_preset_choices()
+ ETF_CATEGORY_LIST = financedatabase_model.get_etfs_categories()
sortby_screen_choices = [
"Assets",
@@ -53,6 +54,7 @@ class ScreenerController(BaseController):
]
PATH = "/etf/scr/"
+ CHOICES_GENERATION = True
def __init__(self, queue: List[str] = None):
"""Constructor"""
@@ -62,15 +64,10 @@ class ScreenerController(BaseController):
self.screen_tickers: List = list()
if session and obbff.USE_PROMPT_TOOLKIT:
- choices: dict = {c: {} for c in self.controller_choices}
- choices["view"] = {c: None for c in self.preset_choices}
- choices["set"] = {c: None for c in self.preset_choices}
- choices["sbc"] = {
- c: None for c in financedatabase_model.get_etfs_categories()
- }
-
- choices["support"] = self.SUPPORT_CHOICES
- choices["about"] = self.ABOUT_CHOICES
+ choices: dict = self.choices_default
+ choices["view"].update({c: None for c in self.PRESET_CHOICES})
+ choices["set"].update({c: None for c in self.PRESET_CHOICES})
+ choices["sbc"].update({c: None for c in self.ETF_CATEGORY_LIST})
self.completer = NestedCompleter.from_nested_dict(choices)
@@ -103,7 +100,7 @@ class ScreenerController(BaseController):
type=str,
help="View specific custom preset",
default="",
- choices=self.preset_choices,
+ choices=self.PRESET_CHOICES,
)
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-p")
@@ -112,7 +109,7 @@ class ScreenerController(BaseController):
if ns_parser.preset:
preset_filter = configparser.RawConfigParser()
preset_filter.optionxform = str # type: ignore
- preset_filter.read(self.preset_choices[ns_parser.preset])
+ preset_filter.read(self.PRESET_CHOICES[ns_parser.preset])
headers = [
"Price",
@@ -144,9 +141,9 @@ class ScreenerController(BaseController):
else:
console.print("\nPresets:")
- for preset in self.preset_choices:
+ for preset in self.PRESET_CHOICES:
with open(
- self.preset_choices[preset],
+ self.PRESET_CHOICES[preset],
encoding="utf8",
) as f:
description = ""
@@ -174,7 +171,7 @@ class ScreenerController(BaseController):
type=str,
default="template",
help="Filter presets",
- choices=self.preset_choices,
+ choices=self.PRESET_CHOICES,
)
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-p")
@@ -251,6 +248,8 @@ class ScreenerController(BaseController):
nargs="+",
help="Category to look for",
required="-h" not in other_args,
+ choices=self.ETF_CATEGORY_LIST,
+ metavar="CATEGORY",
)
parser.add_argument(
"-l",
@@ -269,7 +268,7 @@ class ScreenerController(BaseController):
)
if ns_parser:
category = " ".join(ns_parser.category)
- if category in financedatabase_model.get_etfs_categories():
+ if category in self.ETF_CATEGORY_LIST:
financedatabase_view.display_etf_by_category(
category=category,
limit=ns_parser.limit,
@@ -278,5 +277,5 @@ class ScreenerController(BaseController):
else:
console.print(
"The category selected does not exist, choose one from:"
- f" {', '.join(financedatabase_model.get_etfs_categories())}\n"
+ f" {', '.join(self.ETF_CATEGORY_LIST)}\n"
)
diff --git a/openbb_terminal/etf/technical_analysis/ta_controller.py b/openbb_terminal/etf/technical_analysis/ta_controller.py
index b05664f75b1..d97b0d8fbc1 100644
--- a/openbb_terminal/etf/technical_analysis/ta_controller.py
+++ b/openbb_terminal/etf/technical_analysis/ta_controller.py
@@ -27,6 +27,7 @@ from openbb_terminal.common.technical_analysis import (
from openbb_terminal.decorators import log_start_end
from openbb_terminal.helper_funcs import (
EXPORT_BOTH_RAW_DATA_AND_FIGURES,
+ check_non_negative,
check_positive,
check_positive_list,
valid_date,
@@ -72,6 +73,7 @@ class TechnicalAnalysisController(BaseController):
]
PATH = "/etf/ta/"
+ CHOICES_GENERATION = True
def __init__(
self,
@@ -90,112 +92,6 @@ class TechnicalAnalysisController(BaseController):
if session and obbff.USE_PROMPT_TOOLKIT:
choices: dict = {c: {} for c in self.controller_choices}
- one_to_hundred: dict = {str(c): {} for c in range(1, 100)}
- zero_to_hundred: dict = {str(c): {} for c in range(0, 100)}
- ma = {
- "--length": None,
- "-l": "--length",
- "--offset": zero_to_hundred,
- "-o": "--offset",
- }
- choices["ema"] = ma
- choices["sma"] = ma
- choices["wma"] = ma
- choices["hma"] = ma
- choices["zlma"] = ma
- choices["vwap"] = {
- "--offset": zero_to_hundred,
- "-o": "--offset",
- "--start": None,
- "--end": None,
- }
- choices["cci"] = {
- "--length": one_to_hundred,
- "-l": "--length",
- "--scalar": None,
- "-s": "--scalar",
- }
- choices["macd"] = {
- "--fast": one_to_hundred,
- "--slow": "--fast",
- "--signal": "--fast",
- }
- choices["cci"] = {
- "--length": one_to_hundred,
- "-l": "--length",
- "--scalar": None,
- "-s": "--scalar",
- "--drift": "--length",
- "-d": "--drift",
- }
- choices["stoch"] = {
- "--fastkperiod": one_to_hundred,
- "-k": "--fastkperiod",
- "--slowdperiod": "--fastkperiod",
- "-d": "--slowdperiod",
- "--slowkperiod": "--fastkperiod",
- }
- choices["fisher"] = {
- "--length": one_to_hundred,
- "-l": "--length",
- }
- choices["cg"] = {
- "--length": one_to_hundred,
- "-l": "--length",
- }
- choices["adx"] = {
- "--length": one_to_hundred,
- "-l": "--length",
- "--scalar": None,
- "-s": "--scalar",
- "--drift": "--length",
- "-d": "--drift",
- }
- choices["aroon"] = {
- "--length": one_to_hundred,
- "-l": "--length",
- "--scalar": None,
- "-s": "--scalar",
- }
- choices["bbands"] = {
- "--length": one_to_hundred,
- "-l": "--length",
- "--std": {str(c): {} for c in np.arange(0.0, 10, 0.25)},
- "-s": "--std",
- "--mamode": {c: {} for c in volatility_model.MAMODES},
- "-m": "--mamode",
- }
- choices["donchian"] = {
- "--length_upper": one_to_hundred,
- "-u": "--length_upper",
- "--length_lower": "--length_upper",
- "-l": "--length_lower",
- }
- choices["kc"] = {
- "--length": one_to_hundred,
- "-l": "--length",
- "--scalar": None,
- "-s": "--scalar",
- "--mamode": {c: {} for c in volatility_model.MAMODES},
- "-m": "--mamode",
- "--offset": zero_to_hundred,
- "-o": "--offset",
- }
- choices["ad"]["--open"] = {}
- choices["adosc"] = {
- "--open": {},
- "--fast": one_to_hundred,
- "--slow": "--fast",
- }
- choices["fib"] = {
- "--period": {str(c): {} for c in range(1, 960)},
- "--start": None,
- "--end": None,
- }
-
- choices["support"] = self.SUPPORT_CHOICES
- choices["about"] = self.ABOUT_CHOICES
-
self.completer = NestedCompleter.from_nested_dict(choices)
def print_help(self):
@@ -273,9 +169,11 @@ class TechnicalAnalysisController(BaseController):
"--offset",
action="store",
dest="n_offset",
- type=int,
+ type=check_non_negative,
default=0,
help="offset",
+ choices=range(0, 100),
+ metavar="N_OFFSET",
)
if other_args and "-" not in other_args[0][0]:
@@ -325,9 +223,11 @@ class TechnicalAnalysisController(BaseController):
"--offset",
action="store",
dest="n_offset",
- type=int,
+ type=check_non_negative,
default=0,
help="offset",
+ choices=range(0, 100),
+ metavar="N_OFFSET",
)
if other_args and "-" not in other_args[0][0]:
@@ -374,9 +274,11 @@ class TechnicalAnalysisController(BaseController):
"--offset",
action="store",
dest="n_offset",
- type=int,
+ type=check_non_negative,
default=0,
help="offset",
+ choices=range(0, 100),
+ metavar="N_OFFSET",
)
if other_args and "-" not in other_args[0][0]:
@@ -423,9 +325,11 @