mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 04:31:13 +00:00
ETF context new and improved (#1103)
* Add lookup commands * main ETF commands * Add technical analysis to ETF * add prediction techniques to ETF * add comparison analysis to etf * allow to select nr of holdings on load * add shold and summary commands to ETF * minor improvs * screener on ETF menu * add etf/scr wsj commands * readd compare command * add sbc to etf/screener * rename chold and shold * fix rogue etf and reset * minor corrections * minor menu print improv * legend of weights and title of plot * refactor screener to screener and disc on ETF * add ETF scripts
This commit is contained in:
committed by
GitHub
parent
f83ee10c22
commit
78f110b389
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
- run: pip install types-pytz types-requests types-termcolor types-tabulate types-PyYAML types-python-dateutil
|
||||
- run: bandit -x ./tests -r . || true
|
||||
- run: black --check .
|
||||
- run: codespell --ignore-words-list=ba,buil,coo,ether,hist,hsi,mape,navagation,operatio,pres,ser,yeld --quiet-level=2 --skip=./tests,.git,*.css,*.csv,*.html,*.ini,*.ipynb,*.js,*.json,*.lock,*.scss,*.txt,*.yaml
|
||||
- run: codespell --ignore-words-list=ba,buil,coo,ether,hist,hsi,mape,navagation,operatio,pres,ser,yeld,shold --quiet-level=2 --skip=./tests,.git,*.css,*.csv,*.html,*.ini,*.ipynb,*.js,*.json,*.lock,*.scss,*.txt,*.yaml
|
||||
- run: flake8 . --count --ignore=E203,W503 --max-line-length=122 --show-source --statistics
|
||||
- run: mypy --ignore-missing-imports --exclude="/setup\.py$" .
|
||||
- run: shopt -s globstar && pyupgrade --py36-plus **/*.py
|
||||
|
||||
2
.pre-commit-config.yaml
vendored
2
.pre-commit-config.yaml
vendored
@@ -23,7 +23,7 @@ repos:
|
||||
hooks:
|
||||
- id: codespell
|
||||
entry: codespell
|
||||
args: [ '--ignore-words-list=ba,buil,coo,ether,hist,hsi,mape,navagation,operatio,pres,ser,yeld', '--quiet-level=2', '--skip=./tests,.git,*.css,*.csv,*.html,*.ini,*.ipynb,*.js,*.json,*.lock,*.scss,*.txt,*.yaml' ]
|
||||
args: [ '--ignore-words-list=ba,buil,coo,ether,hist,hsi,mape,navagation,operatio,pres,ser,yeld,shold', '--quiet-level=2', '--skip=./tests,.git,*.css,*.csv,*.html,*.ini,*.ipynb,*.js,*.json,*.lock,*.scss,*.txt,*.yaml' ]
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: 'v0.910-1'
|
||||
hooks:
|
||||
|
||||
307
gamestonk_terminal/etf/discovery/disc_controller.py
Normal file
307
gamestonk_terminal/etf/discovery/disc_controller.py
Normal file
@@ -0,0 +1,307 @@
|
||||
"""Discovery Controller Module"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import argparse
|
||||
import difflib
|
||||
from typing import List, Union
|
||||
|
||||
from prompt_toolkit.completion import NestedCompleter
|
||||
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.helper_funcs import (
|
||||
EXPORT_ONLY_RAW_DATA_ALLOWED,
|
||||
get_flair,
|
||||
parse_known_args_and_warn,
|
||||
try_except,
|
||||
system_clear,
|
||||
check_positive,
|
||||
)
|
||||
from gamestonk_terminal.menu import session
|
||||
from gamestonk_terminal.etf.discovery import wsj_view
|
||||
|
||||
|
||||
class DiscoveryController:
|
||||
"""Discovery Controller class"""
|
||||
|
||||
CHOICES = [
|
||||
"cls",
|
||||
"home",
|
||||
"h",
|
||||
"?",
|
||||
"help",
|
||||
"q",
|
||||
"quit",
|
||||
"..",
|
||||
"exit",
|
||||
"r",
|
||||
"reset",
|
||||
]
|
||||
|
||||
CHOICES_COMMANDS = [
|
||||
"gainers",
|
||||
"decliners",
|
||||
"active",
|
||||
]
|
||||
|
||||
CHOICES += CHOICES_COMMANDS
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
queue: List[str] = None,
|
||||
):
|
||||
"""Constructor"""
|
||||
self.disc_parser = argparse.ArgumentParser(add_help=False, prog="disc")
|
||||
self.disc_parser.add_argument(
|
||||
"cmd",
|
||||
choices=self.CHOICES,
|
||||
)
|
||||
self.completer: Union[None, NestedCompleter] = None
|
||||
if session and gtff.USE_PROMPT_TOOLKIT:
|
||||
choices: dict = {c: {} for c in self.CHOICES}
|
||||
self.completer = NestedCompleter.from_nested_dict(choices)
|
||||
|
||||
if queue:
|
||||
self.queue = queue
|
||||
else:
|
||||
self.queue = list()
|
||||
|
||||
def print_help(self):
|
||||
"""Print help"""
|
||||
help_str = """
|
||||
gainers show top gainers [WSJ]
|
||||
decliners show top decliners [WSJ]
|
||||
active show most active [WSJ]
|
||||
"""
|
||||
print(help_str)
|
||||
|
||||
def switch(self, an_input: str):
|
||||
"""Process and dispatch input
|
||||
Parameters
|
||||
-------
|
||||
an_input : str
|
||||
string with input arguments
|
||||
Returns
|
||||
-------
|
||||
List[str]
|
||||
List of commands in the queue to execute
|
||||
"""
|
||||
|
||||
# Empty command
|
||||
if not an_input:
|
||||
print("")
|
||||
return self.queue
|
||||
|
||||
# Navigation slash is being used
|
||||
if "/" in an_input:
|
||||
actions = an_input.split("/")
|
||||
|
||||
# Absolute path is specified
|
||||
if not actions[0]:
|
||||
an_input = "home"
|
||||
# Relative path so execute first instruction
|
||||
else:
|
||||
an_input = actions[0]
|
||||
|
||||
# Add all instructions to the queue
|
||||
for cmd in actions[1:][::-1]:
|
||||
if cmd:
|
||||
self.queue.insert(0, cmd)
|
||||
|
||||
(known_args, other_args) = self.disc_parser.parse_known_args(an_input.split())
|
||||
|
||||
# Redirect commands to their correct functions
|
||||
if known_args.cmd:
|
||||
if known_args.cmd in ("..", "q"):
|
||||
known_args.cmd = "quit"
|
||||
elif known_args.cmd in ("?", "h"):
|
||||
known_args.cmd = "help"
|
||||
elif known_args.cmd == "r":
|
||||
known_args.cmd = "reset"
|
||||
|
||||
getattr(
|
||||
self,
|
||||
"call_" + known_args.cmd,
|
||||
lambda _: "Command not recognized!",
|
||||
)(other_args)
|
||||
|
||||
return self.queue
|
||||
|
||||
def call_cls(self, _):
|
||||
"""Process cls command"""
|
||||
system_clear()
|
||||
|
||||
def call_home(self, _):
|
||||
"""Process home command"""
|
||||
self.queue.insert(0, "quit")
|
||||
self.queue.insert(0, "quit")
|
||||
|
||||
def call_help(self, _):
|
||||
"""Process help command"""
|
||||
self.print_help()
|
||||
|
||||
def call_quit(self, _):
|
||||
"""Process quit menu command"""
|
||||
print("")
|
||||
self.queue.insert(0, "quit")
|
||||
|
||||
def call_exit(self, _):
|
||||
"""Process exit terminal command"""
|
||||
self.queue.insert(0, "quit")
|
||||
self.queue.insert(0, "quit")
|
||||
self.queue.insert(0, "quit")
|
||||
|
||||
def call_reset(self, _):
|
||||
"""Process reset command"""
|
||||
self.queue.insert(0, "disc")
|
||||
self.queue.insert(0, "etf")
|
||||
self.queue.insert(0, "reset")
|
||||
self.queue.insert(0, "quit")
|
||||
self.queue.insert(0, "quit")
|
||||
|
||||
@try_except
|
||||
def call_gainers(self, other_args):
|
||||
"""Process gainers command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="gainers",
|
||||
description="Displays top ETF/Mutual fund gainers from wsj.com/market-data",
|
||||
add_help=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--limit",
|
||||
help="Limit of ETFs to display",
|
||||
type=check_positive,
|
||||
default=10,
|
||||
dest="limit",
|
||||
)
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-l")
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
wsj_view.show_top_mover("gainers", ns_parser.limit, ns_parser.export)
|
||||
|
||||
def call_decliners(self, other_args):
|
||||
"""Process decliners command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="decliners",
|
||||
description="Displays top ETF/Mutual fund decliners from wsj.com/market-data",
|
||||
add_help=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--limit",
|
||||
help="Limit of ETFs to display",
|
||||
type=check_positive,
|
||||
default=10,
|
||||
dest="limit",
|
||||
)
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-l")
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
wsj_view.show_top_mover("decliners", ns_parser.limit, ns_parser.export)
|
||||
|
||||
def call_active(self, other_args):
|
||||
"""Process gainers command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="active",
|
||||
description="Displays most active ETF/Mutual funds from wsj.com/market-data",
|
||||
add_help=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--limit",
|
||||
help="Limit of ETFs to display",
|
||||
type=check_positive,
|
||||
default=10,
|
||||
dest="limit",
|
||||
)
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-l")
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
wsj_view.show_top_mover("active", ns_parser.limit, ns_parser.export)
|
||||
|
||||
|
||||
def menu(
|
||||
queue: List[str] = None,
|
||||
):
|
||||
"""Discovery Menu"""
|
||||
disc_controller = DiscoveryController(queue)
|
||||
an_input = "HELP_ME"
|
||||
|
||||
while True:
|
||||
# There is a command in the queue
|
||||
if disc_controller.queue and len(disc_controller.queue) > 0:
|
||||
# If the command is quitting the menu we want to return in here
|
||||
if disc_controller.queue[0] in ("q", "..", "quit"):
|
||||
if len(disc_controller.queue) > 1:
|
||||
return disc_controller.queue[1:]
|
||||
return []
|
||||
|
||||
# Consume 1 element from the queue
|
||||
an_input = disc_controller.queue[0]
|
||||
disc_controller.queue = disc_controller.queue[1:]
|
||||
|
||||
# Print the current location because this was an instruction and we want user to know what was the action
|
||||
if an_input and an_input.split(" ")[0] in disc_controller.CHOICES_COMMANDS:
|
||||
print(f"{get_flair()} /etf/disc/ $ {an_input}")
|
||||
|
||||
# Get input command from user
|
||||
else:
|
||||
# Display help menu when entering on this menu from a level above
|
||||
if an_input == "HELP_ME":
|
||||
disc_controller.print_help()
|
||||
|
||||
# Get input from user using auto-completion
|
||||
if session and gtff.USE_PROMPT_TOOLKIT and disc_controller.completer:
|
||||
an_input = session.prompt(
|
||||
f"{get_flair()} /etf/disc/ $ ",
|
||||
completer=disc_controller.completer,
|
||||
search_ignore_case=True,
|
||||
)
|
||||
# Get input from user without auto-completion
|
||||
else:
|
||||
an_input = input(f"{get_flair()} /etf/disc/ $ ")
|
||||
|
||||
try:
|
||||
# Process the input command
|
||||
disc_controller.queue = disc_controller.switch(an_input)
|
||||
|
||||
except SystemExit:
|
||||
print(
|
||||
f"\nThe command '{an_input}' doesn't exist on the /etf/disc menu.",
|
||||
end="",
|
||||
)
|
||||
similar_cmd = difflib.get_close_matches(
|
||||
an_input.split(" ")[0] if " " in an_input else an_input,
|
||||
disc_controller.CHOICES,
|
||||
n=1,
|
||||
cutoff=0.7,
|
||||
)
|
||||
if similar_cmd:
|
||||
if " " in an_input:
|
||||
candidate_input = (
|
||||
f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}"
|
||||
)
|
||||
else:
|
||||
candidate_input = similar_cmd[0]
|
||||
|
||||
if candidate_input == an_input:
|
||||
an_input = ""
|
||||
disc_controller.queue = []
|
||||
print("\n")
|
||||
continue
|
||||
|
||||
print(f" Replacing by '{candidate_input}'.")
|
||||
disc_controller.queue.insert(0, candidate_input)
|
||||
else:
|
||||
print("\n")
|
||||
an_input = ""
|
||||
disc_controller.queue = []
|
||||
50
gamestonk_terminal/etf/discovery/wsj_view.py
Normal file
50
gamestonk_terminal/etf/discovery/wsj_view.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""WSJ view"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import os
|
||||
|
||||
from tabulate import tabulate
|
||||
|
||||
from gamestonk_terminal.etf.discovery import wsj_model
|
||||
from gamestonk_terminal.helper_funcs import (
|
||||
export_data,
|
||||
)
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
|
||||
|
||||
def show_top_mover(sort_type: str, limit: int = 10, export=""):
|
||||
"""
|
||||
Show top ETF movers from wsj.com
|
||||
Parameters
|
||||
----------
|
||||
sort_type: str
|
||||
What to show. Either Gainers, Decliners or Activity
|
||||
limit: int
|
||||
Number of etfs to show
|
||||
export: str
|
||||
Format to export data
|
||||
"""
|
||||
data = wsj_model.etf_movers(sort_type)
|
||||
if data.empty:
|
||||
print("No data available\n")
|
||||
return
|
||||
|
||||
if gtff.USE_TABULATE_DF:
|
||||
print(
|
||||
tabulate(
|
||||
data.iloc[:limit],
|
||||
showindex=False,
|
||||
headers=data.columns,
|
||||
floatfmt=".2f",
|
||||
tablefmt="fancy_grid",
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(data.head(limit).to_string())
|
||||
export_data(
|
||||
export,
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
sort_type,
|
||||
data,
|
||||
)
|
||||
print("")
|
||||
@@ -4,27 +4,45 @@ __docformat__ = "numpy"
|
||||
import argparse
|
||||
import difflib
|
||||
import os
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Union
|
||||
import yfinance as yf
|
||||
import matplotlib.pyplot as plt
|
||||
import mplfinance as mpf
|
||||
from colorama import Style
|
||||
|
||||
from prompt_toolkit.completion import NestedCompleter
|
||||
from thepassiveinvestor import create_ETF_report
|
||||
|
||||
from thepassiveinvestor import create_ETF_report
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.etf import (
|
||||
screener_view,
|
||||
stockanalysis_view,
|
||||
wsj_view,
|
||||
financedatabase_view,
|
||||
stockanalysis_model,
|
||||
yfinance_view,
|
||||
)
|
||||
from gamestonk_terminal.common import newsapi_view
|
||||
from gamestonk_terminal.helper_funcs import (
|
||||
EXPORT_BOTH_RAW_DATA_AND_FIGURES,
|
||||
EXPORT_ONLY_RAW_DATA_ALLOWED,
|
||||
check_non_negative_float,
|
||||
check_positive,
|
||||
valid_date,
|
||||
get_flair,
|
||||
parse_known_args_and_warn,
|
||||
try_except,
|
||||
system_clear,
|
||||
plot_autoscale,
|
||||
export_data,
|
||||
)
|
||||
from gamestonk_terminal.menu import session
|
||||
from gamestonk_terminal.stocks import stocks_helper
|
||||
from gamestonk_terminal.etf.technical_analysis import ta_controller
|
||||
from gamestonk_terminal.stocks.comparison_analysis import ca_controller
|
||||
from gamestonk_terminal.etf.screener import screener_controller
|
||||
from gamestonk_terminal.etf.discovery import disc_controller
|
||||
|
||||
# pylint: disable=C0415,C0302
|
||||
|
||||
|
||||
class ETFController:
|
||||
@@ -45,26 +63,29 @@ class ETFController:
|
||||
]
|
||||
|
||||
CHOICES_COMMANDS = [
|
||||
"search",
|
||||
"ln",
|
||||
"ld",
|
||||
"load",
|
||||
"overview",
|
||||
"compare",
|
||||
"holdings",
|
||||
"screener",
|
||||
"gainers",
|
||||
"decliners",
|
||||
"active",
|
||||
"news",
|
||||
"candle",
|
||||
"pir",
|
||||
"fds",
|
||||
"weights",
|
||||
"summary",
|
||||
"compare",
|
||||
]
|
||||
|
||||
CHOICES += CHOICES_COMMANDS
|
||||
preset_options = [
|
||||
file.strip(".ini")
|
||||
for file in os.listdir(
|
||||
os.path.join(os.path.abspath(os.path.dirname(__file__)), "presets/")
|
||||
)
|
||||
CHOICES_MENUS = [
|
||||
"ta",
|
||||
"pred",
|
||||
"ca",
|
||||
"scr",
|
||||
"disc",
|
||||
]
|
||||
|
||||
CHOICES += CHOICES_COMMANDS + CHOICES_MENUS
|
||||
|
||||
def __init__(self, queue: List[str] = None):
|
||||
"""Constructor"""
|
||||
self.etf_parser = argparse.ArgumentParser(add_help=False, prog="etf")
|
||||
@@ -74,7 +95,6 @@ class ETFController:
|
||||
|
||||
if session and gtff.USE_PROMPT_TOOLKIT:
|
||||
choices: dict = {c: {} for c in self.CHOICES}
|
||||
choices["screener"]["--preset"] = {c: None for c in self.preset_options}
|
||||
self.completer = NestedCompleter.from_nested_dict(choices)
|
||||
|
||||
if queue:
|
||||
@@ -82,25 +102,39 @@ class ETFController:
|
||||
else:
|
||||
self.queue = list()
|
||||
|
||||
self.etf_name = ""
|
||||
self.etf_data = ""
|
||||
self.etf_holdings: List = list()
|
||||
|
||||
def print_help(self):
|
||||
"""Print help"""
|
||||
help_str = """
|
||||
StockAnalysis.com:
|
||||
search search ETFs matching name (i.e. BlackRock or Invesco)
|
||||
overview get overview of ETF symbol
|
||||
holdings get top holdings for ETF
|
||||
compare compare overview of multiple ETF
|
||||
screener screen etfs based on overview data
|
||||
Wall St. Journal:
|
||||
gainers show top gainers
|
||||
decliners show top decliners
|
||||
active show most active
|
||||
The Passive Investor:
|
||||
pir create ETF report of multiple tickers
|
||||
Finance Database:
|
||||
fds advanced ETF search based on category, name and/or description
|
||||
"""
|
||||
print(help_str)
|
||||
help_txt = f"""
|
||||
ln lookup by name [FinanceDatabase/StockAnalysis.com]
|
||||
ld lookup by description [FinanceDatabase]
|
||||
load load ETF data [Yfinance]
|
||||
{Style.DIM if not self.etf_name else ""}
|
||||
Symbol: {self.etf_name}{Style.DIM if len(self.etf_holdings)==0 else ""}
|
||||
Major holdings: {', '.join(self.etf_holdings)}
|
||||
|
||||
> ca comparison analysis, e.g.: get similar, historical, correlation, financials
|
||||
{Style.RESET_ALL}
|
||||
> disc discover ETFs, e.g.: gainers/decliners/active
|
||||
> scr screener ETFs, e.g.: overview/performance, using preset filters
|
||||
{Style.DIM if not self.etf_name else ""}
|
||||
overview get overview [StockAnalysis]
|
||||
holdings top company holdings [StockAnalysis]
|
||||
weights sector weights allocation [Yfinance]
|
||||
summary summary description of the ETF [Yfinance]
|
||||
candle view a candle chart for ETF
|
||||
news latest news of the company [News API]
|
||||
|
||||
pir create (multiple) passive investor excel report(s) [PassiveInvestor]
|
||||
compare compare multiple different ETFs [StockAnalysis]
|
||||
|
||||
> ta technical analysis, e.g.: ema, macd, rsi, adx, bbands, obv
|
||||
> pred prediction techniques, e.g.: regression, arima, rnn, lstm
|
||||
{Style.RESET_ALL}"""
|
||||
print(help_txt)
|
||||
|
||||
def switch(self, an_input: str):
|
||||
"""Process and dispatch input
|
||||
@@ -176,41 +210,182 @@ Finance Database:
|
||||
|
||||
def call_reset(self, _):
|
||||
"""Process reset command"""
|
||||
if self.etf_name:
|
||||
self.queue.insert(0, f"load {self.etf_name}")
|
||||
self.queue.insert(0, "etf")
|
||||
self.queue.insert(0, "reset")
|
||||
self.queue.insert(0, "quit")
|
||||
|
||||
@try_except
|
||||
def call_search(self, other_args: List[str]):
|
||||
"""Process search command"""
|
||||
def call_ln(self, other_args: List[str]):
|
||||
"""Process ln command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="search",
|
||||
description="Search all available etfs for matching input",
|
||||
prog="ln",
|
||||
description="Lookup by name [Source: FinanceDatabase/StockAnalysis.com]",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--etf",
|
||||
"-n",
|
||||
"--name",
|
||||
type=str,
|
||||
dest="search_str",
|
||||
dest="name",
|
||||
nargs="+",
|
||||
help="String to search for",
|
||||
help="Name to look for ETFs",
|
||||
required="-h" not in other_args,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--source",
|
||||
type=str,
|
||||
default="fd",
|
||||
dest="source",
|
||||
help="Name to search for, using either FinanceDatabase (fd) or StockAnalysis (sa) as source.",
|
||||
choices=["sa", "fd"],
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--limit",
|
||||
type=check_positive,
|
||||
dest="limit",
|
||||
help="Limit of ETFs to display",
|
||||
default=5,
|
||||
)
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-e")
|
||||
other_args.insert(0, "-n")
|
||||
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
search_string = " ".join(ns_parser.search_str)
|
||||
stockanalysis_view.view_search(
|
||||
to_match=search_string, export=ns_parser.export
|
||||
name_to_search = " ".join(ns_parser.name)
|
||||
if ns_parser.source == "fd":
|
||||
financedatabase_view.display_etf_by_name(
|
||||
name=name_to_search,
|
||||
limit=ns_parser.limit,
|
||||
export=ns_parser.export,
|
||||
)
|
||||
elif ns_parser.source == "sa":
|
||||
stockanalysis_view.display_etf_by_name(
|
||||
name=name_to_search,
|
||||
limit=ns_parser.limit,
|
||||
export=ns_parser.export,
|
||||
)
|
||||
else:
|
||||
print("Wrong source choice!\n")
|
||||
|
||||
@try_except
|
||||
def call_ld(self, other_args: List[str]):
|
||||
"""Process ld command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="ld",
|
||||
description="Lookup by description [Source: FinanceDatabase/StockAnalysis.com]",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--description",
|
||||
type=str,
|
||||
dest="description",
|
||||
nargs="+",
|
||||
help="Name to look for ETFs",
|
||||
required="-h" not in other_args,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--limit",
|
||||
type=check_positive,
|
||||
dest="limit",
|
||||
help="Limit of ETFs to display",
|
||||
default=5,
|
||||
)
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-d")
|
||||
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
description_to_search = " ".join(ns_parser.description)
|
||||
financedatabase_view.display_etf_by_description(
|
||||
description=description_to_search,
|
||||
limit=ns_parser.limit,
|
||||
export=ns_parser.export,
|
||||
)
|
||||
|
||||
@try_except
|
||||
def call_load(self, other_args: List[str]):
|
||||
"""Process load command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="load",
|
||||
description="Load ETF ticker to perform analysis on.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--ticker",
|
||||
action="store",
|
||||
dest="ticker",
|
||||
required="-h" not in other_args,
|
||||
help="ETF ticker",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--start",
|
||||
type=valid_date,
|
||||
default=(datetime.now() - timedelta(days=366)).strftime("%Y-%m-%d"),
|
||||
dest="start",
|
||||
help="The starting date (format YYYY-MM-DD) of the ETF",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--end",
|
||||
type=valid_date,
|
||||
default=datetime.now().strftime("%Y-%m-%d"),
|
||||
dest="end",
|
||||
help="The ending date (format YYYY-MM-DD) of the ETF",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--limit",
|
||||
type=check_positive,
|
||||
default=5,
|
||||
dest="limit",
|
||||
help="Limit of holdings to display",
|
||||
)
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-t")
|
||||
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
df_etf_candidate = yf.download(
|
||||
ns_parser.ticker,
|
||||
start=ns_parser.start,
|
||||
end=ns_parser.end,
|
||||
progress=False,
|
||||
)
|
||||
if df_etf_candidate.empty:
|
||||
print("ETF ticker provided does not exist!\n")
|
||||
return
|
||||
|
||||
df_etf_candidate.index.name = "date"
|
||||
|
||||
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:
|
||||
print("No company holdings found!\n")
|
||||
else:
|
||||
self.etf_holdings = holdings.index[: ns_parser.limit].tolist()
|
||||
print(f"Top company holdings found: {', '.join(self.etf_holdings)}\n")
|
||||
|
||||
print("")
|
||||
|
||||
@try_except
|
||||
def call_overview(self, other_args: List[str]):
|
||||
"""Process overview command"""
|
||||
@@ -220,25 +395,13 @@ Finance Database:
|
||||
prog="overview",
|
||||
description="Get overview data for selected etf",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--etf",
|
||||
type=str,
|
||||
dest="name",
|
||||
help="Symbol to look for",
|
||||
required="-h" not in other_args,
|
||||
)
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-e")
|
||||
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
|
||||
if ns_parser:
|
||||
stockanalysis_view.view_overview(
|
||||
symbol=ns_parser.name, export=ns_parser.export
|
||||
symbol=self.etf_name, export=ns_parser.export
|
||||
)
|
||||
|
||||
@try_except
|
||||
@@ -248,15 +411,7 @@ Finance Database:
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="holdings",
|
||||
description="Look at ETF holdings",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--etf",
|
||||
type=str,
|
||||
dest="name",
|
||||
help="ETF to get holdings for",
|
||||
required="-h" not in other_args,
|
||||
description="Look at ETF company holdings",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
@@ -264,21 +419,319 @@ Finance Database:
|
||||
type=int,
|
||||
dest="limit",
|
||||
help="Number of holdings to get",
|
||||
default=20,
|
||||
default=10,
|
||||
)
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-e")
|
||||
other_args.insert(0, "-l")
|
||||
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
stockanalysis_view.view_holdings(
|
||||
symbol=ns_parser.name,
|
||||
symbol=self.etf_name,
|
||||
num_to_show=ns_parser.limit,
|
||||
export=ns_parser.export,
|
||||
)
|
||||
|
||||
@try_except
|
||||
def call_news(self, other_args: List[str]):
|
||||
"""Process news command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="news",
|
||||
description="""
|
||||
Prints latest news about ETF, including date, title and web link. [Source: News API]
|
||||
""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--limit",
|
||||
action="store",
|
||||
dest="limit",
|
||||
type=check_positive,
|
||||
default=5,
|
||||
help="Limit of latest news being printed.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--date",
|
||||
action="store",
|
||||
dest="n_start_date",
|
||||
type=valid_date,
|
||||
default=datetime.now() - timedelta(days=7),
|
||||
help="The starting date (format YYYY-MM-DD) to search articles from",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--oldest",
|
||||
action="store_false",
|
||||
dest="n_oldest",
|
||||
default=True,
|
||||
help="Show oldest articles first",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--sources",
|
||||
default=[],
|
||||
nargs="+",
|
||||
help="Show news only from the sources specified (e.g bbc yahoo.com)",
|
||||
)
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-l")
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
if self.etf_name:
|
||||
sources = ns_parser.sources
|
||||
for idx, source in enumerate(sources):
|
||||
if source.find(".") == -1:
|
||||
sources[idx] += ".com"
|
||||
|
||||
d_stock = yf.Ticker(self.etf_name).info
|
||||
|
||||
newsapi_view.news(
|
||||
term=d_stock["shortName"].replace(" ", "+")
|
||||
if "shortName" in d_stock
|
||||
else self.etf_name,
|
||||
num=ns_parser.limit,
|
||||
s_from=ns_parser.n_start_date.strftime("%Y-%m-%d"),
|
||||
show_newest=ns_parser.n_oldest,
|
||||
sources=",".join(sources),
|
||||
)
|
||||
else:
|
||||
print("Use 'load <ticker>' prior to this command!", "\n")
|
||||
|
||||
@try_except
|
||||
def call_candle(self, other_args: List[str]):
|
||||
"""Process candle command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="candle",
|
||||
description="Shows historic data for an ETF",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES
|
||||
)
|
||||
if ns_parser:
|
||||
if self.etf_name:
|
||||
data = stocks_helper.process_candle(self.etf_data)
|
||||
df_etf = stocks_helper.find_trendline(data, "OC_High", "high")
|
||||
df_etf = stocks_helper.find_trendline(data, "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_etf.columns:
|
||||
ap0.append(
|
||||
mpf.make_addplot(df_etf["OC_High_trend"], color="g"),
|
||||
)
|
||||
|
||||
if "OC_Low_trend" in df_etf.columns:
|
||||
ap0.append(
|
||||
mpf.make_addplot(df_etf["OC_Low_trend"], color="b"),
|
||||
)
|
||||
|
||||
if gtff.USE_ION:
|
||||
plt.ion()
|
||||
|
||||
mpf.plot(
|
||||
df_etf,
|
||||
type="candle",
|
||||
mav=(20, 50),
|
||||
volume=True,
|
||||
title=f"\nETF: {self.etf_name}",
|
||||
addplot=ap0,
|
||||
xrotation=10,
|
||||
style=s,
|
||||
figratio=(10, 7),
|
||||
figscale=1.10,
|
||||
figsize=(plot_autoscale()),
|
||||
update_width_config=dict(
|
||||
candle_linewidth=1.0, candle_width=0.8, volume_linewidth=1.0
|
||||
),
|
||||
)
|
||||
print("")
|
||||
|
||||
export_data(
|
||||
ns_parser.export,
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "candle"),
|
||||
f"{self.etf_name}",
|
||||
self.etf_data,
|
||||
)
|
||||
|
||||
else:
|
||||
print("No ticker loaded. First use `load {ticker}`\n")
|
||||
|
||||
@try_except
|
||||
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",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--etfs",
|
||||
type=str,
|
||||
dest="names",
|
||||
help="Symbols to create a report for (e.g. ARKW,ARKQ)",
|
||||
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=os.path.dirname(os.path.abspath(__file__)).replace(
|
||||
"gamestonk_terminal", "exports"
|
||||
),
|
||||
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 = parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
if self.etf_name:
|
||||
create_ETF_report(
|
||||
ns_parser.names if ns_parser.names else [self.etf_name],
|
||||
filename=ns_parser.filename,
|
||||
folder=ns_parser.folder,
|
||||
)
|
||||
print(
|
||||
f"Created ETF report as {ns_parser.filename} in folder {ns_parser.folder} \n"
|
||||
)
|
||||
|
||||
@try_except
|
||||
def call_weights(self, other_args: List[str]):
|
||||
"""Process weights command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="weights",
|
||||
description="Look at ETF sector holdings",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-m",
|
||||
"--min",
|
||||
type=check_non_negative_float,
|
||||
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",
|
||||
)
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-l")
|
||||
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES
|
||||
)
|
||||
if ns_parser:
|
||||
yfinance_view.display_etf_weightings(
|
||||
name=self.etf_name,
|
||||
raw=ns_parser.raw,
|
||||
min_pct_to_display=ns_parser.min,
|
||||
export=ns_parser.export,
|
||||
)
|
||||
|
||||
@try_except
|
||||
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 = parse_known_args_and_warn(
|
||||
parser,
|
||||
other_args,
|
||||
)
|
||||
if ns_parser:
|
||||
yfinance_view.display_etf_description(
|
||||
name=self.etf_name,
|
||||
)
|
||||
|
||||
@try_except
|
||||
def call_ta(self, _):
|
||||
"""Process ta command"""
|
||||
if self.etf_name:
|
||||
self.queue = ta_controller.menu(
|
||||
self.etf_name, self.etf_data.index[0], self.etf_data, self.queue
|
||||
)
|
||||
else:
|
||||
print("Use 'load <ticker>' prior to this command!", "\n")
|
||||
|
||||
@try_except
|
||||
def call_pred(self, _):
|
||||
"""Process pred command"""
|
||||
if gtff.ENABLE_PREDICT:
|
||||
if self.etf_name:
|
||||
try:
|
||||
from gamestonk_terminal.stocks.prediction_techniques import (
|
||||
pred_controller,
|
||||
)
|
||||
|
||||
self.queue = pred_controller.menu(
|
||||
self.etf_name,
|
||||
self.etf_data.index[0],
|
||||
"1440min",
|
||||
self.etf_data,
|
||||
self.queue,
|
||||
)
|
||||
except ModuleNotFoundError as e:
|
||||
print(
|
||||
"One of the optional packages seems to be missing: ",
|
||||
e,
|
||||
"\n",
|
||||
)
|
||||
else:
|
||||
print("Use 'load <ticker>' prior to this command!", "\n")
|
||||
else:
|
||||
print(
|
||||
"Predict is disabled. Check ENABLE_PREDICT flag on feature_flags.py",
|
||||
"\n",
|
||||
)
|
||||
|
||||
@try_except
|
||||
def call_ca(self, _):
|
||||
"""Process ca command"""
|
||||
if len(self.etf_holdings) > 0:
|
||||
self.queue = ca_controller.menu(
|
||||
self.etf_holdings, self.queue, from_submenu=True
|
||||
)
|
||||
|
||||
@try_except
|
||||
def call_scr(self, _):
|
||||
"""Process scr command"""
|
||||
self.queue = screener_controller.menu(self.queue)
|
||||
|
||||
@try_except
|
||||
def call_disc(self, _):
|
||||
"""Process disc command"""
|
||||
self.queue = disc_controller.menu(self.queue)
|
||||
|
||||
@try_except
|
||||
def call_compare(self, other_args):
|
||||
"""Process compare command"""
|
||||
@@ -286,7 +739,7 @@ Finance Database:
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="compare",
|
||||
description="Compare selected ETFs",
|
||||
description="Compare selected ETFs [Source: StockAnalysis]",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
@@ -307,204 +760,6 @@ Finance Database:
|
||||
etf_list = ns_parser.names.upper().split(",")
|
||||
stockanalysis_view.view_comparisons(etf_list, export=ns_parser.export)
|
||||
|
||||
@try_except
|
||||
def call_screener(self, other_args):
|
||||
"""Process screener command"""
|
||||
# TODO: Change presets to use view/set like in stocks/options
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="screener",
|
||||
add_help=False,
|
||||
description="Screens ETFS from a personal scraping github repository. Data scraped from stockanalysis.com",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--limit",
|
||||
type=int,
|
||||
help="Number of etfs to show",
|
||||
dest="limit",
|
||||
default=20,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--preset",
|
||||
choices=self.preset_options,
|
||||
default="etf_config",
|
||||
help="Preset to use",
|
||||
dest="preset",
|
||||
)
|
||||
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
|
||||
screener_view.view_screener(
|
||||
num_to_show=ns_parser.limit,
|
||||
preset=ns_parser.preset,
|
||||
export=ns_parser.export,
|
||||
)
|
||||
|
||||
@try_except
|
||||
def call_gainers(self, other_args):
|
||||
"""Process gainers command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="gainers",
|
||||
description="Displays top ETF/Mutual fund gainers from wsj.com/market-data",
|
||||
add_help=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l", "--limit", help="Number to show", type=int, default=25, dest="limit"
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
wsj_view.show_top_mover("gainers", ns_parser.limit, ns_parser.export)
|
||||
|
||||
def call_decliners(self, other_args):
|
||||
"""Process decliners command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="decliners",
|
||||
description="Displays top ETF/Mutual fund decliners from wsj.com/market-data",
|
||||
add_help=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l", "--limit", help="Number to show", type=int, default=25, dest="limit"
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
wsj_view.show_top_mover("decliners", ns_parser.limit, ns_parser.export)
|
||||
|
||||
def call_active(self, other_args):
|
||||
"""Process gainers command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="active",
|
||||
description="Displays most active ETF/Mutual funds from wsj.com/market-data",
|
||||
add_help=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l", "--limit", help="Number to show", type=int, default=25, dest="limit"
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
wsj_view.show_top_mover("active", ns_parser.limit, ns_parser.export)
|
||||
|
||||
@try_except
|
||||
def call_pir(self, other_args):
|
||||
"""Process pir command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="pir",
|
||||
description="Create a ETF Report of the selected ETFs",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--etfs",
|
||||
type=str,
|
||||
dest="names",
|
||||
help="Symbols to create a report for",
|
||||
required="-h" not in other_args,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--filename",
|
||||
default=f"ETF_report_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
|
||||
dest="filename",
|
||||
help="Filename of the ETF report",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--folder",
|
||||
default=os.path.dirname(os.path.abspath(__file__)).replace(
|
||||
"gamestonk_terminal", "exports"
|
||||
),
|
||||
dest="folder",
|
||||
help="Folder where the ETF report will be saved",
|
||||
)
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-e")
|
||||
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
etf_list = ns_parser.names.upper().split(",")
|
||||
create_ETF_report(
|
||||
etf_list, filename=ns_parser.filename, folder=ns_parser.folder
|
||||
)
|
||||
print(
|
||||
f"Created ETF report as {ns_parser.filename} in folder {ns_parser.folder} \n"
|
||||
)
|
||||
|
||||
@try_except
|
||||
def call_fds(self, other_args):
|
||||
"""Process fds command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Display a selection of ETFs based on category, name and/or description filtered by total "
|
||||
"assets. Returns the top ETFs when no argument is given. [Source: Finance Database]",
|
||||
add_help=False,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--category",
|
||||
default=None,
|
||||
nargs="+",
|
||||
dest="category",
|
||||
help="Specify the ETF selection based on a category",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--name",
|
||||
default=None,
|
||||
nargs="+",
|
||||
dest="name",
|
||||
help="Specify the ETF selection based on the name",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--description",
|
||||
default=None,
|
||||
nargs="+",
|
||||
dest="description",
|
||||
help="Specify the ETF selection based on the description (not shown in table)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-ie",
|
||||
"--include_exchanges",
|
||||
action="store_false",
|
||||
help="When used, data from different exchanges is also included. This leads to a much larger "
|
||||
"pool of data due to the same ETF being listed on multiple exchanges",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--limit",
|
||||
default=10,
|
||||
type=int,
|
||||
dest="limit",
|
||||
help="Enter the number of ETFs you wish to see in the Tabulate window",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--options",
|
||||
action="store_true",
|
||||
help="Obtain the available categories",
|
||||
)
|
||||
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
financedatabase_view.show_etfs(
|
||||
category=ns_parser.category,
|
||||
name=ns_parser.name,
|
||||
description=ns_parser.description,
|
||||
include_exchanges=ns_parser.include_exchanges,
|
||||
amount=ns_parser.limit,
|
||||
options=ns_parser.options,
|
||||
)
|
||||
|
||||
|
||||
def menu(queue: List[str] = None):
|
||||
etf_controller = ETFController(queue)
|
||||
|
||||
73
gamestonk_terminal/etf/financedatabase_model.py
Normal file
73
gamestonk_terminal/etf/financedatabase_model.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""Finance Database model"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
from typing import Dict, List
|
||||
import financedatabase as fd
|
||||
|
||||
|
||||
def get_etfs_by_name(name: str) -> Dict:
|
||||
"""Return a selection of ETFs based on name filtered by total assets. [Source: Finance Database]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: str
|
||||
Search by name to find ETFs matching the criteria.
|
||||
|
||||
Returns
|
||||
----------
|
||||
data : pd.DataFrame
|
||||
Dataframe with ETFs that match a certain name
|
||||
"""
|
||||
data = fd.select_etfs()
|
||||
data = fd.search_products(data, query=name, search="long_name")
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_etfs_by_description(description: str) -> Dict:
|
||||
"""Return a selection of ETFs based on description filtered by total assets. [Source: Finance Database]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
description: str
|
||||
Search by description to find ETFs matching the criteria.
|
||||
|
||||
Returns
|
||||
----------
|
||||
data : pd.DataFrame
|
||||
Dataframe with ETFs that match a certain description
|
||||
"""
|
||||
data = fd.select_etfs()
|
||||
data = fd.search_products(data, query=description, search="summary")
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_etfs_by_category(category: str) -> Dict:
|
||||
"""Return a selection of ETFs based on category filtered by total assets. [Source: Finance Database]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
category: str
|
||||
Search by category to find ETFs matching the criteria.
|
||||
|
||||
Returns
|
||||
----------
|
||||
data : pd.DataFrame
|
||||
Dataframe with ETFs that match a certain description
|
||||
"""
|
||||
data = fd.select_etfs(category=category)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_etfs_categories() -> List[str]:
|
||||
"""Return a selection of ETF categories. [Source: Finance Database]
|
||||
|
||||
Returns
|
||||
----------
|
||||
List[str]
|
||||
ETF categories
|
||||
"""
|
||||
|
||||
return fd.show_options("etfs")
|
||||
@@ -1,57 +1,35 @@
|
||||
"""Finance Database view"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import financedatabase as fd
|
||||
import os
|
||||
import pandas as pd
|
||||
from tabulate import tabulate
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.etf import financedatabase_model
|
||||
from gamestonk_terminal.helper_funcs import export_data
|
||||
|
||||
|
||||
def show_etfs(
|
||||
category: str,
|
||||
def display_etf_by_name(
|
||||
name: str,
|
||||
description: str,
|
||||
include_exchanges: bool,
|
||||
amount: int,
|
||||
options: str,
|
||||
limit: int,
|
||||
export: str = "",
|
||||
):
|
||||
"""
|
||||
Display a selection of ETFs based on category, name and/or description filtered by total assets.
|
||||
Returns the top ETFs when no argument is given. [Source: Finance Database]
|
||||
"""Display a selection of ETFs based on name filtered by total assets. [Source: Finance Database]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
category: str
|
||||
Search by category to find ETFs matching the criteria.
|
||||
name: str
|
||||
Search by name to find ETFs matching the criteria.
|
||||
description: str
|
||||
Search by description to find ETFs matching the criteria.
|
||||
include_exchanges: bool
|
||||
When you wish to include different exchanges use this boolean.
|
||||
amount : int
|
||||
Number of ETFs to display, default is 10.
|
||||
options : str
|
||||
Show the category options.
|
||||
limit: int
|
||||
Limit of ETFs to display
|
||||
export: str
|
||||
Type of format to export data
|
||||
"""
|
||||
if options:
|
||||
for option in fd.show_options("etfs"):
|
||||
print(option)
|
||||
data = financedatabase_model.get_etfs_by_name(name)
|
||||
if not data:
|
||||
print("No data was found with that name\n")
|
||||
return
|
||||
|
||||
if category is not None:
|
||||
data = fd.select_etfs(
|
||||
category=" ".join(category).title(), exclude_exchanges=include_exchanges
|
||||
)
|
||||
else:
|
||||
data = fd.select_etfs(category=category, exclude_exchanges=include_exchanges)
|
||||
|
||||
if name is not None:
|
||||
print(name)
|
||||
data = fd.search_products(data, query=" ".join(name), search="long_name")
|
||||
if description is not None:
|
||||
data = fd.search_products(data, query=" ".join(description), search="summary")
|
||||
|
||||
tabulate_data = pd.DataFrame(data).T[
|
||||
["long_name", "family", "category", "total_assets"]
|
||||
]
|
||||
@@ -61,7 +39,7 @@ def show_etfs(
|
||||
if gtff.USE_TABULATE_DF:
|
||||
print(
|
||||
tabulate(
|
||||
tabulate_data_sorted.iloc[:amount],
|
||||
tabulate_data_sorted.iloc[:limit],
|
||||
showindex=True,
|
||||
headers=["Name", "Family", "Category", "Total Assets [M]"],
|
||||
floatfmt=".2f",
|
||||
@@ -70,4 +48,99 @@ def show_etfs(
|
||||
"\n",
|
||||
)
|
||||
else:
|
||||
print(tabulate_data_sorted.iloc[:amount].to_string(), "\n")
|
||||
print(tabulate_data_sorted.iloc[:limit].to_string(), "\n")
|
||||
|
||||
export_data(export, os.path.dirname(os.path.abspath(__file__)), "ln_fd", data)
|
||||
|
||||
|
||||
def display_etf_by_description(
|
||||
description: str,
|
||||
limit: int,
|
||||
export: str = "",
|
||||
):
|
||||
"""Display a selection of ETFs based on description filtered by total assets. [Source: Finance Database]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
description: str
|
||||
Search by description to find ETFs matching the criteria.
|
||||
limit: int
|
||||
Limit of ETFs to display
|
||||
export: str
|
||||
Type of format to export data
|
||||
"""
|
||||
data = financedatabase_model.get_etfs_by_description(description)
|
||||
if not data:
|
||||
print("No data was found with that description\n")
|
||||
return
|
||||
|
||||
tabulate_data = pd.DataFrame(data).T[
|
||||
["long_name", "family", "category", "total_assets"]
|
||||
]
|
||||
tabulate_data_sorted = tabulate_data.sort_values(by="total_assets", ascending=False)
|
||||
tabulate_data_sorted["total_assets"] = tabulate_data_sorted["total_assets"] / 1e6
|
||||
|
||||
if gtff.USE_TABULATE_DF:
|
||||
print(
|
||||
tabulate(
|
||||
tabulate_data_sorted.iloc[:limit],
|
||||
showindex=True,
|
||||
headers=["Name", "Family", "Category", "Total Assets [M]"],
|
||||
floatfmt=".2f",
|
||||
tablefmt="fancy_grid",
|
||||
),
|
||||
"\n",
|
||||
)
|
||||
else:
|
||||
print(tabulate_data_sorted.iloc[:limit].to_string(), "\n")
|
||||
|
||||
export_data(export, os.path.dirname(os.path.abspath(__file__)), "ld", data)
|
||||
|
||||
|
||||
def display_etf_by_category(
|
||||
category: str,
|
||||
limit: int,
|
||||
export: str = "",
|
||||
):
|
||||
"""Display a selection of ETFs based on a category filtered by total assets. [Source: Finance Database]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
description: str
|
||||
Search by description to find ETFs matching the criteria.
|
||||
limit: int
|
||||
Limit of ETFs to display
|
||||
export: str
|
||||
Type of format to export data
|
||||
"""
|
||||
data = financedatabase_model.get_etfs_by_category(category)
|
||||
if not data:
|
||||
print("No data was found on that category\n")
|
||||
return
|
||||
|
||||
tabulate_data = pd.DataFrame(data).T[
|
||||
["long_name", "family", "category", "total_assets"]
|
||||
]
|
||||
tabulate_data_sorted = tabulate_data.sort_values(by="total_assets", ascending=False)
|
||||
tabulate_data_sorted["total_assets"] = tabulate_data_sorted["total_assets"] / 1e6
|
||||
|
||||
if gtff.USE_TABULATE_DF:
|
||||
print(
|
||||
tabulate(
|
||||
tabulate_data_sorted.iloc[:limit],
|
||||
showindex=True,
|
||||
headers=["Name", "Family", "Category", "Total Assets [M]"],
|
||||
floatfmt=".2f",
|
||||
tablefmt="fancy_grid",
|
||||
),
|
||||
"\n",
|
||||
)
|
||||
else:
|
||||
print(tabulate_data_sorted.iloc[:limit].to_string(), "\n")
|
||||
|
||||
export_data(
|
||||
export,
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "screener"),
|
||||
"sbc",
|
||||
data,
|
||||
)
|
||||
|
||||
983
gamestonk_terminal/etf/prediction_techniques/pred_controller.py
Normal file
983
gamestonk_terminal/etf/prediction_techniques/pred_controller.py
Normal file
@@ -0,0 +1,983 @@
|
||||
""" Prediction Controller """
|
||||
__docformat__ = "numpy"
|
||||
# pylint:disable=too-many-lines
|
||||
|
||||
import argparse
|
||||
import difflib
|
||||
from typing import List, Union
|
||||
from datetime import datetime, timedelta
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from prompt_toolkit.completion import NestedCompleter
|
||||
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.helper_funcs import (
|
||||
get_flair,
|
||||
parse_known_args_and_warn,
|
||||
check_positive,
|
||||
valid_date,
|
||||
get_next_stock_market_days,
|
||||
EXPORT_ONLY_FIGURES_ALLOWED,
|
||||
try_except,
|
||||
system_clear,
|
||||
)
|
||||
from gamestonk_terminal.menu import session
|
||||
from gamestonk_terminal.common.prediction_techniques import (
|
||||
arima_view,
|
||||
arima_model,
|
||||
ets_model,
|
||||
mc_model,
|
||||
ets_view,
|
||||
knn_view,
|
||||
neural_networks_view,
|
||||
regression_view,
|
||||
pred_helper,
|
||||
mc_view,
|
||||
)
|
||||
from gamestonk_terminal.stocks import stocks_helper
|
||||
|
||||
|
||||
class PredictionTechniquesController:
|
||||
"""Prediction Techniques Controller class"""
|
||||
|
||||
# Command choices
|
||||
CHOICES = [
|
||||
"cls",
|
||||
"home",
|
||||
"h",
|
||||
"?",
|
||||
"help",
|
||||
"q",
|
||||
"quit",
|
||||
"..",
|
||||
"exit",
|
||||
"r",
|
||||
"reset",
|
||||
]
|
||||
|
||||
CHOICES_COMMANDS = [
|
||||
"load",
|
||||
"pick",
|
||||
"ets",
|
||||
"knn",
|
||||
"regression",
|
||||
"arima",
|
||||
"mlp",
|
||||
"rnn",
|
||||
"lstm",
|
||||
"conv1d",
|
||||
"mc",
|
||||
]
|
||||
CHOICES += CHOICES_COMMANDS
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ticker: str,
|
||||
start: datetime,
|
||||
interval: str,
|
||||
stock: pd.DataFrame,
|
||||
queue: List[str] = None,
|
||||
):
|
||||
"""Constructor"""
|
||||
stock["Returns"] = stock["Adj Close"].pct_change()
|
||||
stock["LogRet"] = np.log(stock["Adj Close"]) - np.log(
|
||||
stock["Adj Close"].shift(1)
|
||||
)
|
||||
stock = stock.rename(columns={"Adj Close": "AdjClose"})
|
||||
stock = stock.dropna()
|
||||
|
||||
self.stock = stock
|
||||
self.ticker = ticker
|
||||
self.start = start
|
||||
self.interval = interval
|
||||
self.target = "AdjClose"
|
||||
self.pred_parser = argparse.ArgumentParser(add_help=False, prog="pred")
|
||||
self.pred_parser.add_argument(
|
||||
"cmd",
|
||||
choices=self.CHOICES,
|
||||
)
|
||||
self.completer: Union[None, NestedCompleter] = None
|
||||
if session and gtff.USE_PROMPT_TOOLKIT:
|
||||
choices: dict = {c: {} for c in self.CHOICES}
|
||||
choices["load"]["-r"] = {c: {} for c in stocks_helper.INTERVALS}
|
||||
choices["pick"] = {c: {} for c in self.stock.columns}
|
||||
choices["ets"]["-t"] = {c: {} for c in ets_model.TRENDS}
|
||||
choices["ets"]["-s"] = {c: {} for c in ets_model.SEASONS}
|
||||
choices["arima"]["-i"] = {c: {} for c in arima_model.ICS}
|
||||
choices["mc"]["--dist"] = {c: {} for c in mc_model.DISTRIBUTIONS}
|
||||
self.completer = NestedCompleter.from_nested_dict(choices)
|
||||
|
||||
if queue:
|
||||
self.queue = queue
|
||||
else:
|
||||
self.queue = list()
|
||||
|
||||
def print_help(self):
|
||||
"""Print help"""
|
||||
s_intraday = (f"Intraday {self.interval}", "Daily")[self.interval == "1440min"]
|
||||
if self.start:
|
||||
stock_info = f"{s_intraday} Stock: {self.ticker} (from {self.start.strftime('%Y-%m-%d')})"
|
||||
else:
|
||||
stock_info = "{s_intraday} Stock: {self.ticker}"
|
||||
|
||||
help_string = f"""
|
||||
Prediction Techniques Menu:
|
||||
|
||||
load load new ticker
|
||||
pick pick new target variable
|
||||
|
||||
Ticker Loaded: {stock_info}
|
||||
Target Column: {self.target}
|
||||
|
||||
Models:
|
||||
ets exponential smoothing (e.g. Holt-Winters)
|
||||
knn k-Nearest Neighbors
|
||||
regression polynomial regression
|
||||
arima autoregressive integrated moving average
|
||||
mlp MultiLayer Perceptron
|
||||
rnn Recurrent Neural Network
|
||||
lstm Long-Short Term Memory
|
||||
conv1d 1D Convolutional Neural Network
|
||||
mc Monte-Carlo simulations
|
||||
"""
|
||||
print(help_string)
|
||||
|
||||
def switch(self, an_input: str):
|
||||
"""Process and dispatch input
|
||||
Parameters
|
||||
-------
|
||||
an_input : str
|
||||
string with input arguments
|
||||
Returns
|
||||
-------
|
||||
List[str]
|
||||
List of commands in the queue to execute
|
||||
"""
|
||||
|
||||
# Empty command
|
||||
if not an_input:
|
||||
print("")
|
||||
return self.queue
|
||||
|
||||
# Navigation slash is being used
|
||||
if "/" in an_input:
|
||||
actions = an_input.split("/")
|
||||
|
||||
# Absolute path is specified
|
||||
if not actions[0]:
|
||||
an_input = "home"
|
||||
# Relative path so execute first instruction
|
||||
else:
|
||||
an_input = actions[0]
|
||||
|
||||
# Add all instructions to the queue
|
||||
for cmd in actions[1:][::-1]:
|
||||
if cmd:
|
||||
self.queue.insert(0, cmd)
|
||||
|
||||
(known_args, other_args) = self.pred_parser.parse_known_args(an_input.split())
|
||||
|
||||
# Redirect commands to their correct functions
|
||||
if known_args.cmd:
|
||||
if known_args.cmd in ("..", "q"):
|
||||
known_args.cmd = "quit"
|
||||
elif known_args.cmd in ("?", "h"):
|
||||
known_args.cmd = "help"
|
||||
elif known_args.cmd == "r":
|
||||
known_args.cmd = "reset"
|
||||
|
||||
getattr(
|
||||
self,
|
||||
"call_" + known_args.cmd,
|
||||
lambda _: "Command not recognized!",
|
||||
)(other_args)
|
||||
|
||||
return self.queue
|
||||
|
||||
def call_cls(self, _):
|
||||
"""Process cls command"""
|
||||
system_clear()
|
||||
|
||||
def call_home(self, _):
|
||||
"""Process home command"""
|
||||
self.queue.insert(0, "quit")
|
||||
self.queue.insert(0, "quit")
|
||||
|
||||
def call_help(self, _):
|
||||
"""Process help command"""
|
||||
self.print_help()
|
||||
|
||||
def call_quit(self, _):
|
||||
"""Process quit menu command"""
|
||||
print("")
|
||||
self.queue.insert(0, "quit")
|
||||
|
||||
def call_exit(self, _):
|
||||
"""Process exit terminal command"""
|
||||
self.queue.insert(0, "quit")
|
||||
self.queue.insert(0, "quit")
|
||||
self.queue.insert(0, "quit")
|
||||
|
||||
def call_reset(self, _):
|
||||
"""Process reset command"""
|
||||
self.queue.insert(0, "pred")
|
||||
if self.ticker:
|
||||
self.queue.insert(0, f"load {self.ticker}")
|
||||
self.queue.insert(0, "stocks")
|
||||
self.queue.insert(0, "reset")
|
||||
self.queue.insert(0, "quit")
|
||||
self.queue.insert(0, "quit")
|
||||
|
||||
def call_load(self, other_args: List[str]):
|
||||
"""Process load command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="load",
|
||||
description="Load stock ticker to perform analysis on. When the data source is 'yf', an Indian ticker can be"
|
||||
" loaded by using '.NS' at the end, e.g. 'SBIN.NS'. See available market in"
|
||||
" https://help.yahoo.com/kb/exchanges-data-providers-yahoo-finance-sln2310.html.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--ticker",
|
||||
action="store",
|
||||
dest="ticker",
|
||||
required="-h" not in other_args,
|
||||
help="Stock ticker",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--start",
|
||||
type=valid_date,
|
||||
default=(datetime.now() - timedelta(days=366)).strftime("%Y-%m-%d"),
|
||||
dest="start",
|
||||
help="The starting date (format YYYY-MM-DD) of the stock",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--end",
|
||||
type=valid_date,
|
||||
default=datetime.now().strftime("%Y-%m-%d"),
|
||||
dest="end",
|
||||
help="The ending date (format YYYY-MM-DD) of the stock",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--interval",
|
||||
action="store",
|
||||
dest="interval",
|
||||
type=int,
|
||||
default=1440,
|
||||
choices=stocks_helper.INTERVALS,
|
||||
help="Intraday stock minutes",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--source",
|
||||
action="store",
|
||||
dest="source",
|
||||
choices=stocks_helper.SOURCES,
|
||||
default="yf",
|
||||
help="Source of historical data.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--prepost",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="prepost",
|
||||
help="Pre/After market hours. Only works for 'yf' source, and intraday data",
|
||||
)
|
||||
|
||||
# For the case where a user uses: 'load BB'
|
||||
if other_args and "-t" not in other_args and "-h" not in other_args:
|
||||
other_args.insert(0, "-t")
|
||||
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, EXPORT_ONLY_FIGURES_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
df_stock_candidate = stocks_helper.load(
|
||||
ns_parser.ticker,
|
||||
ns_parser.start,
|
||||
ns_parser.interval,
|
||||
ns_parser.end,
|
||||
ns_parser.prepost,
|
||||
ns_parser.source,
|
||||
)
|
||||
|
||||
if not df_stock_candidate.empty:
|
||||
self.stock = df_stock_candidate
|
||||
if "." in ns_parser.ticker:
|
||||
self.ticker = ns_parser.ticker.upper().split(".")[0]
|
||||
else:
|
||||
self.ticker = ns_parser.ticker.upper()
|
||||
|
||||
self.start = ns_parser.start
|
||||
self.interval = str(ns_parser.interval) + "min"
|
||||
|
||||
self.stock["Returns"] = self.stock["Adj Close"].pct_change()
|
||||
self.stock["LogRet"] = np.log(self.stock["Adj Close"]) - np.log(
|
||||
self.stock["Adj Close"].shift(1)
|
||||
)
|
||||
self.stock = self.stock.rename(columns={"Adj Close": "AdjClose"})
|
||||
self.stock = self.stock.dropna()
|
||||
|
||||
@try_except
|
||||
def call_pick(self, other_args: List[str]):
|
||||
"""Process pick command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
add_help=False,
|
||||
prog="pick",
|
||||
description="""
|
||||
Change target variable
|
||||
""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--target",
|
||||
dest="target",
|
||||
choices=list(self.stock.columns),
|
||||
help="Select variable to analyze",
|
||||
)
|
||||
if other_args and "-t" not in other_args and "-h" not in other_args:
|
||||
other_args.insert(0, "-t")
|
||||
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, EXPORT_ONLY_FIGURES_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
self.target = ns_parser.target
|
||||
print("")
|
||||
|
||||
@try_except
|
||||
def call_ets(self, other_args: List[str]):
|
||||
"""Process ets command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="ets",
|
||||
description="""
|
||||
Exponential Smoothing, see https://otexts.com/fpp2/taxonomy.html
|
||||
|
||||
Trend='N', Seasonal='N': Simple Exponential Smoothing
|
||||
Trend='N', Seasonal='A': Exponential Smoothing
|
||||
Trend='N', Seasonal='M': Exponential Smoothing
|
||||
Trend='A', Seasonal='N': Holt’s linear method
|
||||
Trend='A', Seasonal='A': Additive Holt-Winters’ method
|
||||
Trend='A', Seasonal='M': Multiplicative Holt-Winters’ method
|
||||
Trend='Ad', Seasonal='N': Additive damped trend method
|
||||
Trend='Ad', Seasonal='A': Exponential Smoothing
|
||||
Trend='Ad', Seasonal='M': Holt-Winters’ damped method
|
||||
Trend component: N: None, A: Additive, Ad: Additive Damped
|
||||
Seasonality component: N: None, A: Additive, M: Multiplicative
|
||||
""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--days",
|
||||
action="store",
|
||||
dest="n_days",
|
||||
type=check_positive,
|
||||
default=5,
|
||||
help="prediction days.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--trend",
|
||||
action="store",
|
||||
dest="trend",
|
||||
choices=ets_model.TRENDS,
|
||||
default="N",
|
||||
help="Trend component: N: None, A: Additive, Ad: Additive Damped.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--seasonal",
|
||||
action="store",
|
||||
dest="seasonal",
|
||||
choices=ets_model.SEASONS,
|
||||
default="N",
|
||||
help="Seasonality component: N: None, A: Additive, M: Multiplicative.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--periods",
|
||||
action="store",
|
||||
dest="seasonal_periods",
|
||||
type=check_positive,
|
||||
default=5,
|
||||
help="Seasonal periods.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--end",
|
||||
action="store",
|
||||
type=valid_date,
|
||||
dest="s_end_date",
|
||||
default=None,
|
||||
help="The end date (format YYYY-MM-DD) to select - Backtesting",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
|
||||
if ns_parser.s_end_date:
|
||||
|
||||
if ns_parser.s_end_date < self.stock.index[0]:
|
||||
print(
|
||||
"Backtesting not allowed, since End Date is older than Start Date of historical data\n"
|
||||
)
|
||||
|
||||
if ns_parser.s_end_date < get_next_stock_market_days(
|
||||
last_stock_day=self.stock.index[0],
|
||||
n_next_days=5 + ns_parser.n_days,
|
||||
)[-1]:
|
||||
print(
|
||||
"Backtesting not allowed, since End Date is too close to Start Date to train model\n"
|
||||
)
|
||||
|
||||
ets_view.display_exponential_smoothing(
|
||||
ticker=self.ticker,
|
||||
values=self.stock[self.target],
|
||||
n_predict=ns_parser.n_days,
|
||||
trend=ns_parser.trend,
|
||||
seasonal=ns_parser.seasonal,
|
||||
seasonal_periods=ns_parser.seasonal_periods,
|
||||
s_end_date=ns_parser.s_end_date,
|
||||
export=ns_parser.export,
|
||||
)
|
||||
|
||||
@try_except
|
||||
def call_knn(self, other_args: List[str]):
|
||||
"""Process knn command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="knn",
|
||||
description="""
|
||||
K nearest neighbors is a simple algorithm that stores all
|
||||
available cases and predict the numerical target based on a similarity measure
|
||||
(e.g. distance functions).
|
||||
""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--input",
|
||||
action="store",
|
||||
dest="n_inputs",
|
||||
type=check_positive,
|
||||
default=40,
|
||||
help="number of days to use as input for prediction.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--days",
|
||||
action="store",
|
||||
dest="n_days",
|
||||
type=check_positive,
|
||||
default=5,
|
||||
help="prediction days.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-j",
|
||||
"--jumps",
|
||||
action="store",
|
||||
dest="n_jumps",
|
||||
type=check_positive,
|
||||
default=1,
|
||||
help="number of jumps in training data.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--neighbors",
|
||||
action="store",
|
||||
dest="n_neighbors",
|
||||
type=check_positive,
|
||||
default=20,
|
||||
help="number of neighbors to use on the algorithm.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--end",
|
||||
action="store",
|
||||
type=valid_date,
|
||||
dest="s_end_date",
|
||||
default=None,
|
||||
help="The end date (format YYYY-MM-DD) to select for testing",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--test_size",
|
||||
default=0.2,
|
||||
dest="valid_split",
|
||||
type=float,
|
||||
help="Percentage of data to validate in sample",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no_shuffle",
|
||||
action="store_false",
|
||||
dest="no_shuffle",
|
||||
default=True,
|
||||
help="Specify if shuffling validation inputs.",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, EXPORT_ONLY_FIGURES_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
knn_view.display_k_nearest_neighbors(
|
||||
ticker=self.ticker,
|
||||
data=self.stock[self.target],
|
||||
n_neighbors=ns_parser.n_neighbors,
|
||||
n_input_days=ns_parser.n_inputs,
|
||||
n_predict_days=ns_parser.n_days,
|
||||
test_size=ns_parser.valid_split,
|
||||
end_date=ns_parser.s_end_date,
|
||||
no_shuffle=ns_parser.no_shuffle,
|
||||
)
|
||||
|
||||
@try_except
|
||||
def call_regression(self, other_args: List[str]):
|
||||
"""Process linear command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="regression",
|
||||
description="""
|
||||
Regression attempts to model the relationship between
|
||||
two variables by fitting a linear/quadratic/cubic/other equation to
|
||||
observed data. One variable is considered to be an explanatory variable,
|
||||
and the other is considered to be a dependent variable.
|
||||
""",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--input",
|
||||
action="store",
|
||||
dest="n_inputs",
|
||||
type=check_positive,
|
||||
default=40,
|
||||
help="number of days to use for prediction.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--days",
|
||||
action="store",
|
||||
dest="n_days",
|
||||
type=check_positive,
|
||||
default=5,
|
||||
help="prediction days.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-j",
|
||||
"--jumps",
|
||||
action="store",
|
||||
dest="n_jumps",
|
||||
type=check_positive,
|
||||
default=1,
|
||||
help="number of jumps in training data.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--end",
|
||||
action="store",
|
||||
type=valid_date,
|
||||
dest="s_end_date",
|
||||
default=None,
|
||||
help="The end date (format YYYY-MM-DD) to select - Backtesting",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--polynomial",
|
||||
action="store",
|
||||
dest="n_polynomial",
|
||||
type=check_positive,
|
||||
default=1,
|
||||
help="polynomial associated with regression.",
|
||||
)
|
||||
if (
|
||||
other_args
|
||||
and "-h" not in other_args
|
||||
and ("-p" not in other_args or "--polynomial" not in other_args)
|
||||
):
|
||||
other_args.insert(0, "-p")
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
# BACKTESTING CHECK
|
||||
if ns_parser.s_end_date:
|
||||
if ns_parser.s_end_date < self.stock.index[0]:
|
||||
print(
|
||||
"Backtesting not allowed, since End Date is older than Start Date of historical data\n"
|
||||
)
|
||||
|
||||
if ns_parser.s_end_date < get_next_stock_market_days(
|
||||
last_stock_day=self.stock.index[0],
|
||||
n_next_days=5 + ns_parser.n_days,
|
||||
)[-1]:
|
||||
print(
|
||||
"Backtesting not allowed, since End Date is too close to Start Date to train model\n"
|
||||
)
|
||||
|
||||
regression_view.display_regression(
|
||||
dataset=self.ticker,
|
||||
values=self.stock[self.target],
|
||||
poly_order=ns_parser.n_polynomial,
|
||||
n_input=ns_parser.n_inputs,
|
||||
n_predict=ns_parser.n_days,
|
||||
n_jumps=ns_parser.n_jumps,
|
||||
s_end_date=ns_parser.s_end_date,
|
||||
export=ns_parser.export,
|
||||
)
|
||||
|
||||
@try_except
|
||||
def call_arima(self, other_args: List[str]):
|
||||
"""Process arima command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="arima",
|
||||
description="""
|
||||
In statistics and econometrics, and in particular in time series analysis, an
|
||||
autoregressive integrated moving average (ARIMA) model is a generalization of an
|
||||
autoregressive moving average (ARMA) model. Both of these models are fitted to time
|
||||
series data either to better understand the data or to predict future points in the
|
||||
series (forecasting). ARIMA(p,d,q) where parameters p, d, and q are non-negative
|
||||
integers, p is the order (number of time lags) of the autoregressive model, d is the
|
||||
degree of differencing (the number of times the data have had past values subtracted),
|
||||
and q is the order of the moving-average model.
|
||||
""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--days",
|
||||
action="store",
|
||||
dest="n_days",
|
||||
type=check_positive,
|
||||
default=5,
|
||||
help="prediction days.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--ic",
|
||||
action="store",
|
||||
dest="s_ic",
|
||||
type=str,
|
||||
default="aic",
|
||||
choices=arima_model.ICS,
|
||||
help="information criteria.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--seasonal",
|
||||
action="store_true",
|
||||
default=False,
|
||||
dest="b_seasonal",
|
||||
help="Use weekly seasonal data.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--order",
|
||||
action="store",
|
||||
dest="s_order",
|
||||
default="",
|
||||
type=str,
|
||||
help="arima model order (p,d,q) in format: p,d,q.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--results",
|
||||
action="store_true",
|
||||
dest="b_results",
|
||||
default=False,
|
||||
help="results about ARIMA summary flag.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--end",
|
||||
action="store",
|
||||
type=valid_date,
|
||||
dest="s_end_date",
|
||||
default=None,
|
||||
help="The end date (format YYYY-MM-DD) to select - Backtesting",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
# BACKTESTING CHECK
|
||||
if ns_parser.s_end_date:
|
||||
|
||||
if ns_parser.s_end_date < self.stock.index[0]:
|
||||
print(
|
||||
"Backtesting not allowed, since End Date is older than Start Date of historical data\n"
|
||||
)
|
||||
|
||||
if ns_parser.s_end_date < get_next_stock_market_days(
|
||||
last_stock_day=self.stock.index[0],
|
||||
n_next_days=5 + ns_parser.n_days,
|
||||
)[-1]:
|
||||
print(
|
||||
"Backtesting not allowed, since End Date is too close to Start Date to train model\n"
|
||||
)
|
||||
|
||||
arima_view.display_arima(
|
||||
dataset=self.ticker,
|
||||
values=self.stock[self.target],
|
||||
arima_order=ns_parser.s_order,
|
||||
n_predict=ns_parser.n_days,
|
||||
seasonal=ns_parser.b_seasonal,
|
||||
ic=ns_parser.s_ic,
|
||||
results=ns_parser.b_results,
|
||||
s_end_date=ns_parser.s_end_date,
|
||||
export=ns_parser.export,
|
||||
)
|
||||
|
||||
@try_except
|
||||
def call_mlp(self, other_args: List[str]):
|
||||
"""Process mlp command"""
|
||||
try:
|
||||
ns_parser = pred_helper.parse_args(
|
||||
prog="mlp",
|
||||
description="""Multi-Layered-Perceptron. """,
|
||||
other_args=other_args,
|
||||
)
|
||||
if ns_parser:
|
||||
neural_networks_view.display_mlp(
|
||||
dataset=self.ticker,
|
||||
data=self.stock[self.target],
|
||||
n_input_days=ns_parser.n_inputs,
|
||||
n_predict_days=ns_parser.n_days,
|
||||
learning_rate=ns_parser.lr,
|
||||
epochs=ns_parser.n_epochs,
|
||||
batch_size=ns_parser.n_batch_size,
|
||||
test_size=ns_parser.valid_split,
|
||||
n_loops=ns_parser.n_loops,
|
||||
no_shuffle=ns_parser.no_shuffle,
|
||||
)
|
||||
except Exception as e:
|
||||
print(e, "\n")
|
||||
|
||||
finally:
|
||||
pred_helper.restore_env()
|
||||
|
||||
def call_rnn(self, other_args: List[str]):
|
||||
"""Process rnn command"""
|
||||
try:
|
||||
ns_parser = pred_helper.parse_args(
|
||||
prog="rnn",
|
||||
description="""Recurrent Neural Network. """,
|
||||
other_args=other_args,
|
||||
)
|
||||
if ns_parser:
|
||||
neural_networks_view.display_rnn(
|
||||
dataset=self.ticker,
|
||||
data=self.stock[self.target],
|
||||
n_input_days=ns_parser.n_inputs,
|
||||
n_predict_days=ns_parser.n_days,
|
||||
learning_rate=ns_parser.lr,
|
||||
epochs=ns_parser.n_epochs,
|
||||
batch_size=ns_parser.n_batch_size,
|
||||
test_size=ns_parser.valid_split,
|
||||
n_loops=ns_parser.n_loops,
|
||||
no_shuffle=ns_parser.no_shuffle,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(e, "\n")
|
||||
|
||||
finally:
|
||||
pred_helper.restore_env()
|
||||
|
||||
def call_lstm(self, other_args: List[str]):
|
||||
"""Process lstm command"""
|
||||
try:
|
||||
ns_parser = pred_helper.parse_args(
|
||||
prog="lstm",
|
||||
description="""Long-Short Term Memory. """,
|
||||
other_args=other_args,
|
||||
)
|
||||
if ns_parser:
|
||||
neural_networks_view.display_lstm(
|
||||
dataset=self.ticker,
|
||||
data=self.stock[self.target],
|
||||
n_input_days=ns_parser.n_inputs,
|
||||
n_predict_days=ns_parser.n_days,
|
||||
learning_rate=ns_parser.lr,
|
||||
epochs=ns_parser.n_epochs,
|
||||
batch_size=ns_parser.n_batch_size,
|
||||
test_size=ns_parser.valid_split,
|
||||
n_loops=ns_parser.n_loops,
|
||||
no_shuffle=ns_parser.no_shuffle,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(e, "\n")
|
||||
|
||||
finally:
|
||||
pred_helper.restore_env()
|
||||
|
||||
def call_conv1d(self, other_args: List[str]):
|
||||
"""Process conv1d command"""
|
||||
try:
|
||||
ns_parser = pred_helper.parse_args(
|
||||
prog="conv1d",
|
||||
description="""1D CNN.""",
|
||||
other_args=other_args,
|
||||
)
|
||||
if ns_parser:
|
||||
neural_networks_view.display_conv1d(
|
||||
dataset=self.ticker,
|
||||
data=self.stock[self.target],
|
||||
n_input_days=ns_parser.n_inputs,
|
||||
n_predict_days=ns_parser.n_days,
|
||||
learning_rate=ns_parser.lr,
|
||||
epochs=ns_parser.n_epochs,
|
||||
batch_size=ns_parser.n_batch_size,
|
||||
test_size=ns_parser.valid_split,
|
||||
n_loops=ns_parser.n_loops,
|
||||
no_shuffle=ns_parser.no_shuffle,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
print(e, "\n")
|
||||
|
||||
finally:
|
||||
pred_helper.restore_env()
|
||||
|
||||
@try_except
|
||||
def call_mc(self, other_args: List[str]):
|
||||
"""Process mc command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
add_help=False,
|
||||
prog="mc",
|
||||
description="""
|
||||
Perform Monte Carlo forecasting
|
||||
""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--days",
|
||||
help="Days to predict",
|
||||
dest="n_days",
|
||||
type=check_positive,
|
||||
default=30,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--num",
|
||||
help="Number of simulations to perform",
|
||||
dest="n_sims",
|
||||
default=100,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dist",
|
||||
choices=mc_model.DISTRIBUTIONS,
|
||||
default="lognormal",
|
||||
dest="dist",
|
||||
help="Whether to model returns or log returns",
|
||||
)
|
||||
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
if self.target != "AdjClose":
|
||||
print("MC Prediction designed for AdjClose prices\n")
|
||||
|
||||
mc_view.display_mc_forecast(
|
||||
data=self.stock[self.target],
|
||||
n_future=ns_parser.n_days,
|
||||
n_sims=ns_parser.n_sims,
|
||||
use_log=ns_parser.dist == "lognormal",
|
||||
export=ns_parser.export,
|
||||
)
|
||||
|
||||
|
||||
def menu(
|
||||
ticker: str,
|
||||
start: datetime,
|
||||
interval: str,
|
||||
stock: pd.DataFrame,
|
||||
queue: List[str] = None,
|
||||
):
|
||||
"""Prediction Techniques Menu"""
|
||||
|
||||
pred_controller = PredictionTechniquesController(
|
||||
ticker, start, interval, stock, queue
|
||||
)
|
||||
an_input = "HELP_ME"
|
||||
|
||||
while True:
|
||||
# There is a command in the queue
|
||||
if pred_controller.queue and len(pred_controller.queue) > 0:
|
||||
# If the command is quitting the menu we want to return in here
|
||||
if pred_controller.queue[0] in ("q", "..", "quit"):
|
||||
if len(pred_controller.queue) > 1:
|
||||
return pred_controller.queue[1:]
|
||||
return []
|
||||
|
||||
# Consume 1 element from the queue
|
||||
an_input = pred_controller.queue[0]
|
||||
pred_controller.queue = pred_controller.queue[1:]
|
||||
|
||||
# Print the current location because this was an instruction and we want user to know what was the action
|
||||
if an_input and an_input.split(" ")[0] in pred_controller.CHOICES_COMMANDS:
|
||||
print(f"{get_flair()} /stocks/pred/ $ {an_input}")
|
||||
|
||||
# Get input command from user
|
||||
else:
|
||||
# Display help menu when entering on this menu from a level above
|
||||
if an_input == "HELP_ME":
|
||||
pred_controller.print_help()
|
||||
|
||||
# Get input from user using auto-completion
|
||||
if session and gtff.USE_PROMPT_TOOLKIT and pred_controller.completer:
|
||||
an_input = session.prompt(
|
||||
f"{get_flair()} /stocks/pred/ $ ",
|
||||
completer=pred_controller.completer,
|
||||
search_ignore_case=True,
|
||||
)
|
||||
# Get input from user without auto-completion
|
||||
else:
|
||||
an_input = input(f"{get_flair()} /stocks/pred/ $ ")
|
||||
|
||||
try:
|
||||
# Process the input command
|
||||
pred_controller.queue = pred_controller.switch(an_input)
|
||||
|
||||
except SystemExit:
|
||||
print(
|
||||
f"\nThe command '{an_input}' doesn't exist on the /stocks/options menu.",
|
||||
end="",
|
||||
)
|
||||
similar_cmd = difflib.get_close_matches(
|
||||
an_input.split(" ")[0] if " " in an_input else an_input,
|
||||
pred_controller.CHOICES,
|
||||
n=1,
|
||||
cutoff=0.7,
|
||||
)
|
||||
if similar_cmd:
|
||||
if " " in an_input:
|
||||
candidate_input = (
|
||||
f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}"
|
||||
)
|
||||
else:
|
||||
candidate_input = similar_cmd[0]
|
||||
|
||||
if candidate_input == an_input:
|
||||
an_input = ""
|
||||
pred_controller.queue = []
|
||||
print("\n")
|
||||
continue
|
||||
|
||||
print(f" Replacing by '{an_input}'.")
|
||||
pred_controller.queue.insert(0, an_input)
|
||||
else:
|
||||
print("\n")
|
||||
an_input = ""
|
||||
pred_controller.queue = []
|
||||
@@ -1,3 +1,6 @@
|
||||
# Author of preset: James Maslek
|
||||
# Description: Give a small description of what this preset is trying to do
|
||||
|
||||
[Price]
|
||||
MIN = None
|
||||
MAX = None
|
||||
471
gamestonk_terminal/etf/screener/screener_controller.py
Normal file
471
gamestonk_terminal/etf/screener/screener_controller.py
Normal file
@@ -0,0 +1,471 @@
|
||||
"""Screener Controller Module"""
|
||||
__docformat__ = "numpy"
|
||||
# pylint:disable=too-many-lines
|
||||
# pylint:disable=R0904,C0201
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import configparser
|
||||
import difflib
|
||||
from typing import List, Union
|
||||
|
||||
from prompt_toolkit.completion import NestedCompleter
|
||||
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.helper_funcs import (
|
||||
EXPORT_ONLY_RAW_DATA_ALLOWED,
|
||||
get_flair,
|
||||
parse_known_args_and_warn,
|
||||
try_except,
|
||||
system_clear,
|
||||
check_positive,
|
||||
)
|
||||
from gamestonk_terminal.menu import session
|
||||
from gamestonk_terminal.etf.screener import screener_view
|
||||
from gamestonk_terminal.etf import financedatabase_view, financedatabase_model
|
||||
|
||||
presets_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "presets/")
|
||||
|
||||
|
||||
class ScreenerController:
|
||||
"""Screener Controller class"""
|
||||
|
||||
CHOICES = [
|
||||
"cls",
|
||||
"home",
|
||||
"h",
|
||||
"?",
|
||||
"help",
|
||||
"q",
|
||||
"quit",
|
||||
"..",
|
||||
"exit",
|
||||
"r",
|
||||
"reset",
|
||||
]
|
||||
|
||||
CHOICES_COMMANDS = [
|
||||
"view",
|
||||
"set",
|
||||
"screen",
|
||||
"sbc",
|
||||
]
|
||||
|
||||
CHOICES += CHOICES_COMMANDS
|
||||
|
||||
presets_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "presets/")
|
||||
preset_choices = [
|
||||
f.split(".")[0] for f in os.listdir(presets_path) if f.endswith(".ini")
|
||||
]
|
||||
|
||||
sortby_screen_choices = [
|
||||
"Assets",
|
||||
"NAV",
|
||||
"Expense",
|
||||
"PE",
|
||||
"SharesOut",
|
||||
"Div",
|
||||
"DivYield",
|
||||
"Volume",
|
||||
"Open",
|
||||
"PrevClose",
|
||||
"YrLow",
|
||||
"YrHigh",
|
||||
"Beta",
|
||||
"N_Hold",
|
||||
]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
queue: List[str] = None,
|
||||
):
|
||||
"""Constructor"""
|
||||
self.scr_parser = argparse.ArgumentParser(add_help=False, prog="scr")
|
||||
self.scr_parser.add_argument(
|
||||
"cmd",
|
||||
choices=self.CHOICES,
|
||||
)
|
||||
self.completer: Union[None, NestedCompleter] = None
|
||||
if session and gtff.USE_PROMPT_TOOLKIT:
|
||||
choices: dict = {c: {} for c in self.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()
|
||||
}
|
||||
self.completer = NestedCompleter.from_nested_dict(choices)
|
||||
|
||||
self.preset = "etf_config"
|
||||
self.screen_tickers: List = list()
|
||||
|
||||
if queue:
|
||||
self.queue = queue
|
||||
else:
|
||||
self.queue = list()
|
||||
|
||||
def print_help(self):
|
||||
"""Print help"""
|
||||
help_str = f"""
|
||||
view view available presets
|
||||
set set one of the available presets
|
||||
|
||||
PRESET: {self.preset}
|
||||
|
||||
screen screen ETF using preset selected [StockAnalysis]
|
||||
|
||||
sbc screen by category [FinanceDatabase]
|
||||
"""
|
||||
print(help_str)
|
||||
|
||||
def switch(self, an_input: str):
|
||||
"""Process and dispatch input
|
||||
Parameters
|
||||
-------
|
||||
an_input : str
|
||||
string with input arguments
|
||||
Returns
|
||||
-------
|
||||
List[str]
|
||||
List of commands in the queue to execute
|
||||
"""
|
||||
|
||||
# Empty command
|
||||
if not an_input:
|
||||
print("")
|
||||
return self.queue
|
||||
|
||||
# Navigation slash is being used
|
||||
if "/" in an_input:
|
||||
actions = an_input.split("/")
|
||||
|
||||
# Absolute path is specified
|
||||
if not actions[0]:
|
||||
an_input = "home"
|
||||
# Relative path so execute first instruction
|
||||
else:
|
||||
an_input = actions[0]
|
||||
|
||||
# Add all instructions to the queue
|
||||
for cmd in actions[1:][::-1]:
|
||||
if cmd:
|
||||
self.queue.insert(0, cmd)
|
||||
|
||||
(known_args, other_args) = self.scr_parser.parse_known_args(an_input.split())
|
||||
|
||||
# Redirect commands to their correct functions
|
||||
if known_args.cmd:
|
||||
if known_args.cmd in ("..", "q"):
|
||||
known_args.cmd = "quit"
|
||||
elif known_args.cmd in ("?", "h"):
|
||||
known_args.cmd = "help"
|
||||
elif known_args.cmd == "r":
|
||||
known_args.cmd = "reset"
|
||||
|
||||
getattr(
|
||||
self,
|
||||
"call_" + known_args.cmd,
|
||||
lambda _: "Command not recognized!",
|
||||
)(other_args)
|
||||
|
||||
return self.queue
|
||||
|
||||
def call_cls(self, _):
|
||||
"""Process cls command"""
|
||||
system_clear()
|
||||
|
||||
def call_home(self, _):
|
||||
"""Process home command"""
|
||||
self.queue.insert(0, "quit")
|
||||
self.queue.insert(0, "quit")
|
||||
|
||||
def call_help(self, _):
|
||||
"""Process help command"""
|
||||
self.print_help()
|
||||
|
||||
def call_quit(self, _):
|
||||
"""Process quit menu command"""
|
||||
print("")
|
||||
self.queue.insert(0, "quit")
|
||||
|
||||
def call_exit(self, _):
|
||||
"""Process exit terminal command"""
|
||||
self.queue.insert(0, "quit")
|
||||
self.queue.insert(0, "quit")
|
||||
self.queue.insert(0, "quit")
|
||||
|
||||
def call_reset(self, _):
|
||||
"""Process reset command"""
|
||||
self.queue.insert(0, "scr")
|
||||
self.queue.insert(0, "etf")
|
||||
self.queue.insert(0, "reset")
|
||||
self.queue.insert(0, "quit")
|
||||
self.queue.insert(0, "quit")
|
||||
|
||||
@try_except
|
||||
def call_view(self, other_args: List[str]):
|
||||
"""Process view command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="view",
|
||||
description="""View available presets under presets folder.""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--preset",
|
||||
action="store",
|
||||
dest="preset",
|
||||
type=str,
|
||||
help="View specific custom preset",
|
||||
default="",
|
||||
choices=self.preset_choices,
|
||||
)
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-p")
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
if ns_parser.preset:
|
||||
preset_filter = configparser.RawConfigParser()
|
||||
preset_filter.optionxform = str # type: ignore
|
||||
preset_filter.read(presets_path + ns_parser.preset + ".ini")
|
||||
|
||||
headers = [
|
||||
"Price",
|
||||
"Assets",
|
||||
"NAV",
|
||||
"Expense",
|
||||
"PE",
|
||||
"DivYield",
|
||||
"Volume",
|
||||
"Beta",
|
||||
"N_Hold",
|
||||
"Open",
|
||||
"PrevClose",
|
||||
"YrLow",
|
||||
"YrHigh",
|
||||
]
|
||||
|
||||
print("")
|
||||
for filter_header in headers:
|
||||
print(f" - {filter_header} -")
|
||||
d_filters = {**preset_filter[filter_header]}
|
||||
d_filters = {k: v for k, v in d_filters.items() if v}
|
||||
if d_filters:
|
||||
max_len = len(max(d_filters, key=len))
|
||||
for key, value in d_filters.items():
|
||||
print(f"{key}{(max_len-len(key))*' '}: {value}")
|
||||
print("")
|
||||
|
||||
else:
|
||||
print("\nPresets:")
|
||||
for preset in self.preset_choices:
|
||||
with open(
|
||||
presets_path + preset + ".ini",
|
||||
encoding="utf8",
|
||||
) as f:
|
||||
description = ""
|
||||
for line in f:
|
||||
if line.strip() == "[Price]":
|
||||
break
|
||||
description += line.strip()
|
||||
print(
|
||||
f" {preset}{(30-len(preset)) * ' '}{description.split('Description: ')[1].replace('#', '')}"
|
||||
)
|
||||
print("")
|
||||
|
||||
@try_except
|
||||
def call_set(self, other_args: List[str]):
|
||||
"""Process set command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="set",
|
||||
description="""Set preset.""",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--preset",
|
||||
action="store",
|
||||
dest="preset",
|
||||
type=str,
|
||||
default="template",
|
||||
help="Filter presets",
|
||||
choices=self.preset_choices,
|
||||
)
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-p")
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
self.preset = ns_parser.preset
|
||||
print("")
|
||||
|
||||
@try_except
|
||||
def call_screen(self, other_args):
|
||||
"""Process screen command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="screen",
|
||||
add_help=False,
|
||||
description="Screens ETFS from a personal scraping github repository. Data scraped from stockanalysis.com",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--limit",
|
||||
type=int,
|
||||
help="Limit of etfs to display",
|
||||
dest="limit",
|
||||
default=10,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--sort",
|
||||
dest="sortby",
|
||||
type=str,
|
||||
help="Sort by given column. Default: Assets",
|
||||
default="Assets",
|
||||
choices=self.sortby_screen_choices,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-a",
|
||||
"--ascend",
|
||||
action="store_true",
|
||||
help="Flag to sort in ascending order (lowest on top)",
|
||||
dest="ascend",
|
||||
)
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-l")
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
screener_view.view_screener(
|
||||
preset=self.preset,
|
||||
num_to_show=ns_parser.limit,
|
||||
sortby=ns_parser.sortby,
|
||||
ascend=ns_parser.ascend,
|
||||
export=ns_parser.export,
|
||||
)
|
||||
|
||||
@try_except
|
||||
def call_sbc(self, other_args: List[str]):
|
||||
"""Process sbc command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="sbc",
|
||||
description="Search by category [Source: FinanceDatabase/StockAnalysis.com]",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--category",
|
||||
type=str,
|
||||
dest="category",
|
||||
nargs="+",
|
||||
help="Category to look for",
|
||||
required="-h" not in other_args,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-l",
|
||||
"--limit",
|
||||
type=check_positive,
|
||||
dest="limit",
|
||||
help="Limit of ETFs to display",
|
||||
default=5,
|
||||
)
|
||||
|
||||
if other_args and "-" not in other_args[0][0]:
|
||||
other_args.insert(0, "-c")
|
||||
|
||||
ns_parser = parse_known_args_and_warn(
|
||||
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
|
||||
)
|
||||
if ns_parser:
|
||||
category = " ".join(ns_parser.category)
|
||||
if category in financedatabase_model.get_etfs_categories():
|
||||
financedatabase_view.display_etf_by_category(
|
||||
category=category,
|
||||
limit=ns_parser.limit,
|
||||
export=ns_parser.export,
|
||||
)
|
||||
else:
|
||||
print(
|
||||
"The category selected does not exist, choose one from:"
|
||||
f" {', '.join(financedatabase_model.get_etfs_categories())}\n"
|
||||
)
|
||||
|
||||
|
||||
def menu(
|
||||
queue: List[str] = None,
|
||||
):
|
||||
"""Screener Menu"""
|
||||
|
||||
scr_controller = ScreenerController(queue)
|
||||
an_input = "HELP_ME"
|
||||
|
||||
while True:
|
||||
# There is a command in the queue
|
||||
if scr_controller.queue and len(scr_controller.queue) > 0:
|
||||
# If the command is quitting the menu we want to return in here
|
||||
if scr_controller.queue[0] in ("q", "..", "quit"):
|
||||
if len(scr_controller.queue) > 1:
|
||||
return scr_controller.queue[1:]
|
||||
return []
|
||||
|
||||
# Consume 1 element from the queue
|
||||
an_input = scr_controller.queue[0]
|
||||
scr_controller.queue = scr_controller.queue[1:]
|
||||
|
||||
# Print the current location because this was an instruction and we want user to know what was the action
|
||||
if an_input and an_input.split(" ")[0] in scr_controller.CHOICES_COMMANDS:
|
||||
print(f"{get_flair()} /etf/scr/ $ {an_input}")
|
||||
|
||||
# Get input command from user
|
||||
else:
|
||||
# Display help menu when entering on this menu from a level above
|
||||
if an_input == "HELP_ME":
|
||||
scr_controller.print_help()
|
||||
|
||||
# Get input from user using auto-completion
|
||||
if session and gtff.USE_PROMPT_TOOLKIT and scr_controller.completer:
|
||||
an_input = session.prompt(
|
||||
f"{get_flair()} /etf/scr/ $ ",
|
||||
completer=scr_controller.completer,
|
||||
search_ignore_case=True,
|
||||
)
|
||||
# Get input from user without auto-completion
|
||||
else:
|
||||
an_input = input(f"{get_flair()} /etf/scr/ $ ")
|
||||
|
||||
try:
|
||||
# Process the input command
|
||||
scr_controller.queue = scr_controller.switch(an_input)
|
||||
|
||||
except SystemExit:
|
||||
print(
|
||||
f"\nThe command '{an_input}' doesn't exist on the /etf/scr menu.",
|
||||
end="",
|
||||
)
|
||||
similar_cmd = difflib.get_close_matches(
|
||||
an_input.split(" ")[0] if " " in an_input else an_input,
|
||||
scr_controller.CHOICES,
|
||||
n=1,
|
||||
cutoff=0.7,
|
||||
)
|
||||
if similar_cmd:
|
||||
if " " in an_input:
|
||||
candidate_input = (
|
||||
f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}"
|
||||
)
|
||||
else:
|
||||
candidate_input = similar_cmd[0]
|
||||
|
||||
if candidate_input == an_input:
|
||||
an_input = ""
|
||||
scr_controller.queue = []
|
||||
print("\n")
|
||||
continue
|
||||
|
||||
print(f" Replacing by '{an_input}'.")
|
||||
scr_controller.queue.insert(0, an_input)
|
||||
else:
|
||||
print("\n")
|
||||
an_input = ""
|
||||
scr_controller.queue = []
|
||||
57
gamestonk_terminal/etf/screener/screener_view.py
Normal file
57
gamestonk_terminal/etf/screener/screener_view.py
Normal file
@@ -0,0 +1,57 @@
|
||||
"""ETF Screener view"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import os
|
||||
|
||||
from tabulate import tabulate
|
||||
|
||||
from gamestonk_terminal.etf.screener import screener_model
|
||||
from gamestonk_terminal.helper_funcs import export_data
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
|
||||
# pylint:disable=no-member
|
||||
|
||||
|
||||
def view_screener(
|
||||
preset: str, num_to_show: int, sortby: str, ascend: bool, export: str = ""
|
||||
):
|
||||
"""Display screener output
|
||||
|
||||
Parameters
|
||||
----------
|
||||
preset: str
|
||||
Preset to use
|
||||
num_to_show: int
|
||||
Number of etfs to show
|
||||
sortby: str
|
||||
Column to sort by
|
||||
ascend: bool
|
||||
Ascend when sorted
|
||||
export: str
|
||||
Output format of export
|
||||
|
||||
"""
|
||||
screened_data = screener_model.etf_screener(preset)
|
||||
|
||||
screened_data = screened_data.sort_values(by=sortby, ascending=ascend)
|
||||
|
||||
if gtff.USE_TABULATE_DF:
|
||||
print(
|
||||
tabulate(
|
||||
screened_data.head(num_to_show).fillna(""),
|
||||
tablefmt="fancy_grid",
|
||||
headers=screened_data.columns,
|
||||
showindex=True,
|
||||
disable_numparse=True,
|
||||
)
|
||||
)
|
||||
else:
|
||||
print(screened_data.head(num_to_show).fillna("").to_string())
|
||||
print("")
|
||||
|
||||
export_data(
|
||||
export,
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"screen",
|
||||
screened_data,
|
||||
)
|
||||
@@ -1,46 +0,0 @@
|
||||
"""ETF Screener view"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import os
|
||||
|
||||
from tabulate import tabulate
|
||||
|
||||
from gamestonk_terminal.etf import screener_model
|
||||
from gamestonk_terminal.helper_funcs import export_data
|
||||
|
||||
# pylint:disable=no-member
|
||||
|
||||
|
||||
def view_screener(num_to_show: int, preset: str, export: str):
|
||||
"""Display screener output
|
||||
|
||||
Parameters
|
||||
----------
|
||||
num_to_show: int
|
||||
Number of etfs to show
|
||||
preset: str
|
||||
Preset to use
|
||||
export: str
|
||||
Output format of export
|
||||
|
||||
"""
|
||||
screened_data = screener_model.etf_screener(preset)
|
||||
if screened_data.shape[0] > int(num_to_show):
|
||||
screened_data = screened_data.sample(num_to_show)
|
||||
print(
|
||||
tabulate(
|
||||
screened_data.fillna(""),
|
||||
tablefmt="fancy_grid",
|
||||
headers=screened_data.columns,
|
||||
showindex=True,
|
||||
disable_numparse=True,
|
||||
)
|
||||
)
|
||||
print("")
|
||||
|
||||
export_data(
|
||||
export,
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"screener",
|
||||
screened_data,
|
||||
)
|
||||
@@ -80,22 +80,24 @@ def get_etf_holdings(symbol: str) -> pd.DataFrame:
|
||||
|
||||
link = f"https://stockanalysis.com/etf/{symbol}/holdings/"
|
||||
r = requests.get(link, headers={"User-Agent": get_user_agent()})
|
||||
soup = bs(r.text, "html.parser")
|
||||
soup = soup.find("table")
|
||||
tds = soup.findAll("td")
|
||||
tickers = []
|
||||
for i in tds[1::5]:
|
||||
tickers.append(i.text)
|
||||
percents = []
|
||||
for i in tds[3::5]:
|
||||
percents.append(i.text)
|
||||
shares = []
|
||||
for i in tds[4::5]:
|
||||
shares.append(i.text)
|
||||
df = pd.DataFrame(index=tickers)
|
||||
df["% Of Etf"] = percents
|
||||
df["Shares"] = shares
|
||||
return df
|
||||
if r.status_code == 200:
|
||||
soup = bs(r.text, "html.parser")
|
||||
soup = soup.find("table")
|
||||
tds = soup.findAll("td")
|
||||
tickers = []
|
||||
for i in tds[1::5]:
|
||||
tickers.append(i.text)
|
||||
percents = []
|
||||
for i in tds[3::5]:
|
||||
percents.append(i.text)
|
||||
shares = []
|
||||
for i in tds[4::5]:
|
||||
shares.append(i.text)
|
||||
df = pd.DataFrame(index=tickers)
|
||||
df["% Of Etf"] = percents
|
||||
df["Shares"] = shares
|
||||
return df
|
||||
return pd.DataFrame()
|
||||
|
||||
|
||||
def compare_etfs(symbols: List[str]) -> pd.DataFrame:
|
||||
@@ -119,23 +121,30 @@ def compare_etfs(symbols: List[str]) -> pd.DataFrame:
|
||||
return df_compare
|
||||
|
||||
|
||||
def search_etfs(to_search) -> List[str]:
|
||||
"""Search for an etf string in list of ETFs
|
||||
def get_etfs_by_name(name_to_search: str) -> pd.DataFrame:
|
||||
"""Get an ETF symbol and name based on ETF string to search. [Source: StockAnalysis]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
to_search: str
|
||||
String to match
|
||||
name_to_search: str
|
||||
ETF name to match
|
||||
|
||||
Returns
|
||||
-------
|
||||
matching_etfs: List[str]
|
||||
List of matching ETF names
|
||||
df: pd.Dataframe
|
||||
Dataframe with symbols and names
|
||||
"""
|
||||
all_symbols, all_names = get_all_names_symbols()
|
||||
matching_etfs = [
|
||||
all_symbols[idx] + " - " + etf
|
||||
for idx, etf in enumerate(all_names)
|
||||
if to_search.lower() in etf.lower()
|
||||
]
|
||||
return matching_etfs
|
||||
|
||||
filtered_symbols = list()
|
||||
filtered_names = list()
|
||||
for symbol, name in zip(all_symbols, all_names):
|
||||
if name_to_search.lower() in name.lower():
|
||||
filtered_symbols.append(symbol)
|
||||
filtered_names.append(name)
|
||||
|
||||
df = pd.DataFrame(
|
||||
list(zip(filtered_symbols, filtered_names)), columns=["Symbol", "Name"]
|
||||
)
|
||||
|
||||
return df
|
||||
|
||||
@@ -4,9 +4,8 @@ __docformat__ = "numpy"
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
import pandas as pd
|
||||
from tabulate import tabulate
|
||||
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.etf import stockanalysis_model
|
||||
from gamestonk_terminal.helper_funcs import export_data
|
||||
|
||||
@@ -47,14 +46,18 @@ def view_holdings(symbol: str, num_to_show: int, export: str):
|
||||
"""
|
||||
|
||||
data = stockanalysis_model.get_etf_holdings(symbol)
|
||||
print(
|
||||
tabulate(
|
||||
data[:num_to_show],
|
||||
headers=data.columns,
|
||||
tablefmt="fancy_grid",
|
||||
),
|
||||
"\n",
|
||||
)
|
||||
if gtff.USE_TABULATE_DF:
|
||||
print(
|
||||
tabulate(
|
||||
data[:num_to_show],
|
||||
headers=data.columns,
|
||||
tablefmt="fancy_grid",
|
||||
),
|
||||
"\n",
|
||||
)
|
||||
else:
|
||||
print(data.head(num_to_show).to_string(), "\n")
|
||||
|
||||
export_data(export, os.path.dirname(os.path.abspath(__file__)), "holdings", data)
|
||||
|
||||
|
||||
@@ -72,34 +75,50 @@ def view_comparisons(symbols: List[str], export: str):
|
||||
etf_list = stockanalysis_model.get_all_names_symbols()[0]
|
||||
for etf in symbols:
|
||||
if etf not in etf_list:
|
||||
print(f"{etf} not a known symbol. ")
|
||||
print(f"{etf} not a known symbol.\n")
|
||||
etf_list.remove(etf)
|
||||
|
||||
data = stockanalysis_model.compare_etfs(symbols)
|
||||
print(tabulate(data, headers=data.columns, tablefmt="fancy_grid"), "\n")
|
||||
if data.empty:
|
||||
print("No data found for given ETFs\n")
|
||||
return
|
||||
if gtff.USE_TABULATE_DF:
|
||||
print(tabulate(data, headers=data.columns, tablefmt="fancy_grid"), "\n")
|
||||
else:
|
||||
print(data.to_string())
|
||||
export_data(export, os.path.dirname(os.path.abspath(__file__)), "overview", data)
|
||||
|
||||
|
||||
def view_search(to_match: str, export: str):
|
||||
"""Display ETFs matching search string
|
||||
def display_etf_by_name(name: str, limit: int, export: str):
|
||||
"""Display ETFs matching search string. [Source: StockAnalysis]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
to_match: str
|
||||
name: str
|
||||
String being matched
|
||||
limit: int
|
||||
Limit of ETFs to display
|
||||
export: str
|
||||
Export to given file type
|
||||
|
||||
"""
|
||||
matching_etfs = stockanalysis_model.search_etfs(to_match)
|
||||
matching_etfs = stockanalysis_model.get_etfs_by_name(name)
|
||||
|
||||
if gtff.USE_TABULATE_DF:
|
||||
print(
|
||||
tabulate(
|
||||
matching_etfs.head(limit),
|
||||
showindex=False,
|
||||
tablefmt="fancy_grid",
|
||||
),
|
||||
"\n",
|
||||
)
|
||||
else:
|
||||
print(matching_etfs.head(limit).to_string(), "\n")
|
||||
|
||||
export_data(
|
||||
export,
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"search",
|
||||
pd.DataFrame(data=matching_etfs),
|
||||
"ln_sa",
|
||||
matching_etfs,
|
||||
)
|
||||
print(*matching_etfs, sep="\n")
|
||||
if len(matching_etfs) == 0:
|
||||
print("No matches found")
|
||||
print("")
|
||||
|
||||
1351
gamestonk_terminal/etf/technical_analysis/ta_controller.py
Normal file
1351
gamestonk_terminal/etf/technical_analysis/ta_controller.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,42 +0,0 @@
|
||||
"""WSJ view"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import os
|
||||
|
||||
from tabulate import tabulate
|
||||
|
||||
from gamestonk_terminal.etf import wsj_model
|
||||
from gamestonk_terminal.helper_funcs import (
|
||||
export_data,
|
||||
)
|
||||
|
||||
|
||||
def show_top_mover(sort_type: str, limit: int = 20, export=""):
|
||||
"""
|
||||
Show top ETF movers from wsj.com
|
||||
Parameters
|
||||
----------
|
||||
sort_type: str
|
||||
What to show. Either Gainers, Decliners or Activity
|
||||
limit: int
|
||||
Number of etfs to show
|
||||
export: str
|
||||
Format to export data
|
||||
"""
|
||||
data = wsj_model.etf_movers(sort_type)
|
||||
print(
|
||||
tabulate(
|
||||
data.iloc[:limit],
|
||||
showindex=False,
|
||||
headers=data.columns,
|
||||
floatfmt=".2f",
|
||||
tablefmt="fancy_grid",
|
||||
)
|
||||
)
|
||||
export_data(
|
||||
export,
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "movers"),
|
||||
sort_type,
|
||||
data,
|
||||
)
|
||||
print("")
|
||||
54
gamestonk_terminal/etf/yfinance_model.py
Normal file
54
gamestonk_terminal/etf/yfinance_model.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""Yahoo Finance model"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
from typing import Dict
|
||||
import yfinance as yf
|
||||
|
||||
|
||||
def get_etf_sector_weightings(name: str) -> Dict:
|
||||
"""Return sector weightings allocation of ETF. [Source: Yahoo Finance]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: str
|
||||
ETF name
|
||||
|
||||
Returns
|
||||
----------
|
||||
Dict
|
||||
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()
|
||||
|
||||
|
||||
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 ""
|
||||
101
gamestonk_terminal/etf/yfinance_view.py
Normal file
101
gamestonk_terminal/etf/yfinance_view.py
Normal file
@@ -0,0 +1,101 @@
|
||||
"""Yahoo Finance view"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import os
|
||||
import pandas as pd
|
||||
from matplotlib import pyplot as plt
|
||||
from tabulate import tabulate
|
||||
from gamestonk_terminal.etf import yfinance_model
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.config_plot import PLOT_DPI
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale, export_data
|
||||
|
||||
|
||||
def display_etf_weightings(
|
||||
name: str,
|
||||
raw: bool = False,
|
||||
min_pct_to_display: float = 5,
|
||||
export: str = "",
|
||||
):
|
||||
"""Display sector weightings allocation of ETF. [Source: Yahoo Finance]
|
||||
|
||||
Parameters
|
||||
----------
|
||||
name: str
|
||||
ETF name
|
||||
raw: bool
|
||||
Display sector weighting allocation
|
||||
min_pct_to_display: float
|
||||
Minimum percentage to display sector
|
||||
export: str
|
||||
Type of format to export data
|
||||
"""
|
||||
sectors = yfinance_model.get_etf_sector_weightings(name)
|
||||
if not sectors:
|
||||
print("No data was found for that ETF\n")
|
||||
return
|
||||
|
||||
holdings = pd.DataFrame(sectors, index=[0]).T
|
||||
|
||||
title = f"Sector holdings of {name}"
|
||||
|
||||
if raw:
|
||||
print(f"\n{title}")
|
||||
holdings.columns = ["% of holdings in the sector"]
|
||||
if gtff.USE_TABULATE_DF:
|
||||
print(
|
||||
tabulate(
|
||||
holdings,
|
||||
headers=holdings.columns,
|
||||
showindex=True,
|
||||
tablefmt="fancy_grid",
|
||||
),
|
||||
)
|
||||
else:
|
||||
print(holdings.to_string, "\n")
|
||||
|
||||
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)]
|
||||
|
||||
plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
|
||||
plt.pie(
|
||||
values,
|
||||
labels=leg,
|
||||
wedgeprops={"linewidth": 0.5, "edgecolor": "white"},
|
||||
labeldistance=1.05,
|
||||
startangle=90,
|
||||
)
|
||||
plt.title(title)
|
||||
plt.tight_layout()
|
||||
if gtff.USE_ION:
|
||||
plt.ion()
|
||||
plt.show()
|
||||
|
||||
print("")
|
||||
|
||||
export_data(export, os.path.dirname(os.path.abspath(__file__)), "weights", holdings)
|
||||
|
||||
|
||||
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:
|
||||
print("No data was found for that ETF\n")
|
||||
return
|
||||
|
||||
print(description, "\n")
|
||||
@@ -129,7 +129,7 @@ class ScreenerController:
|
||||
view view available presets (defaults and customs)
|
||||
set set one of the available presets
|
||||
|
||||
PRESET: {self.preset}
|
||||
PRESET: {self.preset}
|
||||
|
||||
historical view historical price
|
||||
overview overview (e.g. Sector, Industry, Market Cap, Volume)
|
||||
|
||||
@@ -734,6 +734,7 @@ Custom:
|
||||
# Daily
|
||||
if self.interval == "1440min":
|
||||
print("VWAP should be used with intraday data. \n")
|
||||
return
|
||||
|
||||
overlap_view.view_vwap(
|
||||
s_ticker=self.ticker,
|
||||
|
||||
19
scripts/test_etf.gst
vendored
19
scripts/test_etf.gst
vendored
@@ -1,12 +1,11 @@
|
||||
etf
|
||||
gainers
|
||||
decliners
|
||||
active
|
||||
q
|
||||
etf
|
||||
reset
|
||||
search blackrock
|
||||
holdings qqq -l 5
|
||||
overview spy
|
||||
compare spy,qqq
|
||||
load spy
|
||||
overview
|
||||
holdings
|
||||
weights
|
||||
summary
|
||||
candle
|
||||
news
|
||||
pir
|
||||
compare spy,ivv
|
||||
exit
|
||||
7
scripts/test_etf_ca.gst
vendored
Normal file
7
scripts/test_etf_ca.gst
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
etf
|
||||
ln oil
|
||||
ld oil
|
||||
load spy
|
||||
ca
|
||||
volume
|
||||
exit
|
||||
6
scripts/test_etf_disc.gst
vendored
Normal file
6
scripts/test_etf_disc.gst
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
etf
|
||||
disc
|
||||
gainers
|
||||
decliners
|
||||
active
|
||||
exit
|
||||
5
scripts/test_etf_pred.gst
vendored
Normal file
5
scripts/test_etf_pred.gst
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
etf
|
||||
load spy
|
||||
pred
|
||||
mc
|
||||
exit
|
||||
8
scripts/test_etf_scr.gst
vendored
Normal file
8
scripts/test_etf_scr.gst
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
etf
|
||||
scr
|
||||
view
|
||||
view etf_config
|
||||
set etf_config
|
||||
screen
|
||||
sbc Bank Loan
|
||||
exit
|
||||
5
scripts/test_etf_ta.gst
vendored
Normal file
5
scripts/test_etf_ta.gst
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
etf
|
||||
load spy
|
||||
ta
|
||||
ema
|
||||
exit
|
||||
Reference in New Issue
Block a user