Big UX improvement (#1041)

Co-authored-by: jmaslek <jmaslek11@gmail.com>
Co-authored-by: Chavithra <chavithra@gmail.com>
Co-authored-by: Theodore Aptekarev <aptekarev@gmail.com>
Co-authored-by: Danglewood <85772166+deeleeramone@users.noreply.github.com>
Co-authored-by: Didier Rodrigues Lopes <dro.lopes@campus.fct.unl.pt>

* improve user experience phase 1

* add difflib replacement

* implement auto-complete on discovery menu

* auoto-complete, difflib,jump through menus with cd

* Updating tests : terminal

* improve navigation on gst

* add q command to terminal.py

* usage instructions

* Updating tests : terminal

* fix cd command

* vars updated

* show menu when user is going through menus

* allow user to forget the intiail / on scripting

* terminal to accept text file with configuration

* refactor -n to -l and clean code

* add all commands to the tests_stocks_disc

* remove fds from disc since we have search on stocks/ and stocks/sia

* hopefully fix tests

* skip test

* fix completer type and calling menu after command for stocks/disc

* show help menu when moving forward/backwards in menus

* skip test

* skip test - bug fix

* add reset, help and quit to choices

* improve stocks/disc controller

* refactor sia menu

* UX improvements for sia menu

* improve ux design

* refactor ca controller

* fix infinite loop

* improve ux on dps controller

* Add capability of going back from a menu going into a menu

* Ux economy (#1056)

* First round of UX improvements on economy menu

* Do fred menu + sample tests file

* revert to > on menu

* rename jobs file to .gst

* remove cd option

* improve return

* final refactoring of disc_controller

* fix ca,dps,sia,disc,stocks and terminal controllers

* work on ba_controller menu

* fix reset and try_except

* UX on screener controller

* fix for full reset name

* Ux etfs (#1059)

* Refactor ETF menu for new UX

* Add couple more examples to test

* r->reset

* Clean up switch

* gov menu refactored

* fix reset issue

* 80% of refactoring options and pricing/payoff menu is done

* improve payoff controller

* minor fix

* fix: refactor pred and ta stocks controller

* fix: refactor fa(and fmp) controllers

* fix: refactored dd controller (still missing .gst tests)

* fix infinite loop bug

* refactor options controller

* add tests for payoff and pricing

* fix reset for options

* UX Upgrade for Crypto controller(s) (#1049)

* refactor crypto controller

* refactor crypto/defi controller

* added simple test and fixes not returning queue in commands

* Clean up unused functions / iex load (#1047)

* Clean up unused functions and adjust iex load function to not depend on dates

* Had an incorrect tab on s_start definition

* wrong default arg

* regenerate pytest yml

* Comment out parametrization

* Tests : Stocks > Discovery (#1050)

* Updating tests : stocks/discovery/ark

* Updating tests : stocks/discovery/fidelity

* Updating tests : stocks/discovery/fidelity

* Updating tests : stocks/discovery/fidelity

* Updating tests : stocks/discovery/seeking_alpha

* Updating tests : stocks/discovery/seeking_alpha

* Updating tests : stocks/discovery/shortinterest

* Updating tests : stocks/discovery/shortinterest

* Updating tests : stocks/discovery/seeking_alpha

* Updating tests : stocks/discovery/shortinterest

* Updating tests : stocks/discovery/disc_controller

* Updating tests : stocks/dd

* Updating tests : stocks/fa

* Updating tests : stocks/discovery/financedatabase

* Updating tests : stocks/discovery/finnhub

* Updating tests : stocks/discovery/geekofwallstreet

* Updating tests : stocks/discovery/marketbeat

* Updating tests : stocks/discovery/nasdaq

* Updating tests : stocks / discovery / yahoofinance

* Updating tests : black

* Updating tests : unused import

* Updating tests : stocks/discovery/ark

* Tests : Stocks > Doc + Helpers + Government (#1051)

* Updating tests : stocks/government/controller

* Updating tests : stocks/government

* Updating tests : conftest

* Updating tests : stocks > government

* Updating tests : stocks/government

* Updating tests : docs

* Tests : Stocks > Insider (#1054)

* Updating tests : stocks > helper

* Updating tests : doc

* Updating tests : clean stocks > fa > av

* Updating tests : stocks/insider/controller

* Updating tests : stocks/insider/business insider

* Updating tests : stocks/insider/finviz

* Updating tests :  stocks/insider/openinsider

* Updating tests :  stocks/insider/controller

* Updating tests : black

* Updating tests : stocks/insider/controller

* Tests : Stocks > Backtesting + Behavioural Analysis  (#1057)

* Updating tests : add __init__ files

* Updating tests : stocsk/behavioural_analysis

* Updating tests : black

* Updating tests : stocks/backtesting

* Updating tests : stocks/backtesting

* fix: converted top to limit arguments

* fix: refactor disc controller, updated docs

* fix: refactor dd_controller and docs

* fix: refactor nft controller and docs

* fix: pass test temporarily, onchain controller refactor, docs

* fix: overview controller refactor, fix quit command

* fix: linting issues

* fix: refacot ta controller

* fix: refactor pred controller

* fix: linting

* fix: crypto controller

* fix: added tests, updated all controlers

* fix: menu print renamed from stocks to crypto controller, added tests

* fix: fix faster function execution

* Fix annoying pylint C0201 error

* fix: renamed commands to long format

* DISC Cleanup (#1058)

* Get rid/cleanup of marketbeat functions

* Get rid of last unused disc function + fix related tests

* fix test

* Dividend History Chart (#1053)

* Dividend History Chart

* changed to default number 10

* changed divs flag default to false

* updating the expected result for test_fa_controller.

* else statement

* fix: added load on dd

* fix: added autocompletion in all commands with argument choices

* fix: linting

* Workflows + Stocks > Insider (#1061)

* Updating code : stocks/insider remove unused file

* Updating workflows : tests - set mypy==0.910

* fix: linting

* Fixing Linting (#1062)

* Updating code : fixing linting

mypy message = Too many arguments for <lambda>

* Updating workflows : allow newest mypy version

* Updating code : apply black

* Updating code : black

* fix: updated predict controller help to match stocks

* Ux: portfolio (#1063)

* First step of portfolio UX

* Add all broker controllers to UX stuff

* UX Improvement for PO

Co-authored-by: Didier Rodrigues Lopes <dro.lopes@campus.fct.unl.pt>

* UX Improvement for resources (#1064)

Co-authored-by: Didier Rodrigues Lopes <dro.lopes@campus.fct.unl.pt>

* UX: stock/bt (#1065)

* UX: stocks>qa (#1068)

* fix mypy lambda

* skip tests for now

* remove unused paths.py

* rename tests/simulator to tests/e2e

* refactor options/screener menu

* UX : move `gst` files location

* fix stocks_controller

* UX upgrade of the Forex menu (#1048)

* Add basic forex menu test

* Add forex routes to terminal paths

* Remove commands from paths because they're not menus

* WIP Refactor forex controller

* refactor crypto controller

* Remove commented code

* Improve user output with printed text

* Add "help" back to the list of choices

* Enable the command queue for the forex controller

* refactor crypto/defi controller

* Fix candle chart blocking the terminal

* Black

* Return help and reset to the list of known args

* added simple test and fixes not returning queue in commands

* Add currency codes loading to the av_model

* Return the queue in edge cases (when commands don't process)

* Split select into to and from to enable autocompletion

* Style text output

* Stop printing help after the execution of every command

* Update test scenarios

* Black

* fix: converted top to limit arguments

* fix: refactor disc controller, updated docs

* Delete oanda the old view file that was moved into the oanda menu

* fix: refactor dd_controller and docs

* fix: refactor nft controller and docs

* fix: pass test temporarily, onchain controller refactor, docs

* fix: overview controller refactor, fix quit command

* fix: linting issues

* fix: refacot ta controller

* fix: refactor pred controller

* fix: linting

* fix: crypto controller

* fix: added tests, updated all controlers

* fix: menu print renamed from stocks to crypto controller, added tests

* fix: fix faster function execution

* Implement basic UX improvement of the oanda controller

* Update the forex controller

* Fix annoying pylint C0201 error

* Add latest convention changes to the forex controller

* fix: renamed commands to long format

* fix: added load on dd

* Merge the latest updates of the controller design

* fix: added autocompletion in all commands with argument choices

* fix: linting

* fix: linting

* fix: updated predict controller help to match stocks

* Update the forex menu e2e test scripts

* Add check_non_negative_float helper

* Update the controller with the latest changes to the convention

* Update the currency pair loading mechanism

* Move data models logic to the models file

* Update update oanda views to be interact with the models

* Upgrade oanda controller

* Remove raw stocks ba and eda code from the forex menu

* Update "help wanted" banner for ba and eda in forex menu

* Remove remainders of stocks ba and eda from forex context

* Improve typing, docstrings and error messages

* Remove stocks ba and eda code that was merged from other ux branches

* Add fixes that prevented incorrect argument handling

* Remove -argument from the candles call
If there is an expected agr `-g` and you put an incorrect argument
`-argument` the difflib will interpret `argument` as `g`

Co-authored-by: jose-donato <zmcdonato@gmail.com>
Co-authored-by: Didier Rodrigues Lopes <dro.lopes@campus.fct.unl.pt>

* fix reset for menus that require ticker

* UX improvement on research menu

* skip insider controller test

* initial refactoring of insider_controller

* improve insider controller ux part 2

* finish refactoring ins controller ux menu

* fix bug of when commands in queue have args

* skip test

* comment test stuff

* UX: stocks>qa (#1071)

* refactor return self.queue

* fix return None

* minor fix

* minor tabulate fix

* Get rid of leading '/' in terminal and add some newlines on quits. (#1074)

* Get rid of leading '/' in terminal and add some newlines on quits.

* print crypto help on menu entry

* looks like a format issue here...

* newlines on quit

* remove unnecessary line break

* fix pylint issue

* refactor

* refactor

* allow portfolio optimization to quit context

* add exit at end of scripts

* improve dd_controller

* test ux in stocks/dd menu

* Ux upgrade for reports context (#1075)

* Refactor reports and upgrade them with the latest controller convention

* Add test scenarios for the reports context

* test ux stocks/ta

* ux branch scripts/ta

* improvement on main param

* Upgrades for the UX upgrade for the fa and fa/fmp menus (#1076)

* Change --num to --limit

* Remove inconsistent/unreliable functions from the choices list

* Improve docstring compliance with numpy doc format

* Fix hardcoded class id in dcf scrapper

* Fix annoying pylint error in discordbot that was blocking the hook

* Add test scenarios for the fa and fa/fmp menus

* Fix forgotten replacement of --num to --limit

* Fix tests

* Return some excluded commands to the choices list

* Fix dd report to reflect the updates

* Fix set function not returning the queue

* Update gst test files

* fix exit on ca from another submenu

* fix changes of options/screener

* fix dps controller

* fix fa when running out of AV calls

* Fix TVL plot locking interpreter during defi test

* Add exception handling for broken coingecko scrapper

* skip fa/av test that fails

* fix dps tests

Co-authored-by: Chavithra PARANA <chavithra@gmail.com>
Co-authored-by: jmaslek <jmaslek11@gmail.com>
Co-authored-by: jose-donato <zmcdonato@gmail.com>
Co-authored-by: jose-donato <43375532+jose-donato@users.noreply.github.com>
Co-authored-by: Theodore Aptekarev <aptekarev@gmail.com>
Co-authored-by: Danglewood <85772166+deeleeramone@users.noreply.github.com>
This commit is contained in:
Didier Rodrigues Lopes
2021-12-21 12:19:23 +00:00
committed by GitHub
parent 77428f81b0
commit a85758c9ec
240 changed files with 21477 additions and 17517 deletions

View File

@@ -16,6 +16,8 @@ from gamestonk_terminal.stocks.screener import finviz_view
from gamestonk_terminal.stocks.screener import finviz_model
from gamestonk_terminal.helper_funcs import plot_autoscale
# pylint:disable=no-member
async def historical_command(ctx, signal="most_volatile", start=""):
"""Displays historical price comparison between similar companies [Yahoo Finance]"""
@@ -31,7 +33,7 @@ async def historical_command(ctx, signal="most_volatile", start=""):
screen = ticker.Ticker()
if signal in list(finviz_model.d_signals.keys()):
if signal in finviz_model.d_signals:
screen.set_filter(signal=finviz_model.d_signals[signal])
else:

View File

@@ -1,3 +1,4 @@
# pylint: disable=W0612
from datetime import datetime, timedelta
import asyncio
import discord

View File

@@ -6,6 +6,8 @@ import pandas as pd
import pmdarima
from statsmodels.tsa.arima.model import ARIMA
ICS = ["aic", "aicc", "bic", "hqic", "oob"]
def get_arima_model(
values: Union[pd.Series, pd.DataFrame],

View File

@@ -7,6 +7,9 @@ import numpy as np
import pandas as pd
from statsmodels.tsa.holtwinters import ExponentialSmoothing
TRENDS = ["N", "A", "Ad"]
SEASONS = ["N", "A", "M"]
def get_exponential_smoothing_model(
data: Union[pd.Series, np.ndarray],

View File

@@ -6,6 +6,8 @@ import pandas as pd
import numpy as np
from scipy.stats import norm
DISTRIBUTIONS = ["normal", "lognormal"]
def get_mc_brownian(
data: Union[pd.Series, np.ndarray], n_future: int, n_sims: int, use_log=True

View File

@@ -157,6 +157,7 @@ def parse_args(prog: str, description: str, other_args: List[str]):
"default: usually the same as false, uses env/TensorFlow default",
)
parser.add_argument(
"-l",
"--loops",
action="store",
dest="n_loops",

View File

@@ -2,6 +2,7 @@
__docformat__ = "numpy"
import os
import warnings
from typing import Any
import matplotlib.pyplot as plt
@@ -405,7 +406,8 @@ def display_normality(df: pd.DataFrame, target: str, export: str = ""):
"""
data = df[target]
normal = qa_model.get_normality(data)
stats1 = normal.copy()
stats1 = normal.copy().T
print(stats1)
stats1.loc[:, stats1.iloc[1, :] > 0.05] = stats1.loc[
:, stats1.iloc[1, :] > 0.05
].apply(lambda x: color_red(x[0]), axis=1)
@@ -443,6 +445,8 @@ def display_qqplot(name: str, df: pd.DataFrame, target: str):
target : str
Column in data to look at
"""
# Statsmodels has a UserWarning for marker kwarg-- which we dont use
warnings.filterwarnings(category=UserWarning, action="ignore")
data = df[target]
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
qqplot(data, stats.distributions.norm, fit=True, line="45", ax=ax)

View File

@@ -4,6 +4,9 @@ ___docformat__ = "numpy"
import pandas as pd
import pandas_ta as ta
WINDOW_LENGTHS = [20, 50]
WINDOW_LENGTHS2 = [10, 20]
def ema(
s_interval: str, df_stock: pd.DataFrame, length: int, offset: int

View File

@@ -4,6 +4,8 @@ __docformat__ = "numpy"
import pandas as pd
import pandas_ta as ta
MAMODES = ["ema", "sma", "wma", "hma", "zlma"]
def bbands(
s_interval: str, df_stock: pd.DataFrame, length: int, n_std: float, mamode: str

View File

@@ -6,9 +6,6 @@ env_files = [f for f in os.listdir() if f.endswith(".env")]
if env_files:
load_dotenv(env_files[0])
# Choose one of: stocks, options, crypto, economy, etf, portfolio, forex
DEFAULT_CONTEXT = ""
# Set to True to see full stack traces for debugging/error reporting
DEBUG_MODE = False

View File

@@ -1,27 +1,26 @@
"""Cryptocurrency Controller"""
"""Cryptocurrency Context Controller"""
__docformat__ = "numpy"
# pylint: disable=R0904, C0302, R1710, W0622
# pylint: disable=R0904, C0302, R1710, W0622, C0201
import argparse
import difflib
import matplotlib.pyplot as plt
from typing import List, Union
import pandas as pd
from colorama import Style
from prompt_toolkit.completion import NestedCompleter
from binance.client import Client
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import (
EXPORT_BOTH_RAW_DATA_AND_FIGURES,
EXPORT_ONLY_RAW_DATA_ALLOWED,
get_flair,
parse_known_args_and_warn,
check_positive,
system_clear,
try_except,
MENU_GO_BACK,
MENU_QUIT,
MENU_RESET,
)
from gamestonk_terminal.menu import session
from gamestonk_terminal.cryptocurrency.due_diligence import (
coinpaprika_view,
binance_view,
@@ -30,31 +29,42 @@ from gamestonk_terminal.cryptocurrency.due_diligence import (
binance_model,
coinbase_model,
)
from gamestonk_terminal.cryptocurrency.cryptocurrency_helpers import (
FIND_KEYS,
load,
find,
load_ta_data,
plot_chart,
)
import gamestonk_terminal.config_terminal as cfg
# pylint: disable=import-outside-toplevel
CRYPTO_SOURCES = {
"bin": "Binance",
"cg": "CoinGecko",
"cp": "CoinPaprika",
"cb": "Coinbase",
}
class CryptoController:
CHOICES = [
"?",
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMAND = [
CHOICES_COMMANDS = [
"headlines",
"chart",
"load",
@@ -63,23 +73,16 @@ class CryptoController:
CHOICES_MENUS = ["ta", "dd", "ov", "disc", "onchain", "defi", "nft", "pred"]
SOURCES = {
"bin": "Binance",
"cg": "CoinGecko",
"cp": "CoinPaprika",
"cb": "Coinbase",
}
DD_VIEWS_MAPPING = {
"cg": pycoingecko_view,
"cp": coinpaprika_view,
"bin": binance_view,
}
CHOICES += CHOICES_COMMAND
CHOICES += CHOICES_COMMANDS
CHOICES += CHOICES_MENUS
def __init__(self):
def __init__(self, queue: List[str] = None):
"""CONSTRUCTOR"""
self.crypto_parser = argparse.ArgumentParser(add_help=False, prog="crypto")
@@ -91,41 +94,50 @@ class CryptoController:
self.current_currency = ""
self.source = ""
self.completer: Union[None, NestedCompleter] = None
if session and gtff.USE_PROMPT_TOOLKIT:
choices: dict = {c: {} for c in self.CHOICES}
choices["load"]["--source"] = {c: {} for c in CRYPTO_SOURCES.keys()}
choices["find"]["--source"] = {c: {} for c in CRYPTO_SOURCES.keys()}
choices["find"]["-k"] = {c: {} for c in FIND_KEYS}
choices["headlines"] = {c: {} for c in finbrain_crypto_view.COINS}
self.completer = NestedCompleter.from_nested_dict(choices)
if queue:
self.queue = queue
else:
self.queue = list()
def print_help(self):
"""Print help"""
help_text = """
What do you want to do?
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon the program
reset reset terminal and reload configs
load load a specific cryptocurrency for analysis
find alternate way to search for coins
"""
help_text += (
f"\nCoin: {self.current_coin}" if self.current_coin != "" else "\nCoin: ?"
)
help_text += (
f"\nSource: {self.SOURCES.get(self.source, '?')}\n"
f"\nSource: {CRYPTO_SOURCES.get(self.source, '?')}\n"
if self.source != ""
else "\nSource: ?\n"
)
help_text += """
chart view a candle chart for a specific cryptocurrency
headlines crypto sentiment from 15+ major news headlines [Finbrain]
"""
dim = Style.DIM if not self.current_coin else ""
help_text += f"""
load load a specific cryptocurrency for analysis
chart view a candle chart for a specific cryptocurrency
find alternate way to search for coins
headlines crypto sentiment from 15+ major news headlines [Finbrain]
> disc discover trending cryptocurrencies, e.g.: top gainers, losers, top sentiment
> ov overview of the cryptocurrencies, e.g.: market cap, DeFi, latest news, top exchanges, stables{dim}
> dd due-diligence for loaded coin, e.g.: coin information, social media, market stats
> ta technical analysis for loaded coin, e.g.: ema, macd, rsi, adx, bbands, obv
> pred prediction techniques e.g.: regression, arima, rnn, lstm, conv1d, monte carlo
{Style.RESET_ALL if not self.current_coin else ""}
> onchain information on different blockchains, e.g.: eth gas fees, active asset addresses, whale alerts
> defi decentralized finance information, e.g.: dpi, llama, tvl, lending, borrow, funding
> nft non-fungible tokens, e.g.: today drops
"""
> disc discover trending cryptocurrencies, e.g.: top gainers, losers, top sentiment
> ov overview of the cryptocurrencies, e.g.: market cap, DeFi, latest news, top exchanges, stables
> onchain information on different blockchains, e.g.: eth gas fees, whale alerts, DEXes info
> defi decentralized finance information, e.g.: dpi, llama, tvl, lending, borrow, funding
> nft non-fungible tokens, e.g.: today drops{dim}
> dd due-diligence for loaded coin, e.g.: coin information, social media, market stats
> ta technical analysis for loaded coin, e.g.: ema, macd, rsi, adx, bbands, obv
> pred prediction techniques e.g.: regression, arima, rnn, lstm, conv1d, monte carlo{Style.RESET_ALL if not self.current_coin else ""}
""" # noqa
print(help_text)
def switch(self, an_input: str):
@@ -133,28 +145,41 @@ What do you want to do?
Returns
-------
MENU_GO_BACK, MENU_QUIT, MENU_RESET
MENU_GO_BACK - Show main context menu again
MENU_QUIT - Quit terminal
MENU_RESET - Reset terminal and go back to same previous menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.crypto_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -162,21 +187,44 @@ What do you want to do?
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return MENU_GO_BACK
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - exit the program"""
return MENU_QUIT
"""Process quit menu command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit"]
def call_reset(self, _):
"""Process Reset command - exit the program"""
return MENU_RESET
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "crypto")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "reset", "crypto"]
def call_load(self, other_args):
"""Process load command"""
@@ -216,26 +264,26 @@ What do you want to do?
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
self.current_coin, self.source = self.current_coin, None
return
if ns_parser:
source = ns_parser.source
source = ns_parser.source
for arg in ["--source", source]:
if arg in other_args:
other_args.remove(arg)
for arg in ["--source", source]:
if arg in other_args:
other_args.remove(arg)
self.current_coin, self.source, self.symbol = load(
coin=ns_parser.coin, source=ns_parser.source
)
self.current_coin, self.source, self.symbol = load(
coin=ns_parser.coin, source=ns_parser.source
)
return self.queue
except Exception as e:
print(e, "\n")
self.current_coin, self.source = self.current_coin, None
return self.queue
except TypeError:
print("Couldn't load data\n")
return self.queue
def call_chart(self, other_args):
"""Process chart command"""
@@ -381,37 +429,41 @@ What do you want to do?
)
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if self.source in ["bin", "cb"]:
limit = ns_parser.limit
interval = ns_parser.interval
days = 0
else:
limit = 0
interval = "1day"
days = ns_parser.days
plot_chart(
coin=self.current_coin,
limit=limit,
interval=interval,
days=days,
currency=ns_parser.vs,
source=self.source,
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES
)
if ns_parser:
if self.source in ["bin", "cb"]:
limit = ns_parser.limit
interval = ns_parser.interval
days = 0
else:
limit = 0
interval = "1day"
days = ns_parser.days
plot_chart(
coin=self.current_coin,
limit=limit,
interval=interval,
days=days,
currency=ns_parser.vs,
source=self.source,
)
return self.queue
except Exception as e:
print(e, "\n")
return self.queue
else:
print(
"No coin selected. Use 'load' to load the coin you want to look at.\n"
)
return self.queue
@try_except
def call_ta(self, other_args):
"""Process ta command"""
from gamestonk_terminal.cryptocurrency.technical_analysis import ta_controller
@@ -530,7 +582,7 @@ What do you want to do?
print(
f"Couldn't find any quoted coins for provided symbol {self.current_coin}"
)
return
return self.queue
parser.add_argument(
"--vs",
@@ -578,7 +630,7 @@ What do you want to do?
print(
f"Couldn't find any quoted coins for provided symbol {self.current_coin}"
)
return
return self.queue
parser.add_argument(
"--vs",
@@ -611,84 +663,67 @@ What do you want to do?
try:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
if self.source in ["bin", "cb"]:
limit = ns_parser.limit
interval = ns_parser.interval
days = 0
else:
limit = 0
interval = "1day"
days = ns_parser.days
if self.source in ["bin", "cb"]:
limit = ns_parser.limit
interval = ns_parser.interval
days = 0
else:
limit = 0
interval = "1day"
days = ns_parser.days
self.current_df, self.current_currency = load_ta_data(
coin=self.current_coin,
source=self.source,
currency=ns_parser.vs,
days=days,
limit=limit,
interval=interval,
)
self.current_df, self.current_currency = load_ta_data(
coin=self.current_coin,
source=self.source,
currency=ns_parser.vs,
days=days,
limit=limit,
interval=interval,
)
except Exception as e:
print(e, "\n")
return self.queue
if self.current_currency != "" and not self.current_df.empty:
try:
quit = ta_controller.menu(
return ta_controller.menu(
stock=self.current_df,
ticker=self.current_coin,
start=self.current_df.index[0],
interval="",
queue=self.queue,
)
print("")
if quit is not None:
if quit is True:
return quit
self.print_help()
except (ValueError, KeyError) as e:
print(e)
return self.queue
else:
return
return self.queue
else:
print(
"No coin selected. Use 'load' to load the coin you want to look at.\n"
)
return self.queue
def call_disc(self, _):
"""Process disc command"""
from gamestonk_terminal.cryptocurrency.discovery import discovery_controller
ret = discovery_controller.menu()
if ret is False:
self.print_help()
else:
return True
return discovery_controller.menu(queue=self.queue)
def call_ov(self, _):
"""Process ov command"""
from gamestonk_terminal.cryptocurrency.overview import overview_controller
ret = overview_controller.menu()
if ret is False:
self.print_help()
else:
return True
return overview_controller.menu(queue=self.queue)
def call_defi(self, _):
"""Process defi command"""
from gamestonk_terminal.cryptocurrency.defi import defi_controller
ret = defi_controller.menu()
if ret is False:
self.print_help()
else:
return True
return defi_controller.menu(queue=self.queue)
@try_except
def call_headlines(self, other_args):
@@ -710,38 +745,29 @@ What do you want to do?
choices=finbrain_crypto_view.COINS,
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
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_BOTH_RAW_DATA_AND_FIGURES
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
finbrain_crypto_view.display_crypto_sentiment_analysis(
coin=ns_parser.coin, export=ns_parser.export
)
if ns_parser:
finbrain_crypto_view.display_crypto_sentiment_analysis(
coin=ns_parser.coin, export=ns_parser.export
)
return self.queue
def call_dd(self, _):
"""Process dd command"""
if self.current_coin:
from gamestonk_terminal.cryptocurrency.due_diligence import dd_controller
dd = dd_controller.menu(self.current_coin, self.source, self.symbol)
if dd is False:
self.print_help()
else:
return True
else:
print(
"No coin selected. Use 'load' to load the coin you want to look at.\n"
return dd_controller.menu(
self.current_coin, self.source, self.symbol, queue=self.queue
)
print("No coin selected. Use 'load' to load the coin you want to look at.\n")
return self.queue
def call_pred(self, _):
"""Process pred command"""
@@ -749,11 +775,12 @@ What do you want to do?
print(
"No coin loaded. Please use `load <coin>` to access prediction menu\n."
)
return
return self.queue
if self.source != "cg":
print("Currently only supports CoinGecko source.\n")
return
return self.queue
if self.current_coin:
from gamestonk_terminal.cryptocurrency.prediction_techniques import (
pred_controller,
@@ -762,40 +789,25 @@ What do you want to do?
cryptocurrency_helpers as c_help,
)
pred = pred_controller.menu(
return pred_controller.menu(
self.current_coin,
c_help.load_cg_coin_data(self.current_coin, "USD", 365, "1D"),
self.queue,
)
if pred is False:
self.print_help()
else:
return True
else:
print(
"No coin selected. Use 'load' to load the coin you want to look at.\n"
)
print("No coin selected. Use 'load' to load the coin you want to look at.\n")
return self.queue
def call_onchain(self, _):
"""Process onchain command"""
from gamestonk_terminal.cryptocurrency.onchain import onchain_controller
ret = onchain_controller.menu()
if ret is False:
self.print_help()
else:
return True
return onchain_controller.menu(queue=self.queue)
def call_nft(self, _):
"""Process nft command"""
from gamestonk_terminal.cryptocurrency.nft import nft_controller
ret = nft_controller.menu()
if ret is False:
self.print_help()
else:
return True
return nft_controller.menu(queue=self.queue)
@try_except
def call_find(self, other_args):
@@ -812,7 +824,7 @@ What do you want to do?
It will search for coin that has similar name to polka and display top 25 matches.
-c, --coin stands for coin - you provide here your search query
-k, --key it's a searching key. You can search by symbol, id or name of coin
-t, --top it displays top N number of records.""",
-l, --limit it displays top N number of records.""",
)
parser.add_argument(
@@ -830,83 +842,119 @@ What do you want to do?
dest="key",
help="Specify by which column you would like to search: symbol, name, id",
type=str,
choices=["id", "symbol", "name"],
choices=FIND_KEYS,
default="symbol",
)
parser.add_argument(
"-t",
"--top",
"-l",
"--limit",
default=10,
dest="top",
help="Limit of records",
dest="limit",
help="Number of records to display",
type=check_positive,
)
parser.add_argument(
"--source",
dest="source",
choices=["cp", "cg", "bin", "cb"],
choices=CRYPTO_SOURCES.keys(),
default="cg",
help="Source of data.",
type=str,
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
if other_args:
if not other_args[0][0] == "-":
other_args.insert(0, "-c")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
find(
coin=ns_parser.coin,
source=ns_parser.source,
key=ns_parser.key,
top=ns_parser.top,
export=ns_parser.export,
if other_args and not other_args[0][0] == "-":
other_args.insert(0, "-c")
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
find(
coin=ns_parser.coin,
source=ns_parser.source,
key=ns_parser.key,
top=ns_parser.limit,
export=ns_parser.export,
)
return self.queue
def menu():
crypto_controller = CryptoController()
crypto_controller.call_help(None)
plt.close("all")
def menu(queue: List[str] = None):
crypto_controller = CryptoController(queue)
first = True
while True:
# There is a command in the queue
if crypto_controller.queue and len(crypto_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if crypto_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(crypto_controller.queue) > 1:
return crypto_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = crypto_controller.queue[0]
crypto_controller.queue = crypto_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 crypto_controller.CHOICES_COMMANDS
):
print(f"{get_flair()} /crypto/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in crypto_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (crypto)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (crypto)> ")
# Display help menu when entering on this menu from a level above
if first:
crypto_controller.print_help()
first = False
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and crypto_controller.completer:
an_input = session.prompt(
f"{get_flair()} /crypto/ $ ",
completer=crypto_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /crypto/ $ ")
try:
process_input = crypto_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
crypto_controller.queue = crypto_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
similar_cmd = difflib.get_close_matches(
an_input, crypto_controller.CHOICES, n=1, cutoff=0.7
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,
crypto_controller.CHOICES,
n=1,
cutoff=0.7,
)
if similar_cmd:
print(f"Did you mean '{similar_cmd[0]}'?\n")
continue
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 = ""
crypto_controller.queue = []
print("\n")
continue
print(f" Replacing by '{an_input}'.")
crypto_controller.queue.insert(0, an_input)
else:
print("\n")
an_input = ""
crypto_controller.queue = []

View File

@@ -39,6 +39,8 @@ from gamestonk_terminal import feature_flags as gtff
# TODO: Improve implementation of coin loading
# Currently adding this function to helpers for implementing prediction menu
INTERVALS = ["1H", "3H", "6H", "1D"]
def load_cg_coin_data(
coin: str, currency: str = "USD", days: int = 365, sampling: str = "1D"
@@ -204,7 +206,7 @@ def load(
Tuple[Union[str, pycoingecko_model.Coin], str, str]
- str or Coin object for provided coin
- str with source of the loaded data. CoinGecko, CoinPaprika, or Binance
- str with
- str with symbol
"""
current_coin = "" # type: Optional[Any]
@@ -242,7 +244,11 @@ def load(
return current_coin, None, None
FIND_KEYS = ["id", "symbol", "name"]
# TODO: Find better algorithm then difflib.get_close_matches to find most similar coins
def find(source: str, coin: str, key: str, top: int, export: str) -> None:
"""Find similar coin by coin name,symbol or id.

View File

@@ -2,9 +2,12 @@
__docformat__ = "numpy"
import argparse
from typing import List
import difflib
from typing import List, Union
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal.cryptocurrency.defi import graph_model
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.menu import session
from gamestonk_terminal.helper_funcs import (
@@ -20,6 +23,7 @@ from gamestonk_terminal.helper_funcs import (
from gamestonk_terminal.cryptocurrency.defi import (
defirate_view,
defipulse_view,
llama_model,
llama_view,
substack_view,
graph_view,
@@ -31,10 +35,16 @@ class DefiController:
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = [
@@ -54,13 +64,27 @@ class DefiController:
CHOICES += CHOICES_COMMANDS
def __init__(self):
def __init__(self, queue: List[str] = None):
"""Constructor"""
self.defi_parser = argparse.ArgumentParser(add_help=False, prog="defi")
self.defi_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["llama"]["-s"] = {c: {} for c in llama_model.LLAMA_FILTERS}
choices["tokens"]["-s"] = {c: {} for c in graph_model.TOKENS_FILTERS}
choices["pairs"]["-s"] = {c: {} for c in graph_model.PAIRS_FILTERS}
choices["pools"]["-s"] = {c: {} for c in graph_model.POOLS_FILTERS}
choices["swaps"]["-s"] = {c: {} for c in graph_model.SWAPS_FILTERS}
self.completer = NestedCompleter.from_nested_dict(choices)
if queue:
self.queue = queue
else:
self.queue = list()
def switch(self, an_input: str):
"""Process and dispatch input
@@ -72,28 +96,40 @@ class DefiController:
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.defi_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -101,17 +137,50 @@ class DefiController:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, *_):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program"""
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "defi")
self.queue.insert(0, "crypto")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "reset", "crypto", "defi"]
@try_except
def call_dpi(self, other_args: List[str]):
@@ -127,11 +196,11 @@ class DefiController:
)
parser.add_argument(
"-t",
"--top",
dest="top",
"-l",
"--limit",
dest="limit",
type=check_positive,
help="top N number records",
help="Number of records to display",
default=15,
)
@@ -157,15 +226,14 @@ class DefiController:
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
defipulse_view.display_defipulse(
top=ns_parser.top,
sortby=ns_parser.sortby,
descend=ns_parser.descend,
export=ns_parser.export,
)
if ns_parser:
defipulse_view.display_defipulse(
top=ns_parser.limit,
sortby=ns_parser.sortby,
descend=ns_parser.descend,
export=ns_parser.export,
)
return self.queue
@try_except
def call_llama(self, other_args: List[str]):
@@ -181,11 +249,11 @@ class DefiController:
)
parser.add_argument(
"-t",
"--top",
dest="top",
"-l",
"--limit",
dest="limit",
type=check_positive,
help="top N number records",
help="Number of records to display",
default=10,
)
@@ -196,16 +264,7 @@ class DefiController:
type=str,
help="Sort by given column. Default: tvl",
default="tvl",
choices=[
"tvl",
"symbol",
"category",
"chains",
"change_1h",
"change_1d",
"change_7d",
"tvl",
],
choices=llama_model.LLAMA_FILTERS,
)
parser.add_argument(
@@ -228,16 +287,15 @@ class DefiController:
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
llama_view.display_defi_protocols(
top=ns_parser.top,
sortby=ns_parser.sortby,
descend=ns_parser.descend,
description=ns_parser.description,
export=ns_parser.export,
)
if ns_parser:
llama_view.display_defi_protocols(
top=ns_parser.limit,
sortby=ns_parser.sortby,
descend=ns_parser.descend,
description=ns_parser.description,
export=ns_parser.export,
)
return self.queue
@try_except
def call_tvl(self, other_args: List[str]):
@@ -253,11 +311,11 @@ class DefiController:
)
parser.add_argument(
"-t",
"--top",
dest="top",
"-l",
"--limit",
dest="limit",
type=check_positive,
help="top N number records",
help="Number of records to display",
default=10,
)
@@ -265,10 +323,9 @@ class DefiController:
parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES
)
if not ns_parser:
return
llama_view.display_defi_tvl(top=ns_parser.top, export=ns_parser.export)
if ns_parser:
llama_view.display_defi_tvl(top=ns_parser.limit, export=ns_parser.export)
return self.queue
@try_except
def call_funding(self, other_args: List[str]):
@@ -284,11 +341,11 @@ class DefiController:
)
parser.add_argument(
"-t",
"--top",
dest="top",
"-l",
"--limit",
dest="limit",
type=check_positive,
help="top N number records",
help="Number of records to display",
default=10,
)
@@ -304,12 +361,11 @@ class DefiController:
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
defirate_view.display_funding_rates(
top=ns_parser.top, current=ns_parser.current, export=ns_parser.export
)
if ns_parser:
defirate_view.display_funding_rates(
top=ns_parser.limit, current=ns_parser.current, export=ns_parser.export
)
return self.queue
@try_except
def call_borrow(self, other_args: List[str]):
@@ -325,11 +381,11 @@ class DefiController:
)
parser.add_argument(
"-t",
"--top",
dest="top",
"-l",
"--limit",
dest="limit",
type=check_positive,
help="top N number records",
help="Number of records to display",
default=10,
)
@@ -345,12 +401,11 @@ class DefiController:
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
defirate_view.display_borrow_rates(
top=ns_parser.top, current=ns_parser.current, export=ns_parser.export
)
if ns_parser:
defirate_view.display_borrow_rates(
top=ns_parser.limit, current=ns_parser.current, export=ns_parser.export
)
return self.queue
@try_except
def call_lending(self, other_args: List[str]):
@@ -366,11 +421,11 @@ class DefiController:
)
parser.add_argument(
"-t",
"--top",
dest="top",
"-l",
"--limit",
dest="limit",
type=check_positive,
help="top N number records",
help="Number of records to display",
default=15,
)
@@ -386,12 +441,11 @@ class DefiController:
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
defirate_view.display_lending_rates(
top=ns_parser.top, current=ns_parser.current, export=ns_parser.export
)
if ns_parser:
defirate_view.display_lending_rates(
top=ns_parser.limit, current=ns_parser.current, export=ns_parser.export
)
return self.queue
@try_except
def call_newsletter(self, other_args: List[str]):
@@ -407,11 +461,11 @@ class DefiController:
)
parser.add_argument(
"-t",
"--top",
dest="top",
"-l",
"--limit",
dest="limit",
type=check_positive,
help="top N number records",
help="Number of records to display",
default=10,
)
@@ -419,10 +473,11 @@ class DefiController:
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
substack_view.display_newsletters(top=ns_parser.top, export=ns_parser.export)
if ns_parser:
substack_view.display_newsletters(
top=ns_parser.limit, export=ns_parser.export
)
return self.queue
@try_except
def call_tokens(self, other_args: List[str]):
@@ -460,14 +515,7 @@ class DefiController:
type=str,
help="Sort by given column. Default: index",
default="index",
choices=[
"index",
"symbol",
"name",
"tradeVolumeUSD",
"totalLiquidity",
"txCount",
],
choices=graph_model.TOKENS_FILTERS,
)
parser.add_argument(
@@ -482,16 +530,15 @@ class DefiController:
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
graph_view.display_uni_tokens(
skip=ns_parser.skip,
limit=ns_parser.limit,
sortby=ns_parser.sortby,
descend=ns_parser.descend,
export=ns_parser.export,
)
if ns_parser:
graph_view.display_uni_tokens(
skip=ns_parser.skip,
limit=ns_parser.limit,
sortby=ns_parser.sortby,
descend=ns_parser.descend,
export=ns_parser.export,
)
return self.queue
@try_except
def call_stats(self, other_args: List[str]):
@@ -510,10 +557,9 @@ class DefiController:
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
graph_view.display_uni_stats(export=ns_parser.export)
if ns_parser:
graph_view.display_uni_stats(export=ns_parser.export)
return self.queue
@try_except
def call_pairs(self, other_args: List[str]):
@@ -529,11 +575,11 @@ class DefiController:
)
parser.add_argument(
"-t",
"--top",
dest="top",
"-l",
"--limit",
dest="limit",
type=check_positive,
help="Number of records",
help="Number of records to display",
default=10,
)
@@ -570,15 +616,7 @@ class DefiController:
type=str,
help="Sort by given column. Default: created",
default="created",
choices=[
"created",
"pair",
"token0",
"token1",
"volumeUSD",
"txCount",
"totalSupply",
],
choices=graph_model.PAIRS_FILTERS,
)
parser.add_argument(
@@ -593,18 +631,17 @@ class DefiController:
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
graph_view.display_recently_added(
top=ns_parser.top,
days=ns_parser.days,
min_volume=ns_parser.vol,
min_tx=ns_parser.tx,
sortby=ns_parser.sortby,
descend=ns_parser.descend,
export=ns_parser.export,
)
if ns_parser:
graph_view.display_recently_added(
top=ns_parser.limit,
days=ns_parser.days,
min_volume=ns_parser.vol,
min_tx=ns_parser.tx,
sortby=ns_parser.sortby,
descend=ns_parser.descend,
export=ns_parser.export,
)
return self.queue
@try_except
def call_pools(self, other_args: List[str]):
@@ -620,11 +657,11 @@ class DefiController:
)
parser.add_argument(
"-t",
"--top",
dest="top",
"-l",
"--limit",
dest="limit",
type=check_positive,
help="Number of records",
help="Number of records to display",
default=10,
)
@@ -635,15 +672,7 @@ class DefiController:
type=str,
help="Sort by given column. Default: volumeUSD",
default="volumeUSD",
choices=[
"volumeUSD",
"token0.name",
"token0.symbol",
"token1.name",
"token1.symbol",
"volumeUSD",
"txCount",
],
choices=graph_model.POOLS_FILTERS,
)
parser.add_argument(
@@ -658,15 +687,14 @@ class DefiController:
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
graph_view.display_uni_pools(
top=ns_parser.top,
sortby=ns_parser.sortby,
descend=ns_parser.descend,
export=ns_parser.export,
)
if ns_parser:
graph_view.display_uni_pools(
top=ns_parser.limit,
sortby=ns_parser.sortby,
descend=ns_parser.descend,
export=ns_parser.export,
)
return self.queue
@try_except
def call_swaps(self, other_args: List[str]):
@@ -682,11 +710,11 @@ class DefiController:
)
parser.add_argument(
"-t",
"--top",
dest="top",
"-l",
"--limit",
dest="limit",
type=check_positive,
help="Number of records",
help="Number of records to display",
default=10,
)
@@ -697,7 +725,7 @@ class DefiController:
type=str,
help="Sort by given column. Default: timestamp",
default="timestamp",
choices=["timestamp", "token0", "token1", "amountUSD"],
choices=graph_model.SWAPS_FILTERS,
)
parser.add_argument(
@@ -712,24 +740,19 @@ class DefiController:
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
graph_view.display_last_uni_swaps(
top=ns_parser.top,
sortby=ns_parser.sortby,
descend=ns_parser.descend,
export=ns_parser.export,
)
if ns_parser:
graph_view.display_last_uni_swaps(
top=ns_parser.limit,
sortby=ns_parser.sortby,
descend=ns_parser.descend,
export=ns_parser.export,
)
return self.queue
def print_help(self):
"""Print help"""
help_text = """
Decentralized Finance:
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon the program
Decentralized Finance Menu:
Overview:
llama DeFi protocols listed on DeFi Llama
@@ -739,42 +762,87 @@ Overview:
funding Funding reates - current or last 30 days average
borrow DeFi borrow rates - current or last 30 days average
lending DeFi ending rates - current or last 30 days average
Uniswap:
tokens Tokens trade-able on Uniswap
stats Base statistics about Uniswap
pairs Recently added pairs on Uniswap
pools Pools by volume on Uniswap
swaps Recent swaps done on Uniswap"""
print(help_text, "\n")
swaps Recent swaps done on Uniswap
"""
print(help_text)
def menu():
def menu(queue: List[str] = None):
"""Defi Menu"""
defi_controller = DefiController()
defi_controller.call_help(None)
defi_controller = DefiController(queue=queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if defi_controller.queue and len(defi_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if defi_controller.queue[0] in ("q", "..", "quit"):
if len(defi_controller.queue) > 1:
return defi_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = defi_controller.queue[0]
defi_controller.queue = defi_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 defi_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /crypto/defi/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in defi_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (crypto)>(defi)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (crypto)>(defi)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
defi_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and defi_controller.completer:
an_input = session.prompt(
f"{get_flair()} /crypto/defi/ $ ",
completer=defi_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /crypto/defi/ $ ")
try:
process_input = defi_controller.switch(an_input)
# Process the input command
defi_controller.queue = defi_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
continue
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,
defi_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 process_input is False:
return False
if candidate_input == an_input:
an_input = ""
defi_controller.queue = []
print("\n")
continue
if process_input is True:
return True
print(f" Replacing by '{an_input}'.")
defi_controller.queue.insert(0, an_input)
else:
print("\n")
an_input = ""
defi_controller.queue = []

View File

@@ -11,6 +11,39 @@ from gamestonk_terminal.cryptocurrency.dataframe_helpers import (
UNI_URL = "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2"
SWAPS_FILTERS = ["timestamp", "token0", "token1", "amountUSD"]
POOLS_FILTERS = [
"volumeUSD",
"token0.name",
"token0.symbol",
"token1.name",
"token1.symbol",
"volumeUSD",
"txCount",
]
TOKENS_FILTERS = [
"index",
"symbol",
"name",
"tradeVolumeUSD",
"totalLiquidity",
"txCount",
]
PAIRS_FILTERS = [
"created",
"pair",
"token0",
"token1",
"volumeUSD",
"txCount",
"totalSupply",
]
# TODO: convert USD values to int. otherwise sort by these columns won't work
def query_graph(url: str, query: str) -> dict:
"""Helper methods for querying graphql api. [Source: https://thegraph.com/en/]

View File

@@ -7,6 +7,17 @@ import requests
import pandas as pd
import numpy as np
LLAMA_FILTERS = [
"tvl",
"symbol",
"category",
"chains",
"change_1h",
"change_1d",
"change_7d",
"name",
]
def get_defi_protocols() -> pd.DataFrame:
"""Returns information about listed DeFi protocols, their current TVL and changes to it in the last hour/day/week.
@@ -57,7 +68,7 @@ def get_defi_tvl() -> pd.DataFrame:
pd.DataFrame
Historical values of total sum of Total Value Locked from all listed protocols.
"""
response = requests.get("https://api.llama.fi/charts")
response = requests.get("https://api.llama.fi/charts", timeout=5)
if response.status_code != 200:
raise Exception(f"Status code: {response.status_code}. Reason: {response.text}")
try:

View File

@@ -94,6 +94,9 @@ def display_defi_tvl(top: int, export: str = "") -> None:
df = df.tail(top)
df["totalLiquidityUSD"] = df["totalLiquidityUSD"] / 1_000_000_000
if gtff.USE_ION:
plt.ion()
plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI)
plt.plot(df["date"], df["totalLiquidityUSD"], "-ok", ms=2)

View File

@@ -5,47 +5,47 @@ others. The usage of the following commands along with an example will be exploi
[CoinGecko](#CoinGecko)
* [cg_coins](#cg_coins)
* show coins available on CoinGecko
* [cg_trending](#cg_trending)
* show trending coins on CoinGecko
* [cg_most_voted](#cg_most_voted)
* show most voted coins on CoinGecko
* [cg_top_volume](#cg_top_volume)
* show coins with highest volume on CoinGecko
* [cg_recently](#cg_recently)
* show recently added on CoinGecko
* [cg_sentiment](#cg_sentiment)
* show coins with most positive sentiment
* [cg_gainers](#cg_gainers)
* show top gainers - coins which price gained the most in given period
* [cg_losers](#cg_losers)
* show top losers - coins which price dropped the most in given period
* [cg_yfarms](#cg_yfarms)
* show top Yield Farms
* [cg_top_defi](#cg_top_defi)
* show top DeFi Protocols
* [cg_top_dex](#cg_top_dex)
* show top Decentralized Exchanges
* [cg_top_nft](#cg_top_nft)
* show top Non Fungible Tokens
- [cg_coins](#cg_coins)
- show coins available on CoinGecko
- [cg_trending](#cg_trending)
- show trending coins on CoinGecko
- [cg_most_voted](#cg_most_voted)
- show most voted coins on CoinGecko
- [cg_top_volume](#cg_top_volume)
- show coins with highest volume on CoinGecko
- [cg_recently](#cg_recently)
- show recently added on CoinGecko
- [cg_sentiment](#cg_sentiment)
- show coins with most positive sentiment
- [cg_gainers](#cg_gainers)
- show top gainers - coins which price gained the most in given period
- [cg_losers](#cg_losers)
- show top losers - coins which price dropped the most in given period
- [cg_yfarms](#cg_yfarms)
- show top Yield Farms
- [cg_top_defi](#cg_top_defi)
- show top DeFi Protocols
- [cg_top_dex](#cg_top_dex)
- show top Decentralized Exchanges
- [cg_top_nft](#cg_top_nft)
- show top Non Fungible Tokens
[CoinPaprika](#CoinPaprika)
* [cp_coins](#cp_coins)
* show coins available on CoinPaprika
* [cp_search](#cp_search)
* show search on CoinPaprika
- [cp_coins](#cp_coins)
- show coins available on CoinPaprika
- [cp_search](#cp_search)
- show search on CoinPaprika
[CoinMarketCap](#CoinMarketCap)
* [cmc_top](#cmc_top)
* show top coin from coinmarketcap.com
- [cmc_top](#cmc_top)
- show top coin from coinmarketcap.com
[Binance](#Binance)
* [bin_coins](#bin_coins)
* show coins available on Binance
- [bin_coins](#bin_coins)
- show coins available on Binance
# CoinGecko <a name="CoinGecko"></a>
@@ -59,13 +59,13 @@ Display all available coins in coingecko. You can search with pagination mechani
or you can just search by letter like `cg_coins --letter d --key name --top 10` it will display top 10 matches for coins
which name starts with letter `d` and search will be done in column `name`
* -s/--skip - number of records to skip. Default is 0. There are thousands of coins, so there is mechanism to paginate
- -s/--skip - number of records to skip. Default is 0. There are thousands of coins, so there is mechanism to paginate
through them. e.q if you want to see only records from 500-750 you should use `cg_coins --skip 500 --top 250` or if
you want to see 1200-1880 you should use `coins --skip 1200 --top 680`.
* -t/--top - number of news to display. One page of news contains 25 news, so to get 250 news script needs to scrape 10
- -t/--top - number of news to display. One page of news contains 25 news, so to get 250 news script needs to scrape 10
pages (it can take some time). Default 100. E.g `news --top 150`
* -l/--letter - first letters of coin by which you want to search
* -k/--key - search key. With this parameter you can specify in which column you would like to search. Choose on from
- -l/--letter - first letters of coin by which you want to search
- -k/--key - search key. With this parameter you can specify in which column you would like to search. Choose on from
`name, id, symbol`
![image](https://user-images.githubusercontent.com/275820/123538885-26428980-d737-11eb-9418-e3f80511786d.png)
@@ -73,75 +73,75 @@ which name starts with letter `d` and search will be done in column `name`
## cg_trending <a name="cg_trending"></a>
```text
usage: cg_trending [-t --top] [-s --sortby] [--descend] [-l --links]
usage: cg_trending [-t --top] [-s --sortby] [--descend] [-u --urls]
```
Shows most trending coins on CoinGecko.
* -t/--top - number of coins to display. To display top 10 coins: `cg_trending --top 10`
* -s/--sortby - sort by given column. You can chose on from `rank, name, price_usd, price_btc`
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls e.g. `cg_trending --top 10 --links`
- -t/--top - number of coins to display. To display top 10 coins: `cg_trending --top 10`
- -s/--sortby - sort by given column. You can chose on from `rank, name, price_usd, price_btc`
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls e.g. `cg_trending --top 10 --urls`
![image](https://user-images.githubusercontent.com/275820/124305015-c8fa6e00-db64-11eb-81de-4e8b943c6c0d.png)
## cg_most_voted <a name="cg_most_voted"></a>
```text
usage: cg_most_voted [-t --top] [-s --sortby] [--descend] [-l --links]
usage: cg_most_voted [-t --top] [-s --sortby] [--descend] [-u --urls]
```
Shows most voted coins on CoinGecko.
* -t/--top - number of coins to display. To display top 10 coins: `cg_most_voted --top 10`
* -s/--sortby - sort by given column. You can chose on from `rank, name, price_usd, price_btc`
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls e.g. `cg_most_voted --top 10 --links`
- -t/--top - number of coins to display. To display top 10 coins: `cg_most_voted --top 10`
- -s/--sortby - sort by given column. You can chose on from `rank, name, price_usd, price_btc`
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls e.g. `cg_most_voted --top 10 --urls`
![image](https://user-images.githubusercontent.com/275820/123538516-8cc6a800-d735-11eb-85ee-e3f2141a54fb.png)
## cg_most_visited <a name="cg_most_visited"></a>
```text
usage: cg_most_visited [-t --top] [-s --sortby] [--descend] [-l --links]
usage: cg_most_visited [-t --top] [-s --sortby] [--descend] [-u --urls]
```
Shows most visited coins on CoinGecko.
* -t/--top - number of coins to display. To display top 10 coins: `cg_most_visited --top 10`
* -s/--sortby - sort by given column. You can chose on from `rank, name, price_usd, price_btc`
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls e.g. `cg_most_visited --top 10 --links`
- -t/--top - number of coins to display. To display top 10 coins: `cg_most_visited --top 10`
- -s/--sortby - sort by given column. You can chose on from `rank, name, price_usd, price_btc`
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls e.g. `cg_most_visited --top 10 --urls`
![image](https://user-images.githubusercontent.com/275820/123538524-9cde8780-d735-11eb-9827-c02ea8bd5db7.png)
## cg_sentiment <a name="cg_sentiment"></a>
```text
usage: cg_sentiment [-t --top] [-s --sortby] [--descend] [-l --links]
usage: cg_sentiment [-t --top] [-s --sortby] [--descend] [-u --urls]
```
Shows coins with the most positive sentiment on CoinGecko
* -t/--top - number of coins to display. To display top 10 coins: `cg_sentiment --top 10`
* -s/--sortby - sort by given column. You can chose on from `rank, name, price_usd, price_btc`
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls e.g. `cg_sentiment --top 10 --links`
- -t/--top - number of coins to display. To display top 10 coins: `cg_sentiment --top 10`
- -s/--sortby - sort by given column. You can chose on from `rank, name, price_usd, price_btc`
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls e.g. `cg_sentiment --top 10 --urls`
![image](https://user-images.githubusercontent.com/275820/123538553-c5ff1800-d735-11eb-9fef-ef490184c6d8.png)
## cg_recently <a name="cg_recently"></a>
```text
usage: cg_recently [-t --top] [-s --sortby] [--descend] [-l --links]
usage: cg_recently [-t --top] [-s --sortby] [--descend] [-u --urls]
```
Shows coins which were recently added on CoinGecko
* -t/--top - number of coins to display. To display top 10 coins: `cg_recently --top 10 --sortby rank`
* -s/--sortby - sort by given column. You can chose on from `rank, name, symbol, price, change_24h, change_1h, added`
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls e.g. `cg_recently --top 10 --links`
- -t/--top - number of coins to display. To display top 10 coins: `cg_recently --top 10 --sortby rank`
- -s/--sortby - sort by given column. You can chose on from `rank, name, symbol, price, change_24h, change_1h, added`
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls e.g. `cg_recently --top 10 --urls`
![image](https://user-images.githubusercontent.com/275820/124305122-f47d5880-db64-11eb-9048-b495248723d3.png)
@@ -153,44 +153,44 @@ usage: cg_top_volume [-t --top] [-s --sortby] [--descend]
Shows coins with the highest transactions volume on CoinGecko
* -t/--top - number of coins to display. To display top 10 coins: `cg_top_volume --top 10 --sortby rank`
* -s/--sortby - sort by given column. You can chose on from
- -t/--top - number of coins to display. To display top 10 coins: `cg_top_volume --top 10 --sortby rank`
- -s/--sortby - sort by given column. You can chose on from
`rank, name, symbol, price, change_1h, change_24h, change_7d , volume_24h`
* --descend - flag to sort in descending order (lowest first)
- --descend - flag to sort in descending order (lowest first)
![image](https://user-images.githubusercontent.com/275820/124305188-0a8b1900-db65-11eb-953f-febc32500ff9.png)
## cg_gainers <a name="cg_gainers"></a>
```text
usage: cg_gainers [-p --period] [-t --top] [-s --sortby] [--descend] [-l --links]
usage: cg_gainers [-p --period] [-t --top] [-s --sortby] [--descend] [-u --urls]
```
Shows largest gainers - coins which gain the most in given period.
* -p/--period - time period in which coins gained the most in price. One from `1h, 24h, 7d, 14d, 30d, 60d, 1y`.
- -p/--period - time period in which coins gained the most in price. One from `1h, 24h, 7d, 14d, 30d, 60d, 1y`.
If you want to see top gainers in last 24h `cg_gainers -p 24h` or top gainers in last 60days `cg_gainers -p 60d`
* -t/--top - number of coins to display. To display top 10 coins: `cg_gainers --top 10 --sortby rank`
* -s/--sortby - sort by given column. You can chose on from `rank, name, symbol, price, change, volume`.
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls e.g. `cg_gainers --period 24h --links`
- -t/--top - number of coins to display. To display top 10 coins: `cg_gainers --top 10 --sortby rank`
- -s/--sortby - sort by given column. You can chose on from `rank, name, symbol, price, change, volume`.
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls e.g. `cg_gainers --period 24h --urls`
![image](https://user-images.githubusercontent.com/275820/124305218-137bea80-db65-11eb-914c-865b56cf33ea.png)
## cg_losers <a name="cg_losers"></a>
```text
usage: cg_losers [-p --period] [-t --top] [-s --sortby] [--descend] [-l --links]
usage: cg_losers [-p --period] [-t --top] [-s --sortby] [--descend] [-u --urls]
```
Shows largest losers - coins which lost the most in given period.
* -p/--period - time period in which coins lost the most in price. One from `1h, 24h, 7d, 14d, 30d, 60d, 1y`.
- -p/--period - time period in which coins lost the most in price. One from `1h, 24h, 7d, 14d, 30d, 60d, 1y`.
If you want to see top losers in last 24h `cg_losers -p 24h` or top losers in last 60days `cg_losers -p 60d`
* -t/--top - number of coins to display. To display top 10 coins: `cg_losers --top 10 --sortby rank`
* -s/--sortby - sort by given column. You can chose on from `rank, name, symbol, price, change, volume`.
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls e.g. `cg_losers --period 24h --links`
- -t/--top - number of coins to display. To display top 10 coins: `cg_losers --top 10 --sortby rank`
- -s/--sortby - sort by given column. You can chose on from `rank, name, symbol, price, change, volume`.
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls e.g. `cg_losers --period 24h --urls`
![image](https://user-images.githubusercontent.com/275820/124305327-3ad2b780-db65-11eb-9922-7783b740312c.png))
@@ -204,26 +204,26 @@ Shows Top Yield Farming Pools by Value Locked from <https://www.coingecko.com/en
Yield farming, also referred to as liquidity mining, is a way to generate rewards with cryptocurrency holdings.
In simple terms, it means locking up cryptocurrencies and getting rewards.
* -t/--top - number of yield farms to display. To display top 10 yield farms: `cg_yfarms --top 10 --sortby rank`
* -s/--sortby - sort by given column. You can sort data by `rank, name, value_locked, return_year`
* --descend - flag to sort in descending order (lowest first)
- -t/--top - number of yield farms to display. To display top 10 yield farms: `cg_yfarms --top 10 --sortby rank`
- -s/--sortby - sort by given column. You can sort data by `rank, name, value_locked, return_year`
- --descend - flag to sort in descending order (lowest first)
![image](https://user-images.githubusercontent.com/275820/124305461-6ce41980-db65-11eb-9c82-648f4ff905e8.png)
## cg_top_defi <a name="cg_top_defi"></a>
```text
usage: cg_top_defi [-t --top] [-s --sortby] [--descend] [-l --links]
usage: cg_top_defi [-t --top] [-s --sortby] [--descend] [-u --urls]
```
Shows Top DeFi Coins by Market Capitalization from <https://www.coingecko.com/en/defi>
DeFi or Decentralized Finance refers to financial services that are built on top of distributed networks with no central
intermediaries.
* -t/--top - number of defi coins to display. To display top 10 defi coins: `cg_top_defi --top 10 --sortby rank`
* -s/--sortby - sort by given column. You can sort data by `rank, name, symbol, price, change_24h, change_1h, change_7d`
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls e.g. `cg_top_defi --links`.
- -t/--top - number of defi coins to display. To display top 10 defi coins: `cg_top_defi --top 10 --sortby rank`
- -s/--sortby - sort by given column. You can sort data by `rank, name, symbol, price, change_24h, change_1h, change_7d`
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls e.g. `cg_top_defi --urls`.
![image](https://user-images.githubusercontent.com/275820/124305508-7c636280-db65-11eb-8ae7-38389ac72770.png)
@@ -238,28 +238,28 @@ Decentralized exchanges or DEXs are autonomous decentralized applications (DApps
sellers to trade without having to give up control over their funds to any intermediary or custodian. Source:
[coinmarketcap](#https://coinmarketcap.com/alexandria/article/what-are-decentralized-exchanges-dex)
* -t/--top - number of Decentralized Exchanges to display. To display top 10 DEX: `cg_top_dex --top 10 --sortby rank`
* -s/--sortby - sort by given column. You can sort data by
- -t/--top - number of Decentralized Exchanges to display. To display top 10 DEX: `cg_top_dex --top 10 --sortby rank`
- -s/--sortby - sort by given column. You can sort data by
`rank, name, volume_24h, n_coins, n_pairs, visits, most_traded, market_share_by_vol most_traded_pairs, market_share_by_volume`
* --descend - flag to sort in descending order (lowest first)
- --descend - flag to sort in descending order (lowest first)
![image](https://user-images.githubusercontent.com/275820/124305536-84230700-db65-11eb-82bf-ad2d0878135c.png)
## cg_top_nft <a name="cg_top_nft"></a>
```text
usage: cg_top_nft [-t --top] [-s --sortby] [--descend] [-l --links]
usage: cg_top_nft [-t --top] [-s --sortby] [--descend] [-u --urls]
```
Shows Top NFT Coins by Market Capitalization from <https://www.coingecko.com/en/nft>
NFT (Non-fungible Token) refers to digital assets with unique characteristics.
Examples of NFT include crypto artwork, collectibles, game items, financial products, and more.
* -t/--top - number of NFT to display. To display top 10 NFT Coins: `cg_top_nft --top 10 --sortby rank`
* -s/--sortby - sort by given column. You can sort data by
- -t/--top - number of NFT to display. To display top 10 NFT Coins: `cg_top_nft --top 10 --sortby rank`
- -s/--sortby - sort by given column. You can sort data by
`rank, name, symbol, price, change_24h, change_1h, change_7d, volume_24h, market_cap`
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls e.g. `cg_top_nft --links`.
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls e.g. `cg_top_nft --urls`.
![image](https://user-images.githubusercontent.com/275820/124305553-8be2ab80-db65-11eb-92a8-86485d2b7ef8.png)
@@ -275,12 +275,12 @@ Display all available coins in coinpaprika. You can search with pagination mecha
or you can just search by letter like `cp_coins --letter d --key name --top 10` it will display top 10 matches for coins
which name starts with letter `d` and search will be done in column `name`
* -s/--skip: Number of records to skip. Default is 0. There are thousands of coins, so there is mechanism to paginate
- -s/--skip: Number of records to skip. Default is 0. There are thousands of coins, so there is mechanism to paginate
through them. e.q if you want to see only records from 500-750 you should use `cp_coins --skip 500 --top 250` or if
you want to see 1200-1880 you should use `cp_coins --skip 1200 --top 680`.
* -t/--top: Number of coins to display. E.g `cp_coins --top 150`
* -l/--letter: First letters of coin by which you want to search
* -k/--key: Search key. With this parameter you can specify in which column you would like to search. Choose on from
- -t/--top: Number of coins to display. E.g `cp_coins --top 150`
- -l/--letter: First letters of coin by which you want to search
- -k/--key: Search key. With this parameter you can specify in which column you would like to search. Choose on from
`name, id, symbol`
## cp_search <a name="cp_search"></a>
@@ -291,11 +291,11 @@ usage: cp_search [-q --query] [-c --cat] [-s --sort] [-t --top] [--descend]
Search over CoinPaprika API
* -q/--query: Phrase for search
* -c/--cat: Categories to search: currencies|exchanges|icos|people|tags|all. Default: all
* -t/--top: Number of found records to display.
* -s/--sort: Sort by given column. You can chose on from `category, id, name`.
* --descend: Flag to sort in descending order (lowest first)
- -q/--query: Phrase for search
- -c/--cat: Categories to search: currencies|exchanges|icos|people|tags|all. Default: all
- -t/--top: Number of found records to display.
- -s/--sort: Sort by given column. You can chose on from `category, id, name`.
- --descend: Flag to sort in descending order (lowest first)
![image](https://user-images.githubusercontent.com/275820/125336200-c7435e00-e34d-11eb-965f-16856f4d9913.png)
@@ -309,10 +309,10 @@ cmc_top [-n] [-s --sort SORTBY] [--descend]
This command displays the top n cryptocurrencies from coinmarketcap.com.
* -n: Number of coins to look at (after sort). Defaults to 10.
* -s/--sort : Column to sort by. One of {Symbol,CMC_Rank,LastPrice,DayPctChange,MarketCap}. Defaults to sorting by the
- -n: Number of coins to look at (after sort). Defaults to 10.
- -s/--sort : Column to sort by. One of {Symbol,CMC_Rank,LastPrice,DayPctChange,MarketCap}. Defaults to sorting by the
CMC rank.
* --descend : Flag to change the sorting order. Sorts in ascending order (since default order is the Rank which starts
- --descend : Flag to change the sorting order. Sorts in ascending order (since default order is the Rank which starts
at 1).
<img width="990" alt="crypto" src="https://user-images.githubusercontent.com/25267873/115787544-4746d100-a3ba-11eb-9433-b7cb9142404a.png">
@@ -330,9 +330,9 @@ means that baseAsset is ETH and quoteAsset is BTC. You can search with paginatio
`bin_coins --skip [num] --top [num]` or you can just search by letter like `bin_coins --letter d --key symbol --top 10`
it will display top 10 matches for coins which symbol starts with letter `d` and search will be done in column `symbol`
* -s/--skip: Number of records to skip. Default is 0. There are thousands of coins, so there is mechanism to paginate
- -s/--skip: Number of records to skip. Default is 0. There are thousands of coins, so there is mechanism to paginate
through them. e.q if you want to see only records from 500-750 you should use `bin_coins --skip 500 --top 250`
* -t/--top: Number of coins to display.
* -l/--letter: First letters of coin by which you want to search
* -k/--key: Search key. With this parameter you can specify in which column you would like to search. Choose on from
- -t/--top: Number of coins to display.
- -l/--letter: First letters of coin by which you want to search
- -k/--key: Search key. With this parameter you can specify in which column you would like to search. Choose on from
`symbol, quoteAsset, baseAsset`

View File

@@ -5,6 +5,8 @@ import pandas as pd
from coinmarketcapapi import CoinMarketCapAPI
import gamestonk_terminal.config_terminal as cfg
FILTERS = ["Symbol", "CMC_Rank", "LastPrice", "DayPctChange", "MarketCap"]
def get_cmc_top_n() -> pd.DataFrame:
"""Shows top n coins. [Source: CoinMarketCap]

View File

@@ -5,6 +5,17 @@ from typing import Optional, Any
import pandas as pd
from gamestonk_terminal.cryptocurrency.coinpaprika_helpers import PaprikaSession
CATEGORIES = [
"currencies",
"exchanges",
"icos",
"people",
"tags",
"all",
]
FILTERS = ["category", "id", "name"]
def get_search_results(
query: str, category: Optional[Any] = None, modifier: Optional[Any] = None

View File

@@ -3,6 +3,7 @@ __docformat__ = "numpy"
import pandas as pd
from pycoingecko import CoinGeckoAPI
from requests.adapters import RetryError
from gamestonk_terminal.cryptocurrency.dataframe_helpers import (
percent_to_float,
create_df_index,
@@ -37,6 +38,56 @@ CATEGORIES = {
"most_visited": 4,
}
GAINERS_FILTERS = ["Rank", "Symbol", "Name", "Volume", "Price", "Change"]
TRENDING_FILTERS = [
"Rank",
"Name",
"Price_BTC",
"Price_USD",
]
RECENTLY_FILTERS = [
"Rank",
"Name",
"Symbol",
"Price",
"Change_1h",
"Change_24h",
"Added",
"Url",
]
YFARMS_FILTERS = [
"Rank",
"Name",
"Value_Locked",
"Return_Year",
]
CAP_FILTERS = [
"Rank",
"Name",
"Symbol",
"Price",
"Change_1h",
"Change_24h",
"Change_7d",
"Volume_24h",
"Market_Cap",
]
DEX_FILTERS = [
"Name",
"Rank",
"Volume_24h",
"Coins",
"Pairs",
"Visits",
"Most_Traded",
"Market_Share",
]
# TODO: convert Volume and other str that should be int to int otherwise sort won't work
def get_gainers_or_losers(period: str = "1h", typ: str = "gainers") -> pd.DataFrame:
"""Scrape data about top gainers - coins which gain the most in given period and
@@ -66,7 +117,12 @@ def get_gainers_or_losers(period: str = "1h", typ: str = "gainers") -> pd.DataFr
)
url = f"https://www.coingecko.com/en/coins/trending{PERIODS.get(period)}"
rows = scrape_gecko_data(url).find_all("tbody")[category.get(typ)].find_all("tr")
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
rows = scraped_data.find_all("tbody")[category.get(typ)].find_all("tr")
results = []
for row in rows:
url = GECKO_BASE_URL + row.find("a")["href"]
@@ -117,9 +173,14 @@ def get_discovered_coins(category: str = "trending") -> pd.DataFrame:
f"Wrong category name\nPlease chose one from list: {CATEGORIES.keys()}"
)
url = "https://www.coingecko.com/en/discover"
popular = scrape_gecko_data(url).find_all(
"div", class_="col-12 col-sm-6 col-md-6 col-lg-4"
)[CATEGORIES[category]]
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
popular = scraped_data.find_all("div", class_="col-12 col-sm-6 col-md-6 col-lg-4")[
CATEGORIES[category]
]
rows = popular.find_all("a")
results = []
btc_price = get_btc_price()
@@ -165,7 +226,12 @@ def get_recently_added_coins() -> pd.DataFrame:
]
url = "https://www.coingecko.com/en/coins/recently_added"
rows = scrape_gecko_data(url).find("tbody").find_all("tr")
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
rows = scraped_data.find("tbody").find_all("tr")
results = []
for row in rows:
@@ -211,7 +277,12 @@ def get_yield_farms() -> pd.DataFrame:
"Return_Year",
]
url = "https://www.coingecko.com/en/yield-farming"
rows = scrape_gecko_data(url).find("tbody").find_all("tr")
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
rows = scraped_data.find("tbody").find_all("tr")
results = []
for row in rows:
row_cleaned = clean_row(row)[:-2]
@@ -276,7 +347,12 @@ def get_top_volume_coins() -> pd.DataFrame:
"Market_Cap",
]
url = "https://www.coingecko.com/en/coins/high_volume"
rows = scrape_gecko_data(url).find("tbody").find_all("tr")
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
rows = scraped_data.find("tbody").find_all("tr")
results = []
for row in rows:
row_cleaned = clean_row(row)
@@ -302,7 +378,12 @@ def get_top_defi_coins() -> pd.DataFrame:
"""
url = "https://www.coingecko.com/en/defi"
rows = scrape_gecko_data(url).find("tbody").find_all("tr")
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
rows = scraped_data.find("tbody").find_all("tr")
results = []
for row in rows:
@@ -362,7 +443,12 @@ def get_top_dexes() -> pd.DataFrame:
"Market_Share",
]
url = "https://www.coingecko.com/en/dex"
rows = scrape_gecko_data(url).find("tbody").find_all("tr")
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
rows = scraped_data.find("tbody").find_all("tr")
results = []
for row in rows:
row_cleaned = clean_row(row)
@@ -399,7 +485,12 @@ def get_top_nfts() -> pd.DataFrame:
"""
url = "https://www.coingecko.com/en/nft"
rows = scrape_gecko_data(url).find("tbody").find_all("tr")
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
rows = scraped_data.find("tbody").find_all("tr")
results = []
for row in rows:
link = GECKO_BASE_URL + row.find("a")["href"]

View File

@@ -39,35 +39,39 @@ def display_gainers(
if sortby == "Change":
sortby = f"%Change_{period}"
df = pycoingecko_model.get_gainers_or_losers(
period=period, typ="gainers"
).sort_values(by=sortby, ascending=descend)
df = pycoingecko_model.get_gainers_or_losers(period=period, typ="gainers")
if not df.empty:
df = df.sort_values(by=sortby, ascending=descend)
df_data = df.copy()
df_data = df.copy()
if not links:
df.drop("Url", axis=1, inplace=True)
if not links:
df.drop("Url", axis=1, inplace=True)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".4f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".4f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"gainers",
df_data,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"gainers",
df_data,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_losers(
@@ -94,35 +98,39 @@ def display_losers(
if sortby == "Change":
sortby = f"%Change_{period}"
df = pycoingecko_model.get_gainers_or_losers(
period=period, typ="losers"
).sort_values(by=sortby, ascending=descend)
df = pycoingecko_model.get_gainers_or_losers(period=period, typ="losers")
if not df.empty:
df = df.sort_values(by=sortby, ascending=descend)
df_data = df.copy()
df_data = df.copy()
if not links:
df.drop("Url", axis=1, inplace=True)
if not links:
df.drop("Url", axis=1, inplace=True)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".4f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".4f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"losers",
df_data,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"losers",
df_data,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_discover(
@@ -151,37 +159,42 @@ def display_discover(
"""
df = pycoingecko_model.get_discovered_coins(category=category)
df.index = df.index + 1
df.reset_index(inplace=True)
df.rename(columns={"index": "Rank"}, inplace=True)
if not df.empty:
df.index = df.index + 1
df.reset_index(inplace=True)
df.rename(columns={"index": "Rank"}, inplace=True)
df = df.sort_values(by=sortby, ascending=descend)
df = df.sort_values(by=sortby, ascending=descend)
df_data = df.copy()
df_data = df.copy()
if not links:
df.drop("Url", axis=1, inplace=True)
if not links:
df.drop("Url", axis=1, inplace=True)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".4f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".4f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
category,
df_data,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
category,
df_data,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_recently_added(
@@ -345,30 +358,34 @@ def display_top_volume_coins(top: int, sortby: str, descend: bool, export: str)
Export dataframe data to csv,json,xlsx file
"""
df = pycoingecko_model.get_top_volume_coins().sort_values(
by=sortby, ascending=descend
)
df = pycoingecko_model.get_top_volume_coins()
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".4f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if not df.empty:
df = df.sort_values(by=sortby, ascending=descend)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".4f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"volume",
df,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"volume",
df,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_top_nft(
@@ -396,33 +413,38 @@ def display_top_nft(
df = pycoingecko_model.get_top_nfts().sort_values(by=sortby, ascending=descend)
df_data = df.copy()
if not df.empty:
df_data = df.copy()
if links is True:
df = df[["Rank", "Name", "Symbol", "Url"]]
else:
df.drop("Url", axis=1, inplace=True)
if links is True:
df = df[["Rank", "Name", "Symbol", "Url"]]
else:
df.drop("Url", axis=1, inplace=True)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".4f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".4f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"nft",
df_data,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"nft",
df_data,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_yieldfarms(top: int, sortby: str, descend: bool, export: str) -> None:
@@ -442,27 +464,33 @@ def display_yieldfarms(top: int, sortby: str, descend: bool, export: str) -> Non
"""
df = pycoingecko_model.get_yield_farms()
df_data = df.copy()
df = df.sort_values(by=sortby, ascending=descend)
if not df.empty:
df_data = df.copy()
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".0f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
df = df.sort_values(by=sortby, ascending=descend)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".0f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"yfarms",
df_data,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"yfarms",
df_data,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")

View File

@@ -6,46 +6,46 @@ will be available in menu.
[CoinGecko](#CoinGecko)
* [info](#info)
* show coin base information
* [market](#market)
* show market data
* [ath](#ath)
* show all time high related metrics
* [atl](#atl)
* show all time low related metrics
* [score](#score)
* show different scores related to loaded coin
* [web](#web)
* show websites related to loaded coin
* [social](#social)
* show social media urls for loaded coin
* [bc](#bc)
* show block-chain explorers urls for loaded coins
* [dev](#dev)
* show developers data for loaded coins
- [info](#info)
- show coin base information
- [market](#market)
- show market data
- [ath](#ath)
- show all time high related metrics
- [atl](#atl)
- show all time low related metrics
- [score](#score)
- show different scores related to loaded coin
- [web](#web)
- show websites related to loaded coin
- [social](#social)
- show social media urls for loaded coin
- [bc](#bc)
- show block-chain explorers urls for loaded coins
- [dev](#dev)
- show developers data for loaded coins
[CoinPaprika](#CoinPaprika)
* [basic](#basic)
* show coin basic information
* [ps](#ps)
* show coin price, supply, market cap related metrics.
* [mkt](#mkt)
* show all markets for loaded coin
* [ex](#ex)
* show all exchanges where loaded coin is listed
* [twitter](#twitter)
* show up to 50 last tweets for loaded coin
* [events](#events )
* show events related to loaded coin
- [basic](#basic)
- show coin basic information
- [ps](#ps)
- show coin price, supply, market cap related metrics.
- [mkt](#mkt)
- show all markets for loaded coin
- [ex](#ex)
- show all exchanges where loaded coin is listed
- [twitter](#twitter)
- show up to 50 last tweets for loaded coin
- [events](#events)
- show events related to loaded coin
[Binance](#Binance)
* [book](#book)
* show order book
* [balance](#book)
* show coin balance
- [book](#book)
- show order book
- [balance](#book)
- show coin balance
## chart <a name="chart">
@@ -117,7 +117,7 @@ usage: score
```
In this view you can find different kind of scores for loaded coin. Those scores represents different rankings,
sentiment metrics, some user stats and other, like: coingecko_rank, coingecko_score, developer_score, community_score,
sentiment metrics, some user stats and other, like: coingecko_rank, coingecko_score, developer_score, community_score,
liquidity_score, sentiment_votes_up_percentage, sentiment_votes_down_percentage, public_interest_score, facebook_likes,
twitter_followers, reddit_average_posts_48h, reddit_average_comments_48h, reddit_subscribers,
reddit_accounts_active_48h, telegram_channel_user_count, alexa_rank, bing_matches.
@@ -188,25 +188,25 @@ usage: ps [--vs]
Price, supply related data for loaded coin. There you find metrics like:
name, symbol, rank, supply, volume, ath, market cap, price change related metrics
* --vs: The currency to look against. Available options are:
- --vs: The currency to look against. Available options are:
`BTC,ETH,USD,EUR,PLN,KRW,GBP,CAD,JPY,RUB,TRY,NZD,AUD,CHF,UAH,HKD,SGD,NGN,PHP,MXN,BRL,THB,CLP,CNY,CZK,DKK,HUF,IDR,ILS,INR,MYR,NOK,PKR,SEK,TWD,ZAR,VND,BOB,COP,PEN,ARS,ISK`.
Default: USD
## mkt <a name="mkt"></a>
```text
usage: mkt [--vs] [-t --top] [-s --sort] [--descend] [-l --links]
usage: mkt [--vs] [-t --top] [-s --sort] [--descend] [-u --urls]
```
Get all markets found for given coin.
* --vs: The currency to look against. Available options are:
- --vs: The currency to look against. Available options are:
`BTC,ETH,USD,EUR,PLN,KRW,GBP,CAD,JPY,RUB,TRY,NZD,AUD,CHF,UAH,HKD,SGD,NGN,PHP,MXN,BRL,THB,CLP,CNY,CZK,DKK,HUF,IDR,ILS,INR,MYR,NOK,PKR,SEK,TWD,ZAR,VND,BOB,COP,PEN,ARS,ISK`.
Default: USD
* -t/--top - number of markets to display. To display top 10 markets: `mkt --top 10 --sort trust_score`
* -s/--sort - sort by given column. You can chose on from `pct_volume_share, exchange, pair, trust_score, volume,price`.
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls e.g. `mkt --links`
- -t/--top - number of markets to display. To display top 10 markets: `mkt --top 10 --sort trust_score`
- -s/--sort - sort by given column. You can chose on from `pct_volume_share, exchange, pair, trust_score, volume,price`.
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls e.g. `mkt --links`
## ex <a name="ex"></a>
@@ -216,9 +216,9 @@ usage: ex [-t --top] [-s --sort] [--descend]
Get all exchanges found for given coin.
* -t/--top: Number of exchanges to display. To display top 10 exchanges: `ex --top 10 --sort fiats`
* -s/--sort: Sort by given column. You can chose on from `id, name, adjusted_volume_24h_share, fiats`.
* --descend: Flag to sort in descending order (lowest first)
- -t/--top: Number of exchanges to display. To display top 10 exchanges: `ex --top 10 --sort fiats`
- -s/--sort: Sort by given column. You can chose on from `id, name, adjusted_volume_24h_share, fiats`.
- --descend: Flag to sort in descending order (lowest first)
## twitter <a name="twitter"></a>
@@ -228,39 +228,39 @@ usage: twitter [-t --top] [-s --sort] [--descend]
Show last tweets for given coin.
* -t/--top: Number of tweets to display. To display top 10 tweets: `twitter --top 10 --sort date`
* -s/--sort: Sort by given column. You can chose on from `date, user_name, status, retweet_count, like_count`.
* --descend: Flag to sort in descending order (lowest first)
- -t/--top: Number of tweets to display. To display top 10 tweets: `twitter --top 10 --sort date`
- -s/--sort: Sort by given column. You can chose on from `date, user_name, status, retweet_count, like_count`.
- --descend: Flag to sort in descending order (lowest first)
## events <a name="events"></a>
```text
usage: events [-t --top] [-s --sort] [--descend] [-l --links]
usage: events [-t --top] [-s --sort] [--descend] [-u --urls]
```
Show information about most important coins events.
* -t/--top: Number of events to display. To display top 10 events: `events --top 10 --sort date`
* -s/--sort: Sort by given column. You can chose on from `date, date_to, name, description, is_conference`.
* --descend: Flag to sort in descending order (lowest first)
* -l/--links: Flag to show urls. Using this flag will add additional column with urls e.g. `events --links`. If you
- -t/--top: Number of events to display. To display top 10 events: `events --top 10 --sort date`
- -s/--sort: Sort by given column. You can chose on from `date, date_to, name, description, is_conference`.
- --descend: Flag to sort in descending order (lowest first)
- -u/--urls: Flag to show urls. Using this flag will add additional column with urls e.g. `events --links`. If you
will use this flag url column will be displayed.
# Binance <a name="Binance"></a>
## book <a name="book"></a>
## book <a name="book"></a>
```text
usage: book -l/--limit
```
Gets and shows the order book for the given coin/currency. Shows the cumulative amount of orders.
Gets and shows the order book for the given coin/currency. Shows the cumulative amount of orders.
* -l/--limit Number of orders to get on each side. One of [5,10,20,50,100,500,1000,5000]. Defaults to 100.
- -l/--limit Number of orders to get on each side. One of [5,10,20,50,100,500,1000,5000]. Defaults to 100.
![orderbook](https://user-images.githubusercontent.com/25267873/116886857-84fcf280-ac21-11eb-9803-5baa8bceca05.png)
## balance <a name="balance"></a>
## balance <a name="balance"></a>
```text
usage: balance

View File

@@ -6,6 +6,8 @@ from gamestonk_terminal import config_terminal as cfg
api_url = "https://open-api.coinglass.com/api/pro/v1/"
INTERVALS = [0, 1, 2, 4]
def get_open_interest_per_exchange(symbol: str, interval: int) -> pd.DataFrame:
"""Returns open interest by exchange for a certain symbol

View File

@@ -94,7 +94,7 @@ def get_coin_events_by_id(coin_id: str = "eth-ethereum") -> pd.DataFrame:
session = PaprikaSession()
res = session.make_request(session.ENDPOINTS["coin_events"].format(coin_id))
if not res:
if not res or "error" in res:
return pd.DataFrame()
data = pd.DataFrame(res)
data["description"] = data["description"].apply(

View File

@@ -18,6 +18,21 @@ register_matplotlib_converters()
# pylint: disable=inconsistent-return-statements
# pylint: disable=C0302, too-many-lines
TWEETS_FILTERS = ["date", "user_name", "status", "retweet_count", "like_count"]
EVENTS_FILTERS = ["date", "date_to", "name", "description", "is_conference"]
EX_FILTERS = ["id", "name", "adjusted_volume_24h_share", "fiats"]
MARKET_FILTERS = [
"pct_volume_share",
"exchange",
"pair",
"trust_score",
"volume",
"price",
]
CURRENCIES = [
"BTC",
"ETH",

View File

@@ -11,7 +11,7 @@ from gamestonk_terminal.common.behavioural_analysis.finbrain_view import (
from gamestonk_terminal.common.behavioural_analysis.finbrain_model import get_sentiment
from gamestonk_terminal.helper_funcs import export_data
PATH = os.path.dirname(os.path.abspath(__file__))
PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
try:
COINS_JSON = pd.read_json(PATH + "/data/finbrain_coins.json")

View File

@@ -158,6 +158,8 @@ GLASSNODE_SUPPORTED_ASSETS = [
"ZRX",
]
INTERVALS = ["1h", "24h", "10m", "1w", "1month"]
def get_active_addresses(
asset: str, interval: str, since: int, until: int

View File

@@ -1,5 +1,6 @@
import argparse
from typing import List
import difflib
from typing import List, Union
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal import feature_flags as gtff
@@ -21,10 +22,16 @@ class NFTController:
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = [
@@ -36,33 +43,35 @@ class NFTController:
CHOICES += CHOICES_COMMANDS
def __init__(self):
def __init__(self, queue: List[str] = None):
"""Constructor"""
self.nft_parser = argparse.ArgumentParser(add_help=False, prog="nft")
self.nft_parser.add_argument(
"cmd",
choices=self.CHOICES,
)
self.completer = NestedCompleter.from_nested_dict(
{c: None for c in 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_text = """
What do you want to do?
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon the program
Non Fungible Tokens Menu:
nftcalendar.io:
today today's NFT drops
upcoming upcoming NFT drops
ongoing Ongoing NFT drops
newest Recently NFTs added
"""
"""
print(help_text)
def switch(self, an_input: str):
@@ -80,23 +89,37 @@ nftcalendar.io:
True - quit the program
None - continue in the menu
"""
# Empty command
if not an_input:
print("")
return None
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.nft_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -104,17 +127,50 @@ nftcalendar.io:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help Command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program"""
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "nft")
self.queue.insert(0, "crypto")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "reset", "crypto", "nft"]
@try_except
def call_today(self, other_args: List[str]):
@@ -126,23 +182,22 @@ nftcalendar.io:
description="Today's NFT drops [Source: nftcalendar.io]",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
type=check_positive,
help="Number of NFT collections to display",
dest="num",
dest="limit",
default=5,
)
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
nftcalendar_view.display_nft_today_drops(
num=ns_parser.num,
export=ns_parser.export,
)
if ns_parser:
nftcalendar_view.display_nft_today_drops(
num=ns_parser.limit,
export=ns_parser.export,
)
return self.queue
@try_except
def call_upcoming(self, other_args: List[str]):
@@ -154,23 +209,22 @@ nftcalendar.io:
description="Upcoming's NFT drops [Source: nftcalendar.io]",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
type=check_positive,
help="Number of NFT collections to display",
dest="num",
dest="limit",
default=5,
)
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
nftcalendar_view.display_nft_upcoming_drops(
num=ns_parser.num,
export=ns_parser.export,
)
if ns_parser:
nftcalendar_view.display_nft_upcoming_drops(
num=ns_parser.limit,
export=ns_parser.export,
)
return self.queue
@try_except
def call_ongoing(self, other_args: List[str]):
@@ -182,23 +236,22 @@ nftcalendar.io:
description="Ongoing's NFT drops [Source: nftcalendar.io]",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
type=check_positive,
help="Number of NFT collections to display",
dest="num",
dest="limit",
default=5,
)
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
nftcalendar_view.display_nft_ongoing_drops(
num=ns_parser.num,
export=ns_parser.export,
)
if ns_parser:
nftcalendar_view.display_nft_ongoing_drops(
num=ns_parser.limit,
export=ns_parser.export,
)
return self.queue
@try_except
def call_newest(self, other_args: List[str]):
@@ -210,48 +263,95 @@ nftcalendar.io:
description="Newest's NFT drops [Source: nftcalendar.io]",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
type=check_positive,
help="Number of NFT collections to display",
dest="num",
dest="limit",
default=5,
)
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
nftcalendar_view.display_nft_newest_drops(
num=ns_parser.num,
export=ns_parser.export,
)
if ns_parser:
nftcalendar_view.display_nft_newest_drops(
num=ns_parser.limit,
export=ns_parser.export,
)
return self.queue
def menu():
def menu(queue: List[str] = None):
"""NFT Menu"""
nft_controller = NFTController()
nft_controller.call_help(None)
while True:
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in nft_controller.CHOICES}
)
nft_controller = NFTController(queue=queue)
an_input = "HELP_ME"
an_input = session.prompt(
f"{get_flair()} (nft)> ",
completer=completer,
)
while True:
# There is a command in the queue
if nft_controller.queue and len(nft_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if nft_controller.queue[0] in ("q", "..", "quit"):
if len(nft_controller.queue) > 1:
return nft_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = nft_controller.queue[0]
nft_controller.queue = nft_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 nft_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /crypto/nft/ $ {an_input}")
# Get input command from user
else:
an_input = input(f"{get_flair()} (nft)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
nft_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and nft_controller.completer:
an_input = session.prompt(
f"{get_flair()} /crypto/nft/ $ ",
completer=nft_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /crypto/nft/ $ ")
try:
process_input = nft_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
nft_controller.queue = nft_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exit\n")
continue
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,
nft_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 = ""
nft_controller.queue = []
print("\n")
continue
print(f" Replacing by '{an_input}'.")
nft_controller.queue.insert(0, an_input)
else:
print("\n")
an_input = ""
nft_controller.queue = []

View File

@@ -32,6 +32,38 @@ class BitQueryTimeoutException(Exception):
BQ_URL = "https://graphql.bitquery.io"
CURRENCIES = ["ETH", "USD", "BTC", "USDT"]
LT_FILTERS = ["exchange", "trades", "tradeAmount"]
LT_KIND = ["dex", "time"]
INTERVALS = ["day", "month", "week"]
DVCP_FILTERS = [
"date",
"exchange",
"base",
"quote",
"open",
"high",
"low",
"close",
"tradeAmount",
"trades",
]
UEAT_FILTERS = [
"date",
"uniqueSenders",
"transactions",
"averageGasPrice",
"mediumGasPrice",
"maximumGasPrice",
]
TTCP_FILTERS = ["base", "quoted", "trades", "tradeAmount"]
BAAS_FILTERS = [
"date",
"baseCurrency",
"quoteCurrency",
"dailySpread",
"averageBidPrice",
"averageAskPrice",
]
DECENTRALIZED_EXCHANGES = [
"1inch",
"AfroDex",
@@ -469,7 +501,7 @@ def get_token_volume_on_dexes(
return pd.DataFrame()
df = _extract_dex_trades(data)[["exchange.fullName", "tradeAmount", "count"]]
df.columns = ["exchange", "tradeAmount", "trades"]
df.columns = LT_FILTERS
return df[~df["exchange"].str.startswith("<")].sort_values(
by="tradeAmount", ascending=False
)
@@ -497,7 +529,7 @@ def get_ethereum_unique_senders(interval: str = "day", limit: int = 90) -> pd.Da
"week": 7,
}
if interval not in intervals.keys():
if interval not in intervals:
interval = "day"
days = min(limit * intervals[interval], 90)
@@ -527,16 +559,7 @@ def get_ethereum_unique_senders(interval: str = "day", limit: int = 90) -> pd.Da
df = pd.DataFrame(data["ethereum"]["transactions"])
df["date"] = df["date"].apply(lambda x: x["date"])
return df[
[
"date",
"uniqueSenders",
"transactions",
"averageGasPrice",
"mediumGasPrice",
"maximumGasPrice",
]
]
return df[UEAT_FILTERS]
def get_most_traded_pairs(
@@ -585,7 +608,7 @@ def get_most_traded_pairs(
df = _extract_dex_trades(data)
df.columns = ["trades", "tradeAmount", "base", "quoted"]
return df[["base", "quoted", "trades", "tradeAmount"]]
return df[TTCP_FILTERS]
def get_spread_for_crypto_pair(

View File

@@ -11,6 +11,44 @@ import requests
from gamestonk_terminal.cryptocurrency.dataframe_helpers import create_df_index
import gamestonk_terminal.config_terminal as cfg
PRICES_FILTERS = [
"date",
"cap",
"volumeConverted",
"open",
"high",
"close",
"low",
]
TOP_FILTERS = [
"rank",
"name",
"symbol",
"price",
"txsCount",
"transfersCount",
"holdersCount",
]
TH_FILTERS = [
"value",
]
BALANCE_FILTERS = [
"index",
"balance",
"tokenName",
"tokenSymbol",
]
HIST_FILTERS = ["timestamp", "transactionHash", "token", "value"]
HOLDERS_FILTERS = [
"balance",
"share",
]
def split_cols_with_dot(column: str) -> str:
"""Split column name in data frame columns whenever there is a dot between 2 words.

View File

@@ -8,6 +8,16 @@ import pandas as pd
import numpy as np
import gamestonk_terminal.config_terminal as cfg
FILTERS = [
"date",
"symbol",
"blockchain",
"amount",
"amount_usd",
"from",
"to",
]
class ApiKeyException(Exception):
"""Api Key Exception object"""

View File

@@ -5,69 +5,69 @@ The usage of the following commands along with an example will be exploited belo
[CoinGecko](#CoinGecko)
* [cg_global](#cg_global)
* show global crypto market info
* [cg_defi](#cg_defi)
* show global DeFi market info
* [cg_stables](#cg_stables)
* show Stable Coins
* [cg_nft_today](#cg_nft_today)
* show NFT Of The Day
* [cg_nft](#cg_nft)
* show NFT Market Status
* [cg_exchanges](#cg_exchanges)
* show Top Crypto Exchanges
* [cg_exrates](#cg_exrates)
* show Coin Exchange Rates
* [cg_platforms](#cg_platforms)
* show Crypto Financial Platforms
* [cg_products](#cg_products)
* show Crypto Financial Products
* [cg_indexes](#cg_indexes)
* show Crypto Indexes
* [cg_derivatives](#cg_derivatives)
* show Crypto Derivatives
* [cg_categories](#cg_categories)
* show Crypto Categories
* [cg_hold](#cg_hold)
* show eth, btc holdings overview statistics
* [cg_companies](#cg_companies)
* show eth, btc holdings by public companies
- [cg_global](#cg_global)
- show global crypto market info
- [cg_defi](#cg_defi)
- show global DeFi market info
- [cg_stables](#cg_stables)
- show Stable Coins
- [cg_nft_today](#cg_nft_today)
- show NFT Of The Day
- [cg_nft](#cg_nft)
- show NFT Market Status
- [cg_exchanges](#cg_exchanges)
- show Top Crypto Exchanges
- [cg_exrates](#cg_exrates)
- show Coin Exchange Rates
- [cg_platforms](#cg_platforms)
- show Crypto Financial Platforms
- [cg_products](#cg_products)
- show Crypto Financial Products
- [cg_indexes](#cg_indexes)
- show Crypto Indexes
- [cg_derivatives](#cg_derivatives)
- show Crypto Derivatives
- [cg_categories](#cg_categories)
- show Crypto Categories
- [cg_hold](#cg_hold)
- show eth, btc holdings overview statistics
- [cg_companies](#cg_companies)
- show eth, btc holdings by public companies
[CoinPaprika](#CoinPaprika)
* [cp_global](#cp_global)
* show global crypto market info
* [cp_info](#cp_info)
* show basic info about all coins available on CoinPaprika
* [cp_markets](#cp_markets)
* show market related info about all coins available on CoinPaprika
* [cp_exchanges](#cp_exchanges)
* list all exchanges available on CoinPaprika
* [cp_exmarkets](#cp_exmarkets)
* show all available markets on given exchange
* [cp_platforms](#cp_platforms)
* list blockchain platforms/ecosystems eg. ethereum, solana, kusama, terra
* [cp_contracts](#cp_contracts)
* show all smart contracts for given platform
- [cp_global](#cp_global)
- show global crypto market info
- [cp_info](#cp_info)
- show basic info about all coins available on CoinPaprika
- [cp_markets](#cp_markets)
- show market related info about all coins available on CoinPaprika
- [cp_exchanges](#cp_exchanges)
- list all exchanges available on CoinPaprika
- [cp_exmarkets](#cp_exmarkets)
- show all available markets on given exchange
- [cp_platforms](#cp_platforms)
- list blockchain platforms/ecosystems eg. ethereum, solana, kusama, terra
- [cp_contracts](#cp_contracts)
- show all smart contracts for given platform
# CoinGecko <a name="CoinGecko">
## cg_global <a name="cg_global"></a>
## cg_global <a name="cg_global"></a>
```text
usage: cg_global
```
Display global statistics about Crypto Market like: active_cryptocurrencies, upcoming_icos, ongoing_icos, ended_icos,
markets, market_cap_change_percentage_24h, eth_market_cap_in_pct, btc_market_cap_in_pct, altcoin_market_cap_in_pct.
markets, market_cap_change_percentage_24h, eth_market_cap_in_pct, btc_market_cap_in_pct, altcoin_market_cap_in_pct.
![image](https://user-images.githubusercontent.com/275820/123538175-d1514400-d733-11eb-9634-09c341f63cb9.png)
## news <a name="news"></a>
## news <a name="news"></a>
```text
usage: cg_news [-t --top] [-s --sortby] [--descend] [-l --links]
usage: cg_news [-t --top] [-s --sortby] [--descend] [-u --urls]
```
Shows latest news from <https://www.coingecko.com/en/news>. Display columns: index, title, author, posted columns.
@@ -75,11 +75,11 @@ You can sort by each of column above, using `--sort` parameter and also do it de
also possible to display urls to news with `--links` flag. If you want to display top 75 news, sorted by author use
`cg_news -t 75 -s author` if you want to see urls to source of the news use `cg_news -t 75 -l`
* -t/--top - number of news to display. One page of news contains 25 news, so to get 250 news script needs to scrape 10
- -t/--top - number of news to display. One page of news contains 25 news, so to get 250 news script needs to scrape 10
pages (it can take some time). Default 100. E.g `news --top 150`
* -s/--sortby - sort by given column. You can chose on from `index, title, author, posted`
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls and display only `index, url` columns
- -s/--sortby - sort by given column. You can chose on from `index, title, author, posted`
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls and display only `index, url` columns
![image](https://user-images.githubusercontent.com/275820/124304537-1e824b00-db64-11eb-946d-4eb28a37bd18.png)
@@ -98,17 +98,17 @@ defi_to_eth_ratio, trading_volume_24h, defi_dominance, top_coin_name, top_coin_d
## cg_stables <a name="cg_stables"></a>
```text
usage: cg_stables [-t --top] [-s --sortby] [--descend] [-l --links]
usage: cg_stables [-t --top] [-s --sortby] [--descend] [-u --urls]
```
Shows stablecoins by market capitalization. Stablecoins are cryptocurrencies that attempt to peg their market value to
some external reference like the U.S. dollar or to a commodity's price such as gold.
* -t/--top - number of coins to display. To display top 10 coins: `cg_stables --top 10 --sortby rank`
* -s/--sortby - sort by given column. You can sort data by
- -t/--top - number of coins to display. To display top 10 coins: `cg_stables --top 10 --sortby rank`
- -s/--sortby - sort by given column. You can sort data by
`rank, name, symbol, price, change_24h, exchanges, market_cap, change_30d`
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls e.g. `cg_stables --links`.
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls e.g. `cg_stables --links`.
If you will use this flag `rank, name, symbol, url` columns will be displayed.
![image](https://user-images.githubusercontent.com/275820/124305427-62298480-db65-11eb-9216-02cf9690c958.png)
@@ -140,16 +140,16 @@ Examples of NFT include crypto artwork, collectibles, game items, financial prod
## cg_exchanges <a name="cg_exchanges"></a>
```text
usage: cg_exchanges [-t --top] [-s --sortby] [--descend] [-l --links]
usage: cg_exchanges [-t --top] [-s --sortby] [--descend] [-u --urls]
```
Shows top crypto exchanges base on trust score.
* -t/--top - number of exchanges to display. To display top 10 exchanges by trust_score:
- -t/--top - number of exchanges to display. To display top 10 exchanges by trust_score:
`cg_exchanges --top 10 --sortby trust_score --descend`
* -s/--sortby - sort by given column. You can sort data by `rank, trust_score, id, name, country, established, trade_volume_24h_btc`
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls e.g. `cg_exchanges --links`.
- -s/--sortby - sort by given column. You can sort data by `rank, trust_score, id, name, country, established, trade_volume_24h_btc`
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls e.g. `cg_exchanges --links`.
Using `links` parameter will display only `rank, name, url` columns
![image](https://user-images.githubusercontent.com/275820/123539542-98689d80-d73a-11eb-9561-2de524d329d0.png)
@@ -162,10 +162,10 @@ usage: cg_exrates [-t --top] [-s --sortby] [--descend]
Shows list of crypto, fiats, commodity exchange rates from CoinGecko
* -t/--top - number of exchanges to display. To display top 10 exchanges by trust_score:
- -t/--top - number of exchanges to display. To display top 10 exchanges by trust_score:
`cg_exrates --top 10 --sortby name --descend`
* -s/--sortby - sort by given column. You can sort by `index,name,unit, value, type`
* --descend - flag to sort in descending order (lowest first)
- -s/--sortby - sort by given column. You can sort by `index,name,unit, value, type`
- --descend - flag to sort in descending order (lowest first)
![image](https://user-images.githubusercontent.com/275820/124305586-969d4080-db65-11eb-8cc9-7024ae06758a.png)
@@ -178,9 +178,9 @@ usage: cg_platforms [-t --top] [-s --sortby] [--descend]
Shows Top Crypto Financial Platforms in which you can borrow or lend your crypto e.g. Celsius, Nexo, Crypto.com, Aave
and others.
* -t/--top - number of crypto platforms to display. To display top 10 platforms: `cg_platforms --top 10 --sortby name`
* -s/--sortby - sort by given column. You can sort data by `rank, name, category, centralized`
* --descend - flag to sort in descending order (lowest first)
- -t/--top - number of crypto platforms to display. To display top 10 platforms: `cg_platforms --top 10 --sortby name`
- -s/--sortby - sort by given column. You can sort data by `rank, name, category, centralized`
- --descend - flag to sort in descending order (lowest first)
![image](https://user-images.githubusercontent.com/275820/123539607-e1205680-d73a-11eb-9ab4-b8998ef37731.png)
@@ -192,9 +192,9 @@ usage: cg_products [-t --top] [-s --sortby] [--descend]
Shows Top Crypto Financial Products with which you can earn yield, borrow or lend your crypto.
* -t/--top - number of crypto products to display. To display top 10 products: `cg_products --top 10 --sortby name`
* -s/--sortby - sort by given column. You can sort data by `rank, platform, identifier, supply_rate_percentage, borrow_rate_percentage`
* --descend - flag to sort in descending order (lowest first)
- -t/--top - number of crypto products to display. To display top 10 products: `cg_products --top 10 --sortby name`
- -s/--sortby - sort by given column. You can sort data by `rank, platform, identifier, supply_rate_percentage, borrow_rate_percentage`
- --descend - flag to sort in descending order (lowest first)
![image](https://user-images.githubusercontent.com/275820/124305624-a583f300-db65-11eb-819d-4bd9961d2a08.png)
@@ -207,9 +207,9 @@ usage: cg_indexes [-t --top] [-s --sortby] [--descend]
Shows list of crypto indexes from CoinGecko.Each crypto index is made up of a selection of cryptocurrencies, grouped
together and weighted by market cap.
* -t/--top - number of crypto indexes to display. To display top 10 indexes: `cg_indexes --top 10 --sortby name`
* -s/--sortby - sort by given column. You can sort data by `rank, name, id, market, last, is_multi_asset_composite`
* --descend - flag to sort in descending order (lowest first)
- -t/--top - number of crypto indexes to display. To display top 10 indexes: `cg_indexes --top 10 --sortby name`
- -s/--sortby - sort by given column. You can sort data by `rank, name, id, market, last, is_multi_asset_composite`
- --descend - flag to sort in descending order (lowest first)
![image](https://user-images.githubusercontent.com/275820/124305656-afa5f180-db65-11eb-91fc-a9b57e99e690.png)
@@ -223,28 +223,28 @@ Shows list of crypto derivatives from CoinGecko. Crypto derivatives are secondar
derive their value from a primary underlying asset. In this case, the primary asset would be a cryptocurrency such as
Bitcoin. The most popular crypto derivatives are crypto futures, crypto options, and perpetual contracts.
* -t/--top - number of crypto derivatives to display. To display top 10 derivatives: `cg_derivatives --top 10 --sortby symbol`
* -s/--sortby - sort by given column. You can sort by
- -t/--top - number of crypto derivatives to display. To display top 10 derivatives: `cg_derivatives --top 10 --sortby symbol`
- -s/--sortby - sort by given column. You can sort by
`rank, market, symbol, price, pct_change_24h, contract_type, basis, spread, funding_rate, volume_24h`
* --descend - flag to sort in descending order (lowest first)
- --descend - flag to sort in descending order (lowest first)
![image](https://user-images.githubusercontent.com/275820/124305694-b896c300-db65-11eb-9a15-dfd2d1e5832f.png)
## cg_categories <a name="cg_categories"></a>
```text
usage: cg_categories [-t --top] [-s --sortby] [--descend] [-l --links]
usage: cg_categories [-t --top] [-s --sortby] [--descend] [-u --urls]
```
Shows top cryptocurrency categories by market capitalization from <https://www.coingecko.com/en/categories>
It includes categories like: stablecoins, defi, solana ecosystem, polkadot ecosystem and many others.
* -t/--top - number of crypto categories to display. To display top 10 crypto categories:
- -t/--top - number of crypto categories to display. To display top 10 crypto categories:
`cg_categories --top 10 --sortby rank`
* -s/--sortby - sort by given column. You can sort by
- -s/--sortby - sort by given column. You can sort by
`rank, name, change_1h, change_24h, change_7d, market_cap, volume_24h, n_of_coins`
* --descend - flag to sort in descending order (lowest first)
* -l/--links - flag to show urls. Using this flag will add additional column with urls e.g. `cg_categories --links`.
- --descend - flag to sort in descending order (lowest first)
- -u/--urls - flag to show urls. Using this flag will add additional column with urls e.g. `cg_categories --links`.
Using `links` parameter will display only `rank, name, url` columns
![image](https://user-images.githubusercontent.com/275820/124305739-c51b1b80-db65-11eb-9f35-6dd1fceb731d.png)
@@ -259,7 +259,7 @@ Shows overview of public companies that holds ethereum or bitcoin
Displays most important metrics like: Total Bitcoin/Ethereum Holdings, Total Value (USD), Public Companies
Bitcoin/Ethereum Dominance, Companies.
* -c/--coin - chose a coin. Only available for ethereum or bitcoin. If you want to see overview of public companies
- -c/--coin - chose a coin. Only available for ethereum or bitcoin. If you want to see overview of public companies
that holds ethereum use `hold --coin ethereum` for bitcoin `hold --coin bitcoin`
![image](https://user-images.githubusercontent.com/275820/123539842-f77ae200-d73b-11eb-9d6c-feb2fbd98c01.png)
@@ -273,9 +273,9 @@ usage: cg_companies [-c --coin] [- --links]
Shows Ethereum/Bitcoin Holdings by Public Companies. Track publicly traded companies around the world that are buying
ethereum as part of corporate treasury
* -c/--coin - chose a coin. Only available for ethereum or bitcoin. List of public companies that holds ethereum
- -c/--coin - chose a coin. Only available for ethereum or bitcoin. List of public companies that holds ethereum
`cg_companies --coin ethereum` for bitcoin `cg_companies --coin bitcoin`
* -l/--links - You can use additional flag `--links` to see urls to announcement about buying btc or eth by given
- -u/--urls - You can use additional flag `--links` to see urls to announcement about buying btc or eth by given
company. In this case of usage `links` only columns `rank, company, url` will be displayed
![image](https://user-images.githubusercontent.com/275820/124305825-e419ad80-db65-11eb-85b4-35f2e163f3fd.png)
@@ -300,13 +300,13 @@ usage: cp_info [-t --top] [-s --sort] [--descend]
Show basic coin information for all coins from CoinPaprika
* --vs: The currency to look against. Available options are:
- --vs: The currency to look against. Available options are:
`BTC,ETH,USD,EUR,PLN,KRW,GBP,CAD,JPY,RUB,TRY,NZD,AUD,CHF,UAH,HKD,SGD,NGN,PHP,MXN,BRL,THB,CLP,CNY,CZK,DKK,HUF,IDR,ILS,INR,MYR,NOK,PKR,SEK,TWD,ZAR,VND,BOB,COP,PEN,ARS,ISK`.
Default: USD
* -t/--top: Number of coins to display. To display top 10 coins: `cp_info --top 10 --sort rank`
* -s/--sort: Sort by given column. You can chose on from
- -t/--top: Number of coins to display. To display top 10 coins: `cp_info --top 10 --sort rank`
- -s/--sort: Sort by given column. You can chose on from
`rank, name, symbol, price, volume_24h, circulating_supply, total_supply, max_supply, ath_price,market_cap, beta_value`.
* --descend: Flag to sort in descending order (lowest first)
- --descend: Flag to sort in descending order (lowest first)
## cp_markets <a name="cp_markets"></a>
@@ -316,13 +316,13 @@ usage: cp_markets [--vs] [-t --top] [-s --sort] [--descend]
Show market related (price, supply, volume) coin information for all coins on CoinPaprika
* --vs: The currency to look against. Available options are:
- --vs: The currency to look against. Available options are:
`BTC,ETH,USD,EUR,PLN,KRW,GBP,CAD,JPY,RUB,TRY,NZD,AUD,CHF,UAH,HKD,SGD,NGN,PHP,MXN,BRL,THB,CLP,CNY,CZK,DKK,HUF,IDR,ILS,INR,MYR,NOK,PKR,SEK,TWD,ZAR,VND,BOB,COP,PEN,ARS,ISK`.
Default: USD
* -t/--top: Number of coins to display. To display top 10 coins: `cp_markets --top 10 --sort rank`
* -s/--sort: Sort by given column. You can chose on from
- -t/--top: Number of coins to display. To display top 10 coins: `cp_markets --top 10 --sort rank`
- -s/--sort: Sort by given column. You can chose on from
`rank, name, symbol, price, volume_24h, mcap_change_24h, pct_change_1h, pct_change_24h, ath_price, pct_from_ath`.
* --descend: Flag to sort in descending order (lowest first)
- --descend: Flag to sort in descending order (lowest first)
## cp_exchanges <a name="cp_exchanges"></a>
@@ -332,28 +332,28 @@ usage: cp_exchanges [--vs] [-t --top] [-s --sort] [--descend]
Show all exchanges from CoinPaprika
* --vs: The currency to look against. Available options are:
- --vs: The currency to look against. Available options are:
`BTC,ETH,USD,EUR,PLN,KRW,GBP,CAD,JPY,RUB,TRY,NZD,AUD,CHF,UAH,HKD,SGD,NGN,PHP,MXN,BRL,THB,CLP,CNY,CZK,DKK,HUF,IDR,ILS,INR,MYR,NOK,PKR,SEK,TWD,ZAR,VND,BOB,COP,PEN,ARS,ISK`.
Default: USD
* -t/--top: Number of coins to display. To display top 10 coins: `cp_exchanges --top 10 --sort rank`
* -s/--sort: Sort by given column. You can chose on from
- -t/--top: Number of coins to display. To display top 10 coins: `cp_exchanges --top 10 --sort rank`
- -s/--sort: Sort by given column. You can chose on from
`rank, name, currencies, markets, fiats, confidence, volume_24h, volume_7d, volume_30d, sessions_per_month`.
* --descend: Flag to sort in descending order (lowest first)
- --descend: Flag to sort in descending order (lowest first)
## cp_exmarkets <a name="cp_exmarkets"></a>
```text
usage: cp_exmarkets [-e --exchange] [-t --top] [-s --sort] [--descend] [-l --links]
usage: cp_exmarkets [-e --exchange] [-t --top] [-s --sort] [--descend] [-u --urls]
```
Get all exchange markets found for given exchange
* -e/--exchange: Identifier of exchange e.g for Binance Exchange: `binance`
* -t/--top: Number of markets to display. To display top 10 markets: `cp_exmarkets --top 10 --sort reported_volume_24h_share`
* -s/--sort: Sort by given column. You can chose on from
- -e/--exchange: Identifier of exchange e.g for Binance Exchange: `binance`
- -t/--top: Number of markets to display. To display top 10 markets: `cp_exmarkets --top 10 --sort reported_volume_24h_share`
- -s/--sort: Sort by given column. You can chose on from
`pair , base_currency_name, quote_currency_name, category, reported_volume_24h_share, trust_score, market_url`.
* --descend: Flag to sort in descending order (lowest first)
* -l/--links: Flag to show urls. Using this flag will add additional column with urls e.g. `cp_exmarkets --links`.
- --descend: Flag to sort in descending order (lowest first)
- -u/--urls: Flag to show urls. Using this flag will add additional column with urls e.g. `cp_exmarkets --links`.
If you will use this flag url column will be displayed.
## cp_platforms <a name="cp_platforms"></a>
@@ -372,7 +372,7 @@ usage: cp_contracts [-p --platform] [-t --top] [-s --sort] [--descend]
Gets all contract addresses for given blockchain platform
* -p/--platform: smart contract platform id. Available coins:
- -p/--platform: smart contract platform id. Available coins:
```text
btc-bitcoin, eos-eos, eth-ethereum, xrp-xrp, bch-bitcoin-cash, xem-nem, neo-neo, xlm-stellar, etc-ethereum-classic,
@@ -382,7 +382,7 @@ ncakeswap, fsxu-flashx-ultra, chik-chickenkebab-finance, jgn-juggernaut7492, crx
eubi-eubi-token, swam-swapmatic-token, shells-shells
```
* -t/--top: Number of smart contracts to display. To display 10 smart contracts:
- -t/--top: Number of smart contracts to display. To display 10 smart contracts:
`cp_contracts -p eth-ethereum --top 10 --sort index`
* -s/--sort: Sort by given column. You can chose on from `index, id, type, active, address`.
* --descend: Flag to sort in descending order (lowest first)
- -s/--sort: Sort by given column. You can chose on from `index, id, type, active, address`.
- --descend: Flag to sort in descending order (lowest first)

View File

@@ -6,6 +6,17 @@ from gamestonk_terminal.cryptocurrency.coinbase_helpers import (
make_coinbase_request,
)
PAIRS_FILTERS = [
"id",
"display_name",
"base_currency",
"quote_currency",
"base_min_size",
"base_max_size",
"min_market_funds",
"max_market_funds",
]
def get_trading_pairs() -> pd.DataFrame:
"""Get a list of available currency pairs for trading. [Source: Coinbase]

View File

@@ -7,6 +7,59 @@ import pandas as pd
from dateutil import parser
from gamestonk_terminal.cryptocurrency.coinpaprika_helpers import PaprikaSession
MARKETS_FILTERS = [
"rank",
"name",
"symbol",
"price",
"volume_24h",
"mcap_change_24h",
"pct_change_1h",
"pct_change_24h",
"ath_price",
"pct_from_ath",
]
EXMARKETS_FILTERS = [
"pair",
"base_currency_name",
"quote_currency_name",
"category",
"reported_volume_24h_share",
"trust_score",
"market_url",
]
INFO_FILTERS = [
"rank",
"name",
"symbol",
"price",
"volume_24h",
"circulating_supply",
"total_supply",
"max_supply",
"ath_price",
"market_cap",
"beta_value",
]
EXCHANGES_FILTERS = [
"rank",
"name",
"currencies",
"markets",
"fiats",
"confidence",
"volume_24h",
"volume_7d",
"volume_30d",
"sessions_per_month",
]
CONTRACTS_FILTERS = ["id", "type", "active"]
def get_global_market() -> pd.DataFrame:
"""Return data frame with most important global crypto statistics like:
@@ -313,7 +366,7 @@ def get_contract_platform(platform_id: str = "eth-ethereum") -> pd.DataFrame:
Returns
-------
pandas.DataFrame
id, type, active, balance
id, type, active
"""
session = PaprikaSession()
@@ -321,4 +374,4 @@ def get_contract_platform(platform_id: str = "eth-ethereum") -> pd.DataFrame:
session.ENDPOINTS["contract_platform_addresses"].format(platform_id)
)
return pd.DataFrame(contract_platforms)[["id", "type", "active", "balance"]]
return pd.DataFrame(contract_platforms)[["id", "type", "active"]]

View File

@@ -11,6 +11,28 @@ import requests
import gamestonk_terminal.config_terminal as cfg
CATEGORIES = ["news", "media"]
SORT_FILTERS = [
"published_at",
"domain",
"title",
"negative_votes",
"positive_votes",
]
FILTERS = [
"rising",
"hot",
"bullish",
"bearish",
"important",
"saved",
"lol",
]
REGIONS = ["en", "de", "es", "fr", "nl", "it", "pt", "ru"]
class ApiKeyException(Exception):
"""Api Key Exception object"""
@@ -59,7 +81,7 @@ def make_request(**kwargs: Any) -> dict:
]:
url += f"&filter={filter_}"
if region and region in ["en", "de", "es", "fr", "nl", "it", "pt", "ru"]:
if region and region in REGIONS:
url += f"&regions={region}"
response = requests.get(url)
@@ -124,7 +146,7 @@ def get_news(
DataFrame with recent news from different sources filtered by provided parameters.
"""
if post_kind not in ["news", "media"]:
if post_kind not in CATEGORIES:
post_kind = "news"
results = []

View File

@@ -15,8 +15,72 @@ from gamestonk_terminal.cryptocurrency.pycoingecko_helpers import (
clean_row,
scrape_gecko_data,
GECKO_BASE_URL,
RetryError,
)
HOLD_COINS = ["ethereum", "bitcoin"]
NEWS_FILTERS = ["Index", "Title", "Author", "Posted"]
CATEGORIES_FILTERS = [
"Rank",
"Name",
"Change_1h",
"Change_24h",
"Change_7d",
"Market_Cap",
"Volume_24h",
"Coins",
]
STABLES_FILTERS = [
"Rank",
"Name",
"Symbol",
"Price",
"Change_24h",
"Exchanges",
"Market_Cap",
"Change_30d",
]
PRODUCTS_FILTERS = [
"Rank",
"Platform",
"Identifier",
"Supply_Rate",
"Borrow_Rate",
]
PLATFORMS_FILTERS = ["Rank", "Name", "Category", "Centralized"]
EXCHANGES_FILTERS = [
"Rank",
"Trust_Score",
"Id",
"Name",
"Country",
"Year Established",
"Trade_Volume_24h_BTC",
]
EXRATES_FILTERS = ["Index", "Name", "Unit", "Value", "Type"]
INDEXES_FILTERS = ["Rank", "Name", "Id", "Market", "Last", "MultiAsset"]
DERIVATIVES_FILTERS = [
"Rank",
"Market",
"Symbol",
"Price",
"Pct_Change_24h",
"Contract_Type",
"Basis",
"Spread",
"Funding_Rate",
"Volume_24h",
]
def get_holdings_overview(endpoint: str = "bitcoin") -> pd.DataFrame:
"""Scrapes overview of public companies that holds ethereum or bitcoin
@@ -34,9 +98,12 @@ def get_holdings_overview(endpoint: str = "bitcoin") -> pd.DataFrame:
"""
url = f"https://www.coingecko.com/en/public-companies-{endpoint}"
rows = scrape_gecko_data(url).find_all(
"span", class_="overview-box d-inline-block p-3 mr-2"
)
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
rows = scraped_data.find_all("span", class_="overview-box d-inline-block p-3 mr-2")
kpis = {}
for row in rows:
row_cleaned = clean_row(row)
@@ -69,7 +136,12 @@ def get_companies_assets(endpoint: str = "bitcoin") -> pd.DataFrame:
"""
url = f"https://www.coingecko.com/en/public-companies-{endpoint}"
rows = scrape_gecko_data(url).find("tbody").find_all("tr")
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
rows = scraped_data.find("tbody").find_all("tr")
results = []
for row in rows:
link = row.find("a")["href"]
@@ -110,7 +182,12 @@ def get_news(n: int = 100) -> pd.DataFrame:
dfs = []
for page in range(1, n_of_pages):
url = f"https://www.coingecko.com/en/news?page={page}"
rows = scrape_gecko_data(url).find_all("article")
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
rows = scraped_data.find_all("article")
results = []
for row in rows:
header = row.find("header")
@@ -162,7 +239,12 @@ def get_top_crypto_categories() -> pd.DataFrame:
"Url",
]
url = "https://www.coingecko.com/en/categories"
rows = scrape_gecko_data(url).find("tbody").find_all("tr")
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
rows = scraped_data.find("tbody").find_all("tr")
results = []
for row in rows:
url = GECKO_BASE_URL + row.find("a")["href"]
@@ -216,7 +298,12 @@ def get_stable_coins() -> pd.DataFrame:
"Url",
]
url = "https://www.coingecko.com/en/stablecoins"
rows = scrape_gecko_data(url).find("tbody").find_all("tr")
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
rows = scraped_data.find("tbody").find_all("tr")
results = []
for row in rows:
link = GECKO_BASE_URL + row.find("a")["href"]
@@ -265,8 +352,12 @@ def get_nft_of_the_day() -> pd.DataFrame:
"""
url = "https://www.coingecko.com/en/nft"
soup = scrape_gecko_data(url)
row = soup.find("div", class_="tw-px-4 tw-py-5 sm:tw-p-6")
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
row = scraped_data.find("div", class_="tw-px-4 tw-py-5 sm:tw-p-6")
try:
*author, description, _ = clean_row(row)
if len(author) > 3:
@@ -303,9 +394,12 @@ def get_nft_market_status() -> pd.DataFrame:
"""
url = "https://www.coingecko.com/en/nft"
rows = scrape_gecko_data(url).find_all(
"span", class_="overview-box d-inline-block p-3 mr-2"
)
try:
scraped_data = scrape_gecko_data(url)
except RetryError as e:
print(e)
return pd.DataFrame()
rows = scraped_data.find_all("span", class_="overview-box d-inline-block p-3 mr-2")
kpis = {}
for row in rows:
value, *kpi = clean_row(row)

View File

@@ -143,27 +143,31 @@ def display_nft_market_status(export: str) -> None:
"""
df = gecko.get_nft_market_status()
if not df.empty:
if gtff.USE_TABULATE_DF:
print(
tabulate(
df,
headers=df.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
if gtff.USE_TABULATE_DF:
print(
tabulate(
df,
headers=df.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"nft",
df,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"nft",
df,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_exchange_rates(sortby: str, descend: bool, top: int, export: str) -> None:
@@ -183,26 +187,31 @@ def display_exchange_rates(sortby: str, descend: bool, top: int, export: str) ->
df = gecko.get_exchange_rates().sort_values(by=sortby, ascending=descend)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if not df.empty:
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"exrates",
df,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"exrates",
df,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_global_market_info(export: str) -> None:
@@ -221,26 +230,31 @@ def display_global_market_info(export: str) -> None:
df = gecko.get_global_info()
if gtff.USE_TABULATE_DF:
print(
tabulate(
df,
headers=df.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if not df.empty:
if gtff.USE_TABULATE_DF:
print(
tabulate(
df,
headers=df.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"global",
df,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"global",
df,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_global_defi_info(export: str) -> None:
@@ -254,26 +268,31 @@ def display_global_defi_info(export: str) -> None:
df = gecko.get_global_defi_info()
if gtff.USE_TABULATE_DF:
print(
tabulate(
df,
headers=df.columns,
floatfmt=".1f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if not df.empty:
if gtff.USE_TABULATE_DF:
print(
tabulate(
df,
headers=df.columns,
floatfmt=".1f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"defi",
df,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"defi",
df,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_stablecoins(
@@ -295,35 +314,41 @@ def display_stablecoins(
Export dataframe data to csv,json,xlsx file
"""
df = gecko.get_stable_coins().sort_values(by=sortby, ascending=descend)
df = gecko.get_stable_coins()
df_data = df.copy()
if not df.empty:
df = df.sort_values(by=sortby, ascending=descend)
df_data = df.copy()
if links is True:
df = df[["Rank", "Name", "Symbol", "Url"]]
else:
df.drop("Url", axis=1, inplace=True)
if links is True:
df = df[["Rank", "Name", "Symbol", "Url"]]
else:
df.drop("Url", axis=1, inplace=True)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".0f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".0f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"stables",
df_data,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"stables",
df_data,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_news(
@@ -345,39 +370,45 @@ def display_news(
Export dataframe data to csv,json,xlsx file
"""
df = gecko.get_news(n=top).sort_values(by=sortby, ascending=descend)
df = gecko.get_news(n=top)
df_data = df.copy()
if not df.empty:
df = df.sort_values(by=sortby, ascending=descend)
df_data = df.copy()
df["Title"] = df["Title"].apply(
lambda x: "\n".join(textwrap.wrap(x, width=65)) if isinstance(x, str) else x
)
df["Title"] = df["Title"].apply(
lambda x: "\n".join(textwrap.wrap(x, width=65)) if isinstance(x, str) else x
)
if not links:
df.drop("Url", axis=1, inplace=True)
else:
df = df[["Index", "Url"]]
if not links:
df.drop("Url", axis=1, inplace=True)
else:
df = df[["Index", "Url"]]
if gtff.USE_TABULATE_DF:
print(
tabulate(
df,
headers=df.columns,
floatfmt=".0f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if gtff.USE_TABULATE_DF:
print(
tabulate(
df,
headers=df.columns,
floatfmt=".0f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"news",
df_data,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"news",
df_data,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_categories(
@@ -401,35 +432,41 @@ def display_categories(
Export dataframe data to csv,json,xlsx file
"""
df = gecko.get_top_crypto_categories().sort_values(by=sortby, ascending=descend)
df = gecko.get_top_crypto_categories()
df_data = df.copy()
if not df.empty:
df = df.sort_values(by=sortby, ascending=descend)
df_data = df.copy()
if not links:
df.drop("Url", axis=1, inplace=True)
else:
df = df[["Rank", "Name", "Url"]]
if not links:
df.drop("Url", axis=1, inplace=True)
else:
df = df[["Rank", "Name", "Url"]]
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".0f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".0f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"categories",
df_data,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"categories",
df_data,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_exchanges(
@@ -451,33 +488,40 @@ def display_exchanges(
Export dataframe data to csv,json,xlsx file
"""
df = gecko.get_exchanges().sort_values(by=sortby, ascending=descend)
df = gecko.get_exchanges()
if links is True:
df = df[["Rank", "Name", "Url"]]
else:
df.drop("Url", axis=1, inplace=True)
if not df.empty:
df = df.sort_values(by=sortby, ascending=descend)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".1f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if links is True:
df = df[["Rank", "Name", "Url"]]
else:
df.drop("Url", axis=1, inplace=True)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".1f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"exchanges",
df,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"exchanges",
df,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_platforms(sortby: str, descend: bool, top: int, export: str) -> None:
@@ -495,28 +539,35 @@ def display_platforms(sortby: str, descend: bool, top: int, export: str) -> None
Export dataframe data to csv,json,xlsx file
"""
df = gecko.get_financial_platforms().sort_values(by=sortby, ascending=descend)
df = gecko.get_financial_platforms()
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if not df.empty:
df = df.sort_values(by=sortby, ascending=descend)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"platforms",
df,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"platforms",
df,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_products(sortby: str, descend: bool, top: int, export: str) -> None:
@@ -534,28 +585,35 @@ def display_products(sortby: str, descend: bool, top: int, export: str) -> None:
Export dataframe data to csv,json,xlsx file
"""
df = gecko.get_finance_products().sort_values(by=sortby, ascending=descend)
df = gecko.get_finance_products()
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if not df.empty:
df = df.sort_values(by=sortby, ascending=descend)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"products",
df,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"products",
df,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_indexes(sortby: str, descend: bool, top: int, export: str) -> None:
@@ -573,28 +631,34 @@ def display_indexes(sortby: str, descend: bool, top: int, export: str) -> None:
Export dataframe data to csv,json,xlsx file
"""
df = gecko.get_indexes().sort_values(by=sortby, ascending=descend)
df = gecko.get_indexes()
if not df.empty:
df = df.sort_values(by=sortby, ascending=descend)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"indexes",
df,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"indexes",
df,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")
def display_derivatives(sortby: str, descend: bool, top: int, export: str) -> None:
@@ -612,25 +676,32 @@ def display_derivatives(sortby: str, descend: bool, top: int, export: str) -> No
Export dataframe data to csv,json,xlsx file
"""
df = gecko.get_derivatives().sort_values(by=sortby, ascending=descend)
df = gecko.get_derivatives()
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".4f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
if not df.empty:
df = df.sort_values(by=sortby, ascending=descend)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(top),
headers=df.columns,
floatfmt=".4f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"derivatives",
df,
)
else:
print(df.to_string, "\n")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"derivatives",
df,
)
print("")
print("Unable to retrieve data from CoinGecko.")
print("")

View File

@@ -3,11 +3,10 @@ __docformat__ = "numpy"
import argparse
import difflib
from typing import List
from typing import List, Union
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal import feature_flags as gtff
@@ -23,9 +22,12 @@ from gamestonk_terminal.helper_funcs import (
)
from gamestonk_terminal.menu import session
from gamestonk_terminal.common.prediction_techniques import (
arima_model,
arima_view,
ets_model,
ets_view,
knn_view,
mc_model,
neural_networks_view,
regression_view,
pred_helper,
@@ -38,9 +40,23 @@ class PredictionTechniquesController:
"""Prediction Techniques Controller class"""
# Command choices
CHOICES = ["cls", "?", "help", "q", "quit", "load", "pick"]
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_MODELS = [
CHOICES_COMMANDS = [
"pick",
"load",
"ets",
"knn",
"regression",
@@ -51,15 +67,21 @@ class PredictionTechniquesController:
"conv1d",
"mc",
]
CHOICES += CHOICES_MODELS
CHOICES += CHOICES_COMMANDS
sampling_map = {"H": "Hour", "D": "Day"}
def __init__(
self,
coin: str,
data: pd.DataFrame,
queue: List[str] = None,
):
"""Constructor"""
self.pred_parser = argparse.ArgumentParser(add_help=False, prog="pred")
self.pred_parser.add_argument(
"cmd",
choices=self.CHOICES,
)
data["Returns"] = data["Close"].pct_change()
data["LogRet"] = np.log(data["Close"]) - np.log(data["Close"].shift(1))
data = data.dropna()
@@ -68,28 +90,35 @@ class PredictionTechniquesController:
self.coin = coin
self.resolution = "1D"
self.target = "Close"
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 c_help.INTERVALS}
choices["pick"] = {c: {} for c in self.data.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"""
help_string = f"""
Prediction Techniques:
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon program
Prediction Techniques Menu:
load load new ticker
pick pick new target variable
Coin Loaded: {self.coin}
Target Column: {self.target}
Models:
ets exponential smoothing (e.g. Holt-Winters)
knn k-Nearest Neighbors
@@ -106,30 +135,48 @@ Models:
def switch(self, an_input: str):
"""Process and dispatch input
Parameters
-------
an_input : str
string with input arguments
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -137,17 +184,50 @@ Models:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program"""
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "pred")
self.queue.insert(0, "crypto")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "reset", "crypto", "pred"]
@try_except
def call_load(self, other_args: List[str]):
@@ -189,24 +269,23 @@ Models:
type=str,
dest="resolution",
help="How often to resample data.",
choices=["1H", "3H", "6H", "1D"],
choices=c_help.INTERVALS,
)
if other_args and "-" not in other_args[0]:
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)
if not ns_parser:
return
# TODO: improve loading from this submenu. Currently would recommend using this menu after using main load
self.coin = ns_parser.coin
self.data = c_help.load_cg_coin_data(
ns_parser.coin, ns_parser.currency, ns_parser.days, ns_parser.resolution
)
res = ns_parser.resolution if ns_parser.days < 90 else "1D"
self.resolution = res
print(
f"{ns_parser.days} Days of {self.coin} vs {ns_parser.currency} loaded with {res} resolution.\n"
)
if ns_parser:
self.coin = ns_parser.coin
self.data = c_help.load_cg_coin_data(
ns_parser.coin, ns_parser.currency, ns_parser.days, ns_parser.resolution
)
res = ns_parser.resolution if ns_parser.days < 90 else "1D"
self.resolution = res
print(
f"{ns_parser.days} Days of {self.coin} vs {ns_parser.currency} loaded with {res} resolution.\n"
)
return self.queue
@try_except
def call_pick(self, other_args: List[str]):
@@ -230,10 +309,10 @@ Models:
other_args.insert(0, "-t")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
self.target = ns_parser.target
print("")
if ns_parser:
self.target = ns_parser.target
print("")
return self.queue
@try_except
def call_ets(self, other_args: List[str]):
@@ -272,7 +351,7 @@ Models:
"--trend",
action="store",
dest="trend",
choices=["N", "A", "Ad"],
choices=ets_model.TRENDS,
default="N",
help="Trend component: N: None, A: Additive, Ad: Additive Damped.",
)
@@ -281,7 +360,7 @@ Models:
"--seasonal",
action="store",
dest="seasonal",
choices=["N", "A", "M"],
choices=ets_model.SEASONS,
default="N",
help="Seasonality component: N: None, A: Additive, M: Multiplicative.",
)
@@ -306,37 +385,35 @@ Models:
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
)
if not ns_parser:
return
if ns_parser:
if ns_parser.s_end_date:
if ns_parser.s_end_date < self.data.index[0]:
print(
"Backtesting not allowed, since End Date is older than Start Date of historical data\n"
)
return self.queue
if ns_parser.s_end_date:
if ns_parser.s_end_date < get_next_stock_market_days(
last_stock_day=self.data.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"
)
return self.queue
if ns_parser.s_end_date < self.data.index[0]:
print(
"Backtesting not allowed, since End Date is older than Start Date of historical data\n"
)
return
if ns_parser.s_end_date < get_next_stock_market_days(
last_stock_day=self.data.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"
)
return
ets_view.display_exponential_smoothing(
ticker=self.coin,
values=self.data[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,
time_res=self.resolution,
)
ets_view.display_exponential_smoothing(
ticker=self.coin,
values=self.data[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,
time_res=self.resolution,
)
return self.queue
@try_except
def call_knn(self, other_args: List[str]):
@@ -411,21 +488,22 @@ Models:
default=True,
help="Specify if shuffling validation inputs.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
knn_view.display_k_nearest_neighbors(
ticker=self.coin,
data=self.data[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,
time_res=self.resolution,
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.coin,
data=self.data[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,
time_res=self.resolution,
)
return self.queue
@try_except
def call_regression(self, other_args: List[str]):
@@ -495,36 +573,36 @@ Models:
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
)
if not ns_parser:
return
# BACKTESTING CHECK
if ns_parser.s_end_date:
if ns_parser.s_end_date < self.data.index[0]:
print(
"Backtesting not allowed, since End Date is older than Start Date of historical data\n"
)
return
if ns_parser:
# BACKTESTING CHECK
if ns_parser.s_end_date:
if ns_parser.s_end_date < self.data.index[0]:
print(
"Backtesting not allowed, since End Date is older than Start Date of historical data\n"
)
return self.queue
if ns_parser.s_end_date < get_next_stock_market_days(
last_stock_day=self.data.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"
)
return
if ns_parser.s_end_date < get_next_stock_market_days(
last_stock_day=self.data.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"
)
return self.queue
regression_view.display_regression(
dataset=self.coin,
values=self.data[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,
time_res=self.resolution,
)
regression_view.display_regression(
dataset=self.coin,
values=self.data[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,
time_res=self.resolution,
)
return self.queue
@try_except
def call_arima(self, other_args: List[str]):
@@ -560,7 +638,7 @@ Models:
dest="s_ic",
type=str,
default="aic",
choices=["aic", "aicc", "bic", "hqic", "oob"],
choices=arima_model.ICS,
help="information criteria.",
)
parser.add_argument(
@@ -600,39 +678,37 @@ Models:
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
)
if not ns_parser:
return
if ns_parser:
# BACKTESTING CHECK
if ns_parser.s_end_date:
if ns_parser.s_end_date < self.data.index[0]:
print(
"Backtesting not allowed, since End Date is older than Start Date of historical data\n"
)
return self.queue
# BACKTESTING CHECK
if ns_parser.s_end_date:
if ns_parser.s_end_date < get_next_stock_market_days(
last_stock_day=self.data.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"
)
return self.queue
if ns_parser.s_end_date < self.data.index[0]:
print(
"Backtesting not allowed, since End Date is older than Start Date of historical data\n"
)
return
if ns_parser.s_end_date < get_next_stock_market_days(
last_stock_day=self.data.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"
)
return
arima_view.display_arima(
dataset=self.coin,
values=self.data[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,
time_res=self.resolution,
)
arima_view.display_arima(
dataset=self.coin,
values=self.data[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,
time_res=self.resolution,
)
return self.queue
@try_except
def call_mlp(self, other_args: List[str]):
@@ -643,27 +719,27 @@ Models:
description="""Multi-Layered-Perceptron. """,
other_args=other_args,
)
if not ns_parser:
return
neural_networks_view.display_mlp(
dataset=self.coin,
data=self.data[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,
time_res=self.resolution,
)
if ns_parser:
neural_networks_view.display_mlp(
dataset=self.coin,
data=self.data[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,
time_res=self.resolution,
)
except Exception as e:
print(e, "\n")
return self.queue
finally:
pred_helper.restore_env()
return self.queue
def call_rnn(self, other_args: List[str]):
"""Process rnn command"""
@@ -673,28 +749,27 @@ Models:
description="""Recurrent Neural Network. """,
other_args=other_args,
)
if not ns_parser:
return
neural_networks_view.display_rnn(
dataset=self.coin,
data=self.data[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,
time_res=self.resolution,
)
if ns_parser:
neural_networks_view.display_rnn(
dataset=self.coin,
data=self.data[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,
time_res=self.resolution,
)
except Exception as e:
print(e, "\n")
return self.queue
finally:
pred_helper.restore_env()
return self.queue
def call_lstm(self, other_args: List[str]):
"""Process lstm command"""
@@ -704,27 +779,27 @@ Models:
description="""Long-Short Term Memory. """,
other_args=other_args,
)
if not ns_parser:
return
neural_networks_view.display_lstm(
dataset=self.coin,
data=self.data[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,
time_res=self.resolution,
)
if ns_parser:
neural_networks_view.display_lstm(
dataset=self.coin,
data=self.data[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,
time_res=self.resolution,
)
except Exception as e:
print(e, "\n")
return self.queue
finally:
pred_helper.restore_env()
return self.queue
def call_conv1d(self, other_args: List[str]):
"""Process conv1d command"""
@@ -734,28 +809,28 @@ Models:
description="""1D CNN.""",
other_args=other_args,
)
if not ns_parser:
return
neural_networks_view.display_conv1d(
dataset=self.coin,
data=self.data[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,
time_res=self.resolution,
)
if ns_parser:
neural_networks_view.display_conv1d(
dataset=self.coin,
data=self.data[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,
time_res=self.resolution,
)
except Exception as e:
print(e, "\n")
return self.queue
finally:
pred_helper.restore_env()
return self.queue
@try_except
def call_mc(self, other_args: List[str]):
@@ -785,7 +860,7 @@ Models:
)
parser.add_argument(
"--dist",
choices=["normal", "lognormal"],
choices=mc_model.DISTRIBUTIONS,
default="lognormal",
dest="dist",
help="Whether to model returns or log returns",
@@ -794,55 +869,94 @@ Models:
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_FIGURES_ALLOWED
)
if not ns_parser:
return
if self.target != "Close":
print("MC Prediction designed for AdjClose prices")
return
print("MC Prediction designed for AdjClose prices\n")
return self.queue
mc_view.display_mc_forecast(
data=self.data[self.target],
n_future=ns_parser.n_days,
n_sims=ns_parser.n_sims,
use_log=ns_parser.dist == "lognormal",
export=ns_parser.export,
time_res=self.resolution,
)
if ns_parser:
mc_view.display_mc_forecast(
data=self.data[self.target],
n_future=ns_parser.n_days,
n_sims=ns_parser.n_sims,
use_log=ns_parser.dist == "lognormal",
export=ns_parser.export,
time_res=self.resolution,
)
return self.queue
def menu(coin: str, data: pd.DataFrame):
def menu(coin: str, data: pd.DataFrame, queue: List[str] = None):
"""Prediction Techniques Menu"""
pred_controller = PredictionTechniquesController(coin, data)
pred_controller.call_help(None)
pred_controller = PredictionTechniquesController(coin, data, 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()} /crypto/pred/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in pred_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (crypto)>(pred)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (crypto)>(pred)> ")
# 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()} /crypto/pred/ $ ",
completer=pred_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /crypto/pred/ $ ")
try:
plt.close("all")
process_input = pred_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
pred_controller.queue = pred_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
similar_cmd = difflib.get_close_matches(
an_input, pred_controller.CHOICES, n=1, cutoff=0.7
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:
print(f"Did you mean '{similar_cmd[0]}'?\n")
continue
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 = []

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,7 @@ __docformat__ = "numpy"
import argparse
import difflib
from typing import List
from typing import List, Union, Dict
from colorama import Style
from prompt_toolkit.completion import NestedCompleter
@@ -23,34 +23,55 @@ from gamestonk_terminal.menu import session
class FredController:
CHOICES = ["cls", "?", "help", "q", "quit"]
"""FRED Controller Class"""
CHOICES = [
"cls",
"cd",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
"home",
]
CHOICES_COMMANDS = ["search", "add", "rmv", "plot"]
CHOICES += CHOICES_COMMANDS
def __init__(self):
def __init__(self, queue: List[str] = None):
"""Constructor"""
self.fred_parser = argparse.ArgumentParser(add_help=False, prog="fred")
self.fred_parser.add_argument(
"cmd",
choices=self.CHOICES,
)
self.current_series = dict()
self.current_series: Dict = dict()
self.current_long_id = 0
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"""
id_string = ""
for s_id, sub_dict in self.current_series.items():
id_string += f" {s_id.upper()}{(self.current_long_id-len(s_id)) * ' '} : {sub_dict['title']}\n"
help_text = f"""
What do you want to do?
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon program
search search FRED series notes
add add series ID to list
rmv remove series ID from list
@@ -66,28 +87,40 @@ Current Series IDs:
Returns
-------
MENU_GO_BACK, MENU_QUIT, MENU_RESET
MENU_GO_BACK - Show main context menu again
MENU_QUIT - Quit terminal
MENU_RESET - Reset terminal and go back to same previous menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.fred_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -95,17 +128,49 @@ Current Series IDs:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_quit(self, _):
"""Process Quit command - exit the program"""
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "fred")
self.queue.insert(0, "economy")
self.queue.insert(0, "r")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "r", "economy", "fred"]
@try_except
def call_search(self, other_args: List[str]):
@@ -134,17 +199,17 @@ Current Series IDs:
default=5,
help="Maximum number of series notes to display.",
)
if other_args and "-" not in other_args[0]:
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-s")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
fred_view.notes(
series_term=ns_parser.series_term,
num=ns_parser.num,
)
fred_view.notes(
series_term=ns_parser.series_term,
num=ns_parser.num,
)
return self.queue
@try_except
def call_add(self, other_args: List[str]):
@@ -162,25 +227,24 @@ Current Series IDs:
type=str,
help="FRED Series from https://fred.stlouisfed.org. For multiple series use: series1,series2,series3",
)
if other_args and "-" not in other_args[0]:
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-i")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
# Loop through entries. If it exists, save title in dictionary
for s_id in ns_parser.series_id.split(","):
exists, information = fred_model.check_series_id(s_id)
if exists:
self.current_series[s_id] = {
"title": information["seriess"][0]["title"],
"units": information["seriess"][0]["units_short"],
}
self.current_long_id = max(self.current_long_id, len(s_id))
# Loop through entries. If it exists, save title in dictionary
for s_id in ns_parser.series_id.split(","):
exists, information = fred_model.check_series_id(s_id)
if exists:
self.current_series[s_id] = {
"title": information["seriess"][0]["title"],
"units": information["seriess"][0]["units_short"],
}
self.current_long_id = max(self.current_long_id, len(s_id))
print(
f"Current Series: {', '.join(self.current_series.keys()) .upper() or None}\n"
)
print(
f"Current Series: {', '.join(self.current_series.keys()) .upper() or None}\n"
)
return self.queue
@try_except
def call_rmv(self, other_args: List[str]):
@@ -220,17 +284,17 @@ Current Series IDs:
):
other_args.insert(0, "-i")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser.all:
self.current_series = {}
self.current_long_id = 0
print("")
return
self.current_series.pop(ns_parser.series_id)
print(f"Current Series Ids: {', '.join(self.current_series.keys()) or None}\n")
if ns_parser:
if ns_parser.all:
self.current_series = {}
self.current_long_id = 0
print("")
return self.queue
self.current_series.pop(ns_parser.series_id)
print(
f"Current Series Ids: {', '.join(self.current_series.keys()) or None}\n"
)
return self.queue
@try_except
def call_plot(self, other_args):
@@ -258,46 +322,87 @@ Current Series IDs:
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES
)
if not ns_parser:
return
fred_view.display_fred_series(
self.current_series, ns_parser.start_date, ns_parser.raw, ns_parser.export
)
if ns_parser:
fred_view.display_fred_series(
self.current_series,
ns_parser.start_date,
ns_parser.raw,
ns_parser.export,
)
return self.queue
def menu():
def menu(queue: List[str] = None):
"""Fred Menu"""
fred_controller = FredController(queue)
an_input = "HELP_ME"
fred_controller = FredController()
fred_controller.print_help()
# Loop forever and ever
while True:
# There is a command in the queue
if fred_controller.queue and len(fred_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if fred_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(fred_controller.queue) > 1:
return fred_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = fred_controller.queue[0]
fred_controller.queue = fred_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 fred_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /economy/fred/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in fred_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (economy)(fred)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (economy)(fred)> ")
try:
process_input = fred_controller.switch(an_input)
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
fred_controller.print_help()
if process_input is not None:
return process_input
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and fred_controller.completer:
an_input = session.prompt(
f"{get_flair()} /economy/fred/ $ ",
completer=fred_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /economy/fred/ $ ")
try:
# Process the input command
fred_controller.queue = fred_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
similar_cmd = difflib.get_close_matches(
an_input, fred_controller.CHOICES, n=1, cutoff=0.7
print(
f"\nThe command '{an_input}' doesn't exist on the /economy/fred menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
fred_controller.CHOICES,
n=1,
cutoff=0.7,
)
if similar_cmd:
print(f"Did you mean '{similar_cmd[0]}'?\n")
continue
if " " in an_input:
candidate_input = (
f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}"
)
if candidate_input == an_input:
an_input = ""
fred_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
fred_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -5,9 +5,8 @@ import argparse
import difflib
import os
from datetime import datetime
from typing import List
from typing import List, Union
import matplotlib.pyplot as plt
from prompt_toolkit.completion import NestedCompleter
from thepassiveinvestor import create_ETF_report
@@ -19,11 +18,9 @@ from gamestonk_terminal.etf import (
financedatabase_view,
)
from gamestonk_terminal.helper_funcs import (
EXPORT_ONLY_RAW_DATA_ALLOWED,
get_flair,
parse_known_args_and_warn,
MENU_GO_BACK,
MENU_QUIT,
MENU_RESET,
try_except,
system_clear,
)
@@ -35,10 +32,15 @@ class ETFController:
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
@@ -56,22 +58,33 @@ class ETFController:
]
CHOICES += CHOICES_COMMANDS
preset_options = [
file.strip(".ini")
for file in os.listdir(
os.path.join(os.path.abspath(os.path.dirname(__file__)), "presets/")
)
]
def __init__(self):
def __init__(self, queue: List[str] = None):
"""Constructor"""
self.etf_parser = argparse.ArgumentParser(add_help=False, prog="etf")
self.etf_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["screener"]["--preset"] = {c: None for c in self.preset_options}
self.completer = NestedCompleter.from_nested_dict(choices)
if queue:
self.queue = queue
else:
self.queue = list()
def print_help(self):
"""Print help"""
help_str = """
What do you want to do?
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon the program
reset reset terminal and reload configs
StockAnalysis.com:
search search ETFs matching name (i.e. BlackRock or Invesco)
overview get overview of ETF symbol
@@ -94,28 +107,40 @@ Finance Database:
Returns
-------
MENU_GO_BACK, MENU_QUIT, MENU_RESET
MENU_GO_BACK - Show main context menu again
MENU_QUIT - Quit terminal
MENU_RESET - Reset terminal and go back to same previous menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.etf_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -123,21 +148,47 @@ Finance Database:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return MENU_GO_BACK
def call_home(self, _):
"""Process home command"""
print("")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - exit the program"""
return MENU_QUIT
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit"]
def call_reset(self, _):
"""Process Reset command - reset the program"""
return MENU_RESET
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "etf")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "reset", "etf"]
@try_except
def call_search(self, other_args: List[str]):
@@ -157,29 +208,23 @@ Finance Database:
help="String to search for",
required="-h" not in other_args,
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
dest="export",
help="Export dataframe data to csv,json,xlsx file",
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 other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-e")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
search_string = " ".join(ns_parser.search_str)
stockanalysis_view.view_search(to_match=search_string, export=ns_parser.export)
if ns_parser:
search_string = " ".join(ns_parser.search_str)
stockanalysis_view.view_search(
to_match=search_string, export=ns_parser.export
)
return self.queue
@try_except
def call_overview(self, other_args: List[str]):
"""Process overview command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
@@ -195,24 +240,18 @@ Finance Database:
required="-h" not in other_args,
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
dest="export",
help="Export dataframe data to csv,json,xlsx file",
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 other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-e")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
stockanalysis_view.view_overview(symbol=ns_parser.name, export=ns_parser.export)
if ns_parser:
stockanalysis_view.view_overview(
symbol=ns_parser.name, export=ns_parser.export
)
return self.queue
@try_except
def call_holdings(self, other_args: List[str]):
@@ -239,27 +278,19 @@ Finance Database:
help="Number of holdings to get",
default=20,
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-e")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
stockanalysis_view.view_holdings(
symbol=ns_parser.name,
num_to_show=ns_parser.limit,
export=ns_parser.export,
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_holdings(
symbol=ns_parser.name,
num_to_show=ns_parser.limit,
export=ns_parser.export,
)
return self.queue
@try_except
def call_compare(self, other_args):
@@ -278,24 +309,17 @@ Finance Database:
help="Symbols to compare",
required="-h" not in other_args,
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
dest="export",
help="Export dataframe data to csv,json,xlsx file",
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 other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-e")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
etf_list = ns_parser.names.upper().split(",")
stockanalysis_view.view_comparisons(etf_list, export=ns_parser.export)
if ns_parser:
etf_list = ns_parser.names.upper().split(",")
stockanalysis_view.view_comparisons(etf_list, export=ns_parser.export)
return self.queue
@try_except
def call_screener(self, other_args):
@@ -309,56 +333,86 @@ Finance Database:
description="Screens ETFS from a personal scraping github repository. Data scraped from stockanalysis.com",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
type=int,
help="Number of etfs to show",
dest="num",
dest="limit",
default=20,
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
parser.add_argument(
"--preset",
choices=[
file.strip(".ini")
for file in os.listdir(
os.path.join(os.path.abspath(os.path.dirname(__file__)), "presets/")
)
],
choices=self.preset_options,
default="etf_config",
help="Preset to use",
dest="preset",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
screener_view.view_screener(
num_to_show=ns_parser.num,
preset=ns_parser.preset,
export=ns_parser.export,
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,
)
return self.queue
@try_except
def call_gainers(self, other_args):
"""Process gainers command"""
wsj_view.show_top_mover("gainers", other_args)
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)
return self.queue
def call_decliners(self, other_args):
"""Process decliners command"""
wsj_view.show_top_mover("decliners", other_args)
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)
return self.queue
def call_active(self, other_args):
"""Process gainers command"""
wsj_view.show_top_mover("active", other_args)
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)
return self.queue
@try_except
def call_pir(self, other_args):
@@ -392,21 +446,19 @@ Finance Database:
help="Folder where the ETF report will be saved",
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-e")
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 not ns_parser:
return
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"
)
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"
)
return self.queue
@try_except
def call_fds(self, other_args):
@@ -416,7 +468,6 @@ Finance Database:
"assets. Returns the top ETFs when no argument is given. [Source: Finance Database]",
add_help=False,
)
parser.add_argument(
"-c",
"--category",
@@ -425,7 +476,6 @@ Finance Database:
dest="category",
help="Specify the ETF selection based on a category",
)
parser.add_argument(
"-n",
"--name",
@@ -434,7 +484,6 @@ Finance Database:
dest="name",
help="Specify the ETF selection based on the name",
)
parser.add_argument(
"-d",
"--description",
@@ -443,7 +492,6 @@ Finance Database:
dest="description",
help="Specify the ETF selection based on the description (not shown in table)",
)
parser.add_argument(
"-ie",
"--include_exchanges",
@@ -451,16 +499,14 @@ Finance Database:
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(
"-a",
"--amount",
"-l",
"--limit",
default=10,
type=int,
dest="amount",
dest="limit",
help="Enter the number of ETFs you wish to see in the Tabulate window",
)
parser.add_argument(
"-o",
"--options",
@@ -469,48 +515,85 @@ Finance Database:
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
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.amount,
options=ns_parser.options,
)
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,
)
return self.queue
def menu():
etf_controller = ETFController()
etf_controller.print_help()
plt.close("all")
def menu(queue: List[str] = None):
etf_controller = ETFController(queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if etf_controller.queue and len(etf_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if etf_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(etf_controller.queue) > 1:
return etf_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = etf_controller.queue[0]
etf_controller.queue = etf_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 etf_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /etf/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in etf_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (etf)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (etf)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
etf_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and etf_controller.completer:
an_input = session.prompt(
f"{get_flair()} /etf/ $ ",
completer=etf_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /etf/ $ ")
try:
process_input = etf_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
etf_controller.queue = etf_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
print(f"\nThe command '{an_input}' doesn't exist on the /etf menu.", end="")
similar_cmd = difflib.get_close_matches(
an_input, etf_controller.CHOICES, n=1, cutoff=0.7
an_input.split(" ")[0] if " " in an_input else an_input,
etf_controller.CHOICES,
n=1,
cutoff=0.7,
)
if similar_cmd:
print(f"Did you mean '{similar_cmd[0]}'?\n")
continue
if " " in an_input:
candidate_input = (
f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}"
)
if candidate_input == an_input:
an_input = ""
etf_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
etf_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -1,55 +1,32 @@
"""WSJ view"""
__docformat__ = "numpy"
import argparse
import os
from typing import List
from tabulate import tabulate
from gamestonk_terminal.etf import wsj_model
from gamestonk_terminal.helper_funcs import (
export_data,
parse_known_args_and_warn,
try_except,
)
@try_except
def show_top_mover(sort_type: str, other_args: List[str]):
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
other_args: List[str]
Argparse arguments
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
"""
parser = argparse.ArgumentParser(
prog=sort_type,
description="Displays top ETF/Mutual fund movers from wsj.com/market-data",
add_help=False,
)
parser.add_argument("-n", help="Number to show", type=int, default=25, dest="num")
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
data = wsj_model.etf_movers(sort_type)
print(
tabulate(
data.iloc[: ns_parser.num],
data.iloc[:limit],
showindex=False,
headers=data.columns,
floatfmt=".2f",
@@ -57,7 +34,7 @@ def show_top_mover(sort_type: str, other_args: List[str]):
)
)
export_data(
ns_parser.export,
export,
os.path.join(os.path.dirname(os.path.abspath(__file__)), "movers"),
sort_type,
data,

View File

@@ -1,5 +0,0 @@
# flake8: noqa
from . import (
behavioural_analysis,
exploratory_data_analysis,
)

View File

@@ -1,15 +1,24 @@
"""AlphaVantage FX Model"""
"""AlphaVantage FX Model."""
import argparse
from typing import Dict
from typing import Dict, List
import os
import pandas as pd
import requests
from gamestonk_terminal import config_terminal as cfg
def get_currency_list() -> List:
"""Load AV currency codes from a local file."""
path = os.path.join(os.path.dirname(__file__), "av_forex_currencies.csv")
return list(pd.read_csv(path)["currency code"])
CURRENCY_LIST = get_currency_list()
def check_valid_forex_currency(fx_symbol: str) -> str:
"""Check if given symbol is supported on alphavantage
"""Check if given symbol is supported on alphavantage.
Parameters
----------
@@ -26,8 +35,7 @@ def check_valid_forex_currency(fx_symbol: str) -> str:
argparse.ArgumentTypeError
Symbol not valid on alphavantage
"""
path = os.path.join(os.path.dirname(__file__), "av_forex_currencies.csv")
if fx_symbol.upper() in list(pd.read_csv(path)["currency code"]):
if fx_symbol.upper() in CURRENCY_LIST:
return fx_symbol.upper()
raise argparse.ArgumentTypeError(
@@ -36,7 +44,7 @@ def check_valid_forex_currency(fx_symbol: str) -> str:
def get_quote(to_symbol: str, from_symbol: str) -> Dict:
"""Get current exchange rate quote from alpha vantage
"""Get current exchange rate quote from alpha vantage.
Parameters
----------
@@ -50,8 +58,12 @@ def get_quote(to_symbol: str, from_symbol: str) -> Dict:
Dict
Dictionary of exchange rate
"""
url = "https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RAT"
url += f"E&from_currency={from_symbol}&to_currency={to_symbol}&apikey={cfg.API_KEY_ALPHAVANTAGE}"
url = (
"https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RATE"
+ f"&from_currency={from_symbol}"
+ f"&to_currency={to_symbol}"
+ f"&apikey={cfg.API_KEY_ALPHAVANTAGE}"
)
r = requests.get(url)
if r.status_code != 200:
return {}
@@ -65,7 +77,7 @@ def get_historical(
interval: int = 5,
start_date: str = "",
) -> pd.DataFrame:
"""Get historical forex data
"""Get historical forex data.
Parameters
----------

View File

@@ -1,4 +1,4 @@
"""AlphaVantage Forex View"""
"""AlphaVantage Forex View."""
__docformat__ = "numpy"
import pandas as pd
@@ -11,7 +11,7 @@ from gamestonk_terminal.helper_funcs import plot_autoscale
def display_quote(to_symbol: str, from_symbol: str):
"""Display current forex pair exchange rate
"""Display current forex pair exchange rate.
Parameters
----------
@@ -37,7 +37,7 @@ def display_quote(to_symbol: str, from_symbol: str):
def display_candle(data: pd.DataFrame, to_symbol: str, from_symbol: str):
"""Show candle plot for fx data
"""Show candle plot for fx data.
Parameters
----------
@@ -58,6 +58,10 @@ def display_candle(data: pd.DataFrame, to_symbol: str, from_symbol: str):
)
s = mpf.make_mpf_style(marketcolors=mc, gridstyle=":", y_on_right=True)
if gtff.USE_ION:
plt.ion()
mpf.plot(
data,
type="candle",
@@ -70,11 +74,9 @@ def display_candle(data: pd.DataFrame, to_symbol: str, from_symbol: str):
figscale=1.10,
figsize=(plot_autoscale()),
update_width_config=dict(
candle_linewidth=1.0,
candle_linewidth=0.7,
candle_width=0.8,
),
)
if gtff.USE_ION:
plt.ion()
plt.show()
print("")

File diff suppressed because it is too large Load Diff

View File

@@ -1,202 +0,0 @@
"""Exploratory Data Analysis Controller Module"""
__docformat__ = "numpy"
import argparse
from typing import List
from datetime import datetime
import pandas as pd
from matplotlib import pyplot as plt
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal.common.exploratory_data_analysis import eda_api
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import get_flair, system_clear
from gamestonk_terminal.menu import session
class EdaController:
"""EDA Controller class"""
# Command choices
CHOICES = [
"?",
"cls",
"help",
"q",
"quit",
"summary",
"hist",
"cdf",
"bwy",
"bwm",
"rolling",
"decompose",
"cusum",
"acf",
]
def __init__(
self,
ticker: str,
start: datetime,
interval: str,
stock: pd.DataFrame,
):
"""Constructor"""
self.stock = stock
self.ticker = ticker
self.start = start
self.interval = interval
self.stats_parser = argparse.ArgumentParser(add_help=False, prog="stats")
self.stats_parser.add_argument(
"cmd",
choices=self.CHOICES,
)
def print_help(self):
"""Print help"""
print(
"https://github.com/GamestonkTerminal/"
"GamestonkTerminal/tree/main/gamestonk_terminal/exploratory_data_analysis"
)
s_intraday = (f"Intraday {self.interval}", "Daily")[self.interval == "1440min"]
if self.start:
print(
f"\n{s_intraday} Stock: {self.ticker} (from {self.start.strftime('%Y-%m-%d')})"
)
else:
print(f"\n{s_intraday} Stock: {self.ticker}")
print("\nExploratory Data Analysis:")
print(" cls clear screen")
print(" ?/help show this menu again")
print(" q quit this menu, and shows back to main menu")
print(" quit quit to abandon program")
print("")
print(" summary brief summary statistics")
print(" hist histogram with density plot")
print(" cdf cumulative distribution function")
print(" bwy box and whisker yearly plot")
print(" bwm box and whisker monthly plot")
print(" rolling rolling mean and std deviation")
print(" decompose decomposition in cyclic-trend, season, and residuals")
print(" cusum detects abrupt changes using cumulative sum algorithm")
print(" acf (partial) auto-correlation function differentials")
print("")
return
def switch(self, an_input: str):
"""Process and dispatch input
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
"""
# Empty command
if not an_input:
print("")
return None
(known_args, other_args) = self.stats_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
return getattr(
self,
"call_" + known_args.cmd,
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_quit(self, _):
"""Process Quit command - quit the program"""
return True
def call_summary(self, other_args: List[str]):
"""Process summary command"""
eda_api.summary(other_args, self.stock)
def call_hist(self, other_args: List[str]):
"""Process hist command"""
eda_api.hist(other_args, self.ticker, self.stock, self.start)
def call_cdf(self, other_args: List[str]):
"""Process cdf command"""
eda_api.cumulative_distribution_function(
other_args, self.ticker, self.stock, self.start
)
def call_bwy(self, other_args: List[str]):
"""Process bwy command"""
eda_api.bwy(other_args, self.ticker, self.stock, self.start)
def call_bwm(self, other_args: List[str]):
"""Process bwm command"""
eda_api.bwm(other_args, self.ticker, self.stock, self.start)
def call_rolling(self, other_args: List[str]):
"""Process rolling command"""
eda_api.rolling(other_args, self.ticker, self.stock)
def call_decompose(self, other_args: List[str]):
"""Process decompose command"""
eda_api.decompose(other_args, self.ticker, self.stock)
def call_cusum(self, other_args: List[str]):
"""Process cusum command"""
eda_api.cusum(other_args, self.stock)
def call_acf(self, other_args: List[str]):
"""Process acf command"""
eda_api.acf(other_args, self.ticker, self.stock, self.start)
def menu(ticker: str, start: datetime, interval: str, stock: pd.DataFrame):
"""Statistics Menu"""
eda_controller = EdaController(ticker, start, interval, stock)
eda_controller.call_help(None)
while True:
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in eda_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (forex)>(eda)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (forex)>(eda)> ")
try:
plt.close("all")
process_input = eda_controller.switch(an_input)
if process_input is not None:
return process_input
except SystemExit:
print("The command selected doesn't exist\n")
continue

View File

@@ -1,74 +1,87 @@
"""Forex Controller"""
"""Forex Controller."""
__docformat__ = "numpy"
import argparse
import difflib
from datetime import timedelta, datetime
from typing import List
from typing import List, Union
import pandas as pd
from prompt_toolkit.completion import NestedCompleter
from colorama import Style
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.forex.oanda import oanda_controller
from gamestonk_terminal.forex import av_view, av_model
# from gamestonk_terminal.forex.exploratory_data_analysis import eda_controller
from gamestonk_terminal.helper_funcs import (
get_flair,
system_clear,
MENU_GO_BACK,
MENU_QUIT,
MENU_RESET,
parse_known_args_and_warn,
try_except,
valid_date,
try_except,
system_clear,
)
from gamestonk_terminal.menu import session
# pylint: disable=R1710,import-outside-toplevel
class ForexController:
"""Forex Controller class"""
"""Forex Controller class."""
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = ["select", "load", "quote", "candle"]
CHOICES_COMMANDS = ["to", "from", "load", "quote", "candle"]
CHOICES_MENUS = ["oanda"]
CHOICES += CHOICES_COMMANDS
CHOICES += CHOICES_MENUS
def __init__(self):
"""Construct Data"""
def __init__(self, queue: List[str] = None):
"""Construct Data."""
self.fx_parser = argparse.ArgumentParser(add_help=False, prog="forex")
self.fx_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["to"] = {c: None for c in av_model.CURRENCY_LIST}
choices["from"] = {c: None for c in av_model.CURRENCY_LIST}
self.completer = NestedCompleter.from_nested_dict(choices)
self.from_symbol = "USD"
self.to_symbol = ""
self.data = pd.DataFrame()
def print_help(self):
"""Print help"""
dim_bool = self.from_symbol and self.to_symbol
help_str = f"""
What would you like to do?
cls clear screen
?/help show this menu again
q quit this menu and goes back to main menu
quit quit to abandon program
reset reset terminal and reload configs
if queue:
self.queue = queue
else:
self.queue = list()
select select fx pair
def print_help(self):
"""Print help."""
dim_bool = self.from_symbol and self.to_symbol
help_text = f"""
from select the "from" currency in a forex pair
to select the "to" currency in a forex pair
From: {None or self.from_symbol}
To: {None or self.to_symbol}
@@ -78,38 +91,50 @@ AlphaVantage:
load get historical data
candle show candle plot for loaded data
{Style.RESET_ALL}
Brokerages:
> oanda access oanda menu
Forex brokerages:
> oanda Oanda menu
"""
print(help_str)
print(help_text)
def switch(self, an_input: str):
"""Process and dispatch input
"""Process and dispatch input.
Returns
-------
MENU_GO_BACK, MENU_QUIT, MENU_RESET
MENU_GO_BACK - Show main context menu again
MENU_QUIT - Quit terminal
MENU_RESET - Reset terminal and go back to same previous menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.fx_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -117,67 +142,117 @@ Brokerages:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help Command"""
self.print_help()
def call_cls(self, _):
"""Process cls command."""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return MENU_GO_BACK
def call_home(self, _):
"""Process home command."""
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command."""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - exit the program"""
return MENU_QUIT
"""Process quit menu command."""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command."""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit"]
def call_reset(self, _):
"""Process Reset command - reset the program"""
return MENU_RESET
"""Process reset command."""
if len(self.queue) > 0:
self.queue.insert(0, "forex")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "reset", "forex"]
# COMMANDS
@try_except
def call_select(self, other_args: List[str]):
"""Process select command"""
def call_to(self, other_args: List[str]):
"""Process 'to' command."""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="select",
description="Select Forex pair in the form of TO -f FROM",
prog="to",
description='Select the "to" currency symbol in a forex pair',
)
parser.add_argument(
"-t",
"--to",
"-n",
"--name",
help="To currency",
type=av_model.check_valid_forex_currency,
dest="to_symbol",
)
if (
other_args
and "-n" not in other_args[0]
and "--name" not in other_args[0]
and "-h" not in other_args
):
other_args.insert(0, "-n")
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
self.to_symbol = ns_parser.to_symbol
print(
f"\nSelected pair\nFrom: {self.from_symbol}\nTo: {self.to_symbol}\n\n"
)
return self.queue
@try_except
def call_from(self, other_args: List[str]):
"""Process 'from' command."""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="to",
description='Select the "from" currency symbol in a forex pair',
)
parser.add_argument(
"-f",
"--from",
"-n",
"--name",
help="From currency",
type=av_model.check_valid_forex_currency,
dest="from_symbol",
default=None,
)
if (
other_args
and "-f" not in other_args[0]
and "--from" not in other_args[0]
and "-t" not in other_args
and "-n" not in other_args[0]
and "--name" not in other_args[0]
and "-h" not in other_args
):
other_args.insert(0, "-t")
other_args.insert(0, "-n")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
self.to_symbol = ns_parser.to_symbol
if ns_parser.from_symbol:
if ns_parser:
self.from_symbol = ns_parser.from_symbol
print("")
print(
f"\nSelected pair\nFrom: {self.from_symbol}\nTo: {self.to_symbol}\n\n"
)
return self.queue
@try_except
def call_load(self, other_args: List[str]):
"""Process select command"""
"""Process select command."""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
@@ -208,29 +283,29 @@ Brokerages:
help="Start date of data.",
dest="start_date",
)
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)
if not ns_parser:
return
if ns_parser:
if self.to_symbol and self.from_symbol:
self.data = av_model.get_historical(
to_symbol=self.to_symbol,
from_symbol=self.from_symbol,
resolution=ns_parser.resolution,
interval=ns_parser.interval,
start_date=ns_parser.start_date.strftime("%Y-%m-%d"),
)
print(
f"Loaded historic data from {self.from_symbol} to {self.to_symbol}"
)
else:
print("\nMake sure both a to symbol and a from symbol are supplied\n")
if not self.to_symbol or not self.from_symbol:
print(
"Make sure both a to symbol and a from symbol are supplied using <select> \n"
)
return
self.data = av_model.get_historical(
to_symbol=self.to_symbol,
from_symbol=self.from_symbol,
resolution=ns_parser.resolution,
interval=ns_parser.interval,
start_date=ns_parser.start_date.strftime("%Y-%m-%d"),
)
print("")
return self.queue
@try_except
def call_candle(self, other_args: List[str]):
"""Process quote command"""
"""Process quote command."""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
@@ -238,95 +313,117 @@ Brokerages:
description="Show candle for loaded fx data",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if self.data.empty:
print("No forex historical data loaded. Load first using <load>.")
return
av_view.display_candle(self.data, self.to_symbol, self.from_symbol)
if ns_parser:
if not self.data.empty:
av_view.display_candle(self.data, self.to_symbol, self.from_symbol)
else:
print("No forex historical data loaded. Load first using <load>.")
return self.queue
@try_except
def call_quote(self, other_args: List[str]):
"""Process quote command"""
"""Process quote command."""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="quote",
description="Get current exchange rate quote",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
if self.to_symbol and self.from_symbol:
av_view.display_quote(self.to_symbol, self.from_symbol)
else:
print('Make sure both a "to" symbol and a "from" symbol are selected\n')
if not self.to_symbol or not self.from_symbol:
print(
"Make sure both a to symbol and a from symbol are supplied using <select> \n"
)
return
return self.queue
av_view.display_quote(self.to_symbol, self.from_symbol)
# pylint: disable=inconsistent-return-statements
# MENUS
def call_oanda(self, _):
ret = oanda_controller.menu()
if ret:
return True
self.print_help()
"""Enter Oanda menu."""
from gamestonk_terminal.forex.oanda import oanda_controller
return oanda_controller.menu(self.queue)
# HELP WANTED!
# TODO: Add news and reddit commands back
# def call_eda(self, _):
# try:
# df = fx_view.get_candles_dataframe(account, self.instrument, None)
# df = df.rename(columns={"Close": "Adj Close"})
# instrument = self.instrument
# s_start = pd.to_datetime(df.index.values[0])
# s_interval = "1440min"
# eda_controller.menu(df, instrument, s_start, s_interval)
# except AttributeError:
# print("No data found, do you have your oanda API keys set?")
# def call_ba(self, _):
# instrument = fx_view.format_instrument(self.instrument, " ")
# try:
# df = fx_view.get_candles_dataframe(account, self.instrument, None)
# s_start = pd.to_datetime(df.index.values[0])
# ba_controller.menu(instrument, s_start)
# except AttributeError:
# print("No data found, do you have your oanda API keys set?")
# behavioural analysis and exploratory data analysis would be useful in the
# forex menu. The examples of integration of the common ba and eda components
# into the stocks context can provide an insight on how this can be done.
# The earlier implementation did not work and was deleted in commit
# d0e51033f7d5d4da6386b9e0b787892979924dce
def menu():
"""Forex Menu"""
fx_controller = ForexController()
fx_controller.call_help(None)
def menu(queue: List[str] = None):
"""Forex Menu."""
forex_controller = ForexController(queue)
an_input = "HELP_ME"
while True:
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in fx_controller.CHOICES}
)
# There is a command in the queue
if forex_controller.queue and len(forex_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if forex_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(forex_controller.queue) > 1:
return forex_controller.queue[1:]
return []
an_input = session.prompt(
f"{get_flair()} (forex)> ",
completer=completer,
)
# Consume 1 element from the queue
an_input = forex_controller.queue[0]
forex_controller.queue = forex_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 forex_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /forex/ $ {an_input}")
# Get input command from user
else:
an_input = input(f"{get_flair()} (forex)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
forex_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and forex_controller.completer:
an_input = session.prompt(
f"{get_flair()} /forex/ $ ",
completer=forex_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /forex/ $ ")
try:
process_input = fx_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
forex_controller.queue = forex_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exit\n")
similar_cmd = difflib.get_close_matches(
an_input, fx_controller.CHOICES, n=1, cutoff=0.7
print(
f"\nThe command '{an_input}' doesn't exist on the /forex menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
forex_controller.CHOICES,
n=1,
cutoff=0.7,
)
if similar_cmd:
print(f"Did you mean '{similar_cmd[0]}'?\n")
continue
if " " in an_input:
candidate_input = (
f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}"
)
if candidate_input == an_input:
an_input = ""
forex_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
forex_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -1,911 +0,0 @@
import argparse
from datetime import datetime
from typing import List
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import mplfinance as mpf
import pandas as pd
import pandas_ta as ta
import seaborn as sns
from oandapyV20 import API
from oandapyV20.endpoints import (
accounts,
forexlabs,
instruments,
orders,
positions,
pricing,
trades,
)
from oandapyV20.exceptions import V20Error
from gamestonk_terminal import config_plot as cfgPlot
from gamestonk_terminal import config_terminal as cfg
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import (
check_non_negative,
parse_known_args_and_warn,
plot_autoscale,
try_except,
)
client = API(access_token=cfg.OANDA_TOKEN, environment=cfg.OANDA_ACCOUNT_TYPE)
account = cfg.OANDA_ACCOUNT
def get_fx_price(accountID, instrument, other_args: List[str]):
parser = argparse.ArgumentParser(
add_help=False,
prog="price",
description="Get price for selected instrument.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
try:
parameters = {"instruments": instrument}
request = pricing.PricingInfo(accountID=accountID, params=parameters)
response = client.request(request)
bid = response["prices"][0]["bids"][0]["price"]
ask = response["prices"][0]["asks"][0]["price"]
print(instrument + " Bid: " + bid)
print(instrument + " Ask: " + ask)
print("")
except V20Error as e:
# pylint: disable=W0123
d_error = eval(e.msg)
print(d_error["errorMessage"], "\n")
def get_account_summary(accountID, other_args: List[str]):
parser = argparse.ArgumentParser(
add_help=False,
prog="summary",
description="Print some information about your account.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
try:
request = accounts.AccountSummary(accountID=accountID)
response = client.request(request)
df_summary = pd.DataFrame(
[
{"Type": "Balance", "Value": response["account"]["balance"]},
{"Type": "NAV", "Value": response["account"]["NAV"]},
{
"Type": "Unrealized P/L",
"Value": response["account"]["unrealizedPL"],
},
{"Type": "Total P/L", "Value": response["account"]["pl"]},
{
"Type": "Open Trade Count",
"Value": response["account"]["openTradeCount"],
},
{
"Type": "Margin Available",
"Value": response["account"]["marginAvailable"],
},
{"Type": "Margin Used", "Value": response["account"]["marginUsed"]},
{
"Type": "Margin Closeout",
"Value": response["account"]["marginCloseoutNAV"],
},
{
"Type": "Margin Closeout Percent",
"Value": response["account"]["marginCloseoutPercent"],
},
{
"Type": "Margin Closeout Position Value",
"Value": response["account"]["marginCloseoutPositionValue"],
},
]
)
print(df_summary.to_string(index=False, header=False))
print("")
except V20Error as e:
# pylint: disable=W0123
d_error = eval(e.msg)
print(d_error["errorMessage"], "\n")
def list_orders(accountID, other_args: List[str]):
parser = argparse.ArgumentParser(
add_help=False,
prog="list",
description="List order history",
)
parser.add_argument(
"-s",
"--state",
dest="state",
action="store",
default="ALL",
required=False,
help="Select state for order list. ",
)
parser.add_argument(
"-c",
"--count",
dest="count",
action="store",
default=20,
required=False,
help="the number of orders to retrieve ",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
parameters = {}
parameters["state"] = ns_parser.state.upper()
parameters["count"] = ns_parser.count
try:
request = orders.OrderList(accountID, parameters)
response = client.request(request)
df = pd.DataFrame.from_dict(response["orders"])
df = df[["id", "instrument", "units", "price", "state", "type"]]
print(df)
print("")
except KeyError:
print("No orders were found\n")
except V20Error as e:
# pylint: disable=W0123
d_error = eval(e.msg)
print(d_error["errorMessage"], "\n")
def get_order_book(instrument, other_args):
parser = argparse.ArgumentParser(
add_help=False,
prog="orderbook",
description="Plot the orderbook for the instrument if Oanda provides one.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
parameters = {"bucketWidth": "1"}
try:
instrument = format_instrument(instrument, "_")
request = instruments.InstrumentsOrderBook(
instrument=instrument, params=parameters
)
response = client.request(request)
df = pd.DataFrame.from_dict(response["orderBook"]["buckets"])
pd.set_option("display.max_rows", None)
df = df.take(range(527, 727, 1))
book_plot(df, instrument, "Order Book")
print("")
except V20Error as e:
# pylint: disable=W0123
d_error = eval(e.msg)
print(d_error["errorMessage"], "\n")
def get_position_book(instrument, other_args):
parser = argparse.ArgumentParser(
add_help=False,
prog="positionbook",
description="Plot the positionbook for the instrument if Oanda provides one.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
try:
instrument = format_instrument(instrument, "_")
request = instruments.InstrumentsPositionBook(instrument=instrument)
response = client.request(request)
df = pd.DataFrame.from_dict(response["positionBook"]["buckets"])
pd.set_option("display.max_rows", None)
df = df.take(range(219, 415, 1))
book_plot(df, instrument, "Position Book")
print("")
except V20Error as e:
# pylint: disable=W0123
d_error = eval(e.msg)
print(d_error["errorMessage"], "\n")
def create_order(accountID, instrument, other_args: List[str]):
parser = argparse.ArgumentParser(
add_help=False,
prog="order",
description="Create order",
)
parser.add_argument(
"-u",
"--unit",
dest="units",
action="store",
type=int,
default=0,
required=True,
help="The number of units to place in the order request. Positive for "
+ "a long position and negative for a short position ",
)
parser.add_argument(
"-p",
"--price",
dest="price",
action="store",
type=check_non_negative,
required=True,
help="The price to set for the limit order. ",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if "JPY" in instrument or "THB" in instrument or "HUF" in instrument:
ns_parser.price = round(ns_parser.price, 3)
else:
ns_parser.price = round(ns_parser.price, 5)
data = {
"order": {
"price": ns_parser.price,
"instrument": instrument,
"units": ns_parser.units,
"type": "LIMIT",
"timeInForce": "GTC",
"positionFill": "DEFAULT",
}
}
try:
request = orders.OrderCreate(accountID, data)
response = client.request(request)
order_data = []
order_data.append(
{
"Order ID": response["orderCreateTransaction"]["id"],
"Instrument": response["orderCreateTransaction"]["instrument"],
"Price": response["orderCreateTransaction"]["price"],
"Units": response["orderCreateTransaction"]["units"],
}
)
df = pd.DataFrame.from_dict(order_data)
print(df.to_string(index=False))
print("")
except V20Error as e:
# pylint: disable=W0123
d_error = eval(e.msg)
print(d_error["errorMessage"], "\n")
except Exception as e:
print(e)
def cancel_pending_order(accountID, other_args: List[str]):
parser = argparse.ArgumentParser(
add_help=False,
prog="cancel",
description="Cancel Pending Order ",
)
parser.add_argument(
"-i",
"--id",
dest="orderID",
action="store",
type=str,
help="The pending order ID to cancel ",
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-i")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
try:
request = orders.OrderCancel(accountID, ns_parser.orderID)
response = client.request(request)
order_id = response["orderCancelTransaction"]["orderID"]
print(f"Order {order_id} canceled.")
print("")
except V20Error as e:
# pylint: disable=W0123
d_error = eval(e.msg)
print(d_error["errorMessage"], "\n")
def get_pending_orders(accountID, other_args):
parser = argparse.ArgumentParser(
add_help=False,
prog="pending",
description="Gets information about pending orders.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
try:
request = orders.OrdersPending(accountID)
response = client.request(request)
pending_data = []
for i in range(len(response["orders"])):
pending_data.append(
{
"Order ID": response["orders"][i]["id"],
"Instrument": response["orders"][i]["instrument"],
"Price": response["orders"][i]["price"],
"Units": response["orders"][i]["units"],
"Time Created": response["orders"][i]["createTime"][:10]
+ " "
+ response["orders"][i]["createTime"][11:19],
"Time In Force": response["orders"][i]["timeInForce"],
}
)
df = pd.DataFrame.from_dict(pending_data)
if not pending_data:
print("No pending orders")
else:
print(df.to_string(index=False))
print("")
except V20Error as e:
# pylint: disable=W0123
d_error = eval(e.msg)
print(d_error["errorMessage"], "\n")
def get_open_positions(accountID, other_args):
parser = argparse.ArgumentParser(
add_help=False,
prog="positions",
description="Gets information about open positions.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
try:
request = positions.OpenPositions(accountID)
response = client.request(request)
position_data = []
for i in range(len(response["positions"])):
position_data.append(
{
"Instrument": response["positions"][i]["instrument"],
"Long Units": response["positions"][i]["long"]["units"],
"Total Long P/L": response["positions"][i]["long"]["units"],
"Unrealized Long P/L": response["positions"][i]["long"][
"unrealizedPL"
],
"Short Units": response["positions"][i]["short"]["units"],
"Total Short P/L": response["positions"][i]["short"]["pl"],
"Short Unrealized P/L": response["positions"][i]["short"][
"unrealizedPL"
],
}
)
df = pd.DataFrame.from_dict(position_data)
print(df.to_string(index=False))
print("")
except V20Error as e:
# pylint: disable=W0123
d_error = eval(e.msg)
print(d_error["errorMessage"], "\n")
def get_open_trades(accountID, other_args):
parser = argparse.ArgumentParser(
add_help=False,
prog="trades",
description="Gets information about open trades.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
try:
request = trades.OpenTrades(accountID)
response = client.request(request)
try:
df = pd.DataFrame.from_dict(response["trades"])
df = df[
[
"id",
"instrument",
"initialUnits",
"currentUnits",
"price",
"unrealizedPL",
]
]
df = df.rename(
columns={
"id": "ID",
"instrument": "Instrument",
"initialUnits": "Initial Units",
"currentUnits": "Current Units",
"price": "Entry Price",
"unrealizedPL": "Unrealized P/L",
}
)
print(df.to_string(index=False))
print("")
except KeyError:
print("No trades were found")
print("")
except V20Error as e:
msg_length = len(e.msg)
msg = e.msg[17 : msg_length - 2]
print(msg)
print("")
def close_trade(accountID, other_args: List[str]):
parser = argparse.ArgumentParser(
add_help=False,
prog="closetrade",
description="Close a trade by id.",
)
parser.add_argument(
"-i",
"--id",
dest="orderID",
action="store",
type=str,
required=False,
help="The Trade ID to close. ",
)
parser.add_argument(
"-u",
"--units",
dest="units",
action="store",
required=False,
help="The number of units on the trade to close. If not set it "
+ "defaults to all units. ",
)
if other_args:
if "-i" not in other_args[0]:
other_args.insert(0, "-i")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
data = {}
if ns_parser.units is not None:
data["units"] = ns_parser.units
try:
request = trades.TradeClose(accountID, ns_parser.orderID, data)
response = client.request(request)
close_data = []
close_data.append(
{
"OrderID": response["orderCreateTransaction"]["tradeClose"]["tradeID"],
"Instrument": response["orderFillTransaction"]["instrument"],
"Units": response["orderCreateTransaction"]["units"],
"Price": response["orderFillTransaction"]["price"],
"P/L": response["orderFillTransaction"]["pl"],
}
)
df = pd.DataFrame.from_dict(close_data)
print(df.to_string(index=False))
print("")
except V20Error as e:
# pylint: disable=W0123
d_error = eval(e.msg)
print(d_error["errorMessage"], "\n")
except Exception as e:
print(e, "\n")
def show_candles(accountID, instrument, other_args: List[str]):
parser = argparse.ArgumentParser(
add_help=False,
prog="candles",
description="Display Candle Data",
)
parser.add_argument(
"-g",
"--granularity",
dest="granularity",
action="store",
type=str,
default="D",
required=False,
help="The timeframe to get for the candle chart (Seconds: S5, S10, S15, S30 "
+ "Minutes: M1, M2, M4, M5, M10, M15, M30 Hours: H1, H2, H3, H4, H6, H8, H12 "
+ "Day (default): D, Week: W Month: M",
)
parser.add_argument(
"-c",
"--count",
dest="candlecount",
action="store",
default=180,
type=int,
required=False,
help="The number of candles to retrieve. Default:180 ",
)
parser.add_argument(
"-a",
"--ad",
dest="ad",
action="store_true",
help="Adds ad (Accumulation/Distribution Indicator) to the chart",
)
parser.add_argument(
"-b",
"--bbands",
dest="bbands",
action="store_true",
help="Adds Bollinger Bands to the chart",
)
parser.add_argument(
"-C",
"--cci",
dest="cci",
action="store_true",
help="Adds cci (Commodity Channel Index) to the chart",
)
parser.add_argument(
"-e",
"--ema",
dest="ema",
action="store_true",
help="Adds ema (Exponential Moving Average) to the chart",
)
parser.add_argument(
"-o",
"--obv",
dest="obv",
action="store_true",
help="Adds obv (On Balance Volume) to the chart",
)
parser.add_argument(
"-r",
"--rsi",
dest="rsi",
action="store_true",
help="Adds rsi (Relative Strength Index) to the chart",
)
parser.add_argument(
"-s",
"--sma",
dest="sma",
action="store_true",
help="Adds sma (Simple Moving Average) to the chart",
)
parser.add_argument(
"-v",
"--vwap",
dest="vwap",
action="store_true",
help="Adds vwap (Volume Weighted Average Price) to the chart",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
parameters = {}
parameters["granularity"] = ns_parser.granularity.upper()
parameters["count"] = ns_parser.candlecount
try:
instrument = format_instrument(instrument, "_")
df = get_candles_dataframe(accountID, instrument, parameters)
plots_to_add, legends, subplot_legends = add_plots(df, ns_parser)
if gtff.USE_ION:
plt.ion()
# pylint: disable=W0612
fig, ax = mpf.plot(
df,
type="candle",
style="charles",
volume=True,
title=f"{instrument} {ns_parser.granularity}",
returnfig=True,
addplot=plots_to_add,
)
ax[0].set_title(f"{instrument} {ns_parser.granularity}")
ax[0].legend(legends)
# pylint: disable=C0200
for i in range(0, len(subplot_legends), 2):
ax[subplot_legends[i]].legend(subplot_legends[i + 1])
print("")
except V20Error as e:
# pylint: disable=W0123
d_error = eval(e.msg)
print(d_error["errorMessage"], "\n")
except TypeError as e:
print(e)
def add_plots(df, ns_parser):
panel_number = 2
plots_to_add = []
legends = []
subplot_legends = []
if ns_parser.ad:
ad = ta.ad(df["High"], df["Low"], df["Close"], df["Volume"])
ad_plot = mpf.make_addplot(ad, panel=panel_number)
plots_to_add.append(ad_plot)
subplot_legends.extend([panel_number * 2, ["AD"]])
panel_number += 1
if ns_parser.bbands:
bbands = ta.bbands(df["Close"])
bbands = bbands.drop("BBB_5_2.0", axis=1)
bbands_plot = mpf.make_addplot(bbands, panel=0)
plots_to_add.append(bbands_plot)
legends.extend(["Lower BBand", "Middle BBand", "Upper BBand"])
if ns_parser.cci:
cci = ta.cci(df["High"], df["Low"], df["Close"])
cci_plot = mpf.make_addplot(cci, panel=panel_number)
plots_to_add.append(cci_plot)
subplot_legends.extend([panel_number * 2, ["CCI"]])
panel_number += 1
if ns_parser.ema:
ema = ta.ema(df["Close"])
ema_plot = mpf.make_addplot(ema, panel=0)
plots_to_add.append(ema_plot)
legends.append("10 EMA")
if ns_parser.rsi:
rsi = ta.rsi(df["Close"])
rsi_plot = mpf.make_addplot(rsi, panel=panel_number)
plots_to_add.append(rsi_plot)
subplot_legends.extend([panel_number * 2, ["RSI"]])
panel_number += 1
if ns_parser.obv:
obv = ta.obv(df["Close"], df["Volume"])
obv_plot = mpf.make_addplot(obv, panel=panel_number)
plots_to_add.append(obv_plot)
subplot_legends.extend([panel_number * 2, ["OBV"]])
panel_number += 1
if ns_parser.sma:
sma_length = [20, 50]
for length in sma_length:
sma = ta.sma(df["Close"], length=length)
sma_plot = mpf.make_addplot(sma, panel=0)
plots_to_add.append(sma_plot)
legends.append(f"{length} SMA")
if ns_parser.vwap:
vwap = ta.vwap(df["High"], df["Low"], df["Close"], df["Volume"])
vwap_plot = mpf.make_addplot(vwap, panel=0)
plots_to_add.append(vwap_plot)
legends.append("vwap")
return plots_to_add, legends, subplot_legends
def calendar(instrument, other_args: List[str]):
parser = argparse.ArgumentParser(
add_help=False,
prog="calendar",
description="Show Calendar Data",
)
parser.add_argument(
"-d",
"--days",
dest="days",
action="store",
type=int,
default=7,
required=False,
help="The number of days to search for, up to 30 forward or backward "
+ "use negative numbers to search back. ",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
parameters = {"instrument": instrument, "period": str(ns_parser.days * 86400 * -1)}
try:
request = forexlabs.Calendar(params=parameters)
response = client.request(request)
l_data = []
# pylint: disable=C0200
for i in range(len(response)):
if "forecast" in response[i]:
forecast = response[i]["forecast"]
if response[i]["unit"] != "Index":
forecast += response[i]["unit"]
else:
forecast = ""
if "market" in response[i]:
market = response[i]["market"]
if response[i]["unit"] != "Index":
market += response[i]["unit"]
else:
market = ""
if "actual" in response[i]:
actual = response[i]["actual"]
if response[i]["unit"] != "Index":
actual += response[i]["unit"]
else:
actual = ""
if "previous" in response[i]:
previous = response[i]["previous"]
if response[i]["unit"] != "Index":
previous += response[i]["unit"]
else:
previous = ""
if "impact" in response[i]:
impact = response[i]["impact"]
else:
impact = ""
l_data.append(
{
"Title": response[i]["title"],
"Time": datetime.fromtimestamp(response[i]["timestamp"]),
"Impact": impact,
"Forecast": forecast,
"Market Forecast": market,
"Currency": response[i]["currency"],
"Region": response[i]["region"],
"Actual": actual,
"Previous": previous,
}
)
print(pd.DataFrame(l_data).to_string(index=False))
print("")
except V20Error as e:
# pylint: disable=W0123
d_error = eval(e.msg)
print(d_error["message"], "\n")
# pylint: disable=W0613
# pylint: disable=R1710
def get_candles_dataframe(accountID, instrument, parameters):
if not parameters:
parameters = {
"granularity": "D",
"count": "1500",
}
try:
request = instruments.InstrumentsCandles(instrument, params=parameters)
response = client.request(request)
candles_data = []
for i in range(len(response["candles"])):
candles_data.append(
{
"Date": response["candles"][i]["time"][:10]
+ " "
+ response["candles"][i]["time"][11:19],
"Open": float(response["candles"][i]["mid"]["o"]),
"High": float(response["candles"][i]["mid"]["h"]),
"Low": float(response["candles"][i]["mid"]["l"]),
"Close": float(response["candles"][i]["mid"]["c"]),
"Volume": response["candles"][i]["volume"],
}
)
df = pd.DataFrame(candles_data)
df.set_index("Date", inplace=True)
df.index = pd.to_datetime(df.index)
return df
except V20Error as e:
# pylint: disable=W0123
d_error = eval(e.msg)
print(d_error["errorMessage"], "\n")
# pylint: disable=R1710
@try_except
def load(other_args: List[str]):
"""Load a forex instrument to use"""
parser = argparse.ArgumentParser(
add_help=False,
prog="load",
description="Forex using oanda",
)
parser.add_argument(
"-i",
"--instrument",
required=True,
type=str,
dest="instrument",
help="Forex pair to use. ",
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-i")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
print("")
return ns_parser.instrument.upper()
def book_plot(df, instrument, book_type):
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
df = df.apply(pd.to_numeric)
df["shortCountPercent"] = df["shortCountPercent"] * -1
axis_origin = max(
abs(max(df["longCountPercent"])), abs(max(df["shortCountPercent"]))
)
ax.set_xlim(-axis_origin, +axis_origin)
sns.set_style(style="darkgrid")
sns.barplot(
x="longCountPercent",
y="price",
data=df,
label="Count Percent",
color="green",
orient="h",
)
sns.barplot(
x="shortCountPercent",
y="price",
data=df,
label="Prices",
color="red",
orient="h",
)
ax.invert_yaxis()
plt.title(f"{instrument} {book_type}")
plt.xlabel("Count Percent")
plt.ylabel("Price")
sns.despine(left=True, bottom=True)
ax.yaxis.set_major_locator(mticker.MultipleLocator(5))
if gtff.USE_ION:
plt.ion()
plt.show()
# pylint: disable=R1710
def format_instrument(instrument, char):
try:
if char not in instrument:
instrument_list = list(instrument)
instrument_list.pop(3)
if char == " ":
instrument = "".join(map(str, instrument_list))
else:
instrument_list.insert(3, char)
instrument = "".join(map(str, instrument_list))
return instrument
except TypeError:
print("Please load an instrument")
print("")

View File

@@ -1,21 +1,25 @@
"""Oanda Controller"""
"""Oanda Controller."""
__docformat__ = "numpy"
import argparse
import os
from typing import List
import argparse
import difflib
from typing import List, Union
from colorama import Style
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal import config_terminal as cfg
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.forex import av_model
from gamestonk_terminal.forex.oanda import oanda_view
# from gamestonk_terminal.forex.exploratory_data_analysis import eda_controller
from gamestonk_terminal.helper_funcs import (
get_flair,
MENU_GO_BACK,
MENU_QUIT,
MENU_RESET,
try_except,
parse_known_args_and_warn,
system_clear,
check_non_negative_float,
)
from gamestonk_terminal.menu import session
@@ -24,24 +28,31 @@ account = cfg.OANDA_ACCOUNT
class OandaController:
"""Oanda menu controller."""
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = [
"from",
"to",
"price",
"summary",
"list",
"orderbook",
"positionbook",
"order",
"load",
"cancel",
"positions",
"closetrade",
@@ -54,69 +65,110 @@ class OandaController:
]
CHOICES += CHOICES_COMMANDS
def __init__(self):
"""Construct Data"""
def __init__(self, queue: List[str] = None):
"""Construct Data."""
self.fx_parser = argparse.ArgumentParser(add_help=False, prog="oanda")
self.fx_parser.add_argument(
"cmd",
choices=self.CHOICES,
)
self.instrument = None
self.completer: Union[None, NestedCompleter] = None
if session and gtff.USE_PROMPT_TOOLKIT:
choices: dict = {c: {} for c in self.CHOICES}
# HELP WANTED!
# TODO:
# We currently use the Alpha Vantage currency list for autocompletion
# This leads to messages like `USD_EUR is not a valid instrument.`
# In Oanda they have their own list of available instruments. It would be
# Great to fetch these lists and store them locally like it's done for
# other currency codes (see ./av_forex_currencies.csv and how it's handled).
choices["to"] = {c: None for c in av_model.CURRENCY_LIST}
choices["from"] = {c: None for c in av_model.CURRENCY_LIST}
self.completer = NestedCompleter.from_nested_dict(choices)
self.from_symbol = ""
self.to_symbol = ""
self.instrument: Union[str, None] = None
if queue:
self.queue = queue
else:
self.queue = list()
def print_help(self):
"""Print help"""
print(" ?/help show this menu again")
print(" q quit this menu and goes back to main menu")
print(" quit quit to abandon program")
print(" reset reset terminal and reload configs")
print("")
print(" summary shows account summary")
print(" calendar show calendar")
print(" list list order history")
print(" pending get information on pending orders")
print(" cancel cancel a pending order by ID -i order ID")
print(" positions get open positions")
print(" trades list open trades")
print(" closetrade close a trade by id")
print("")
print(f"Loaded instrument: {self.instrument if self.instrument else ''}")
print("")
print(" load load an instrument to use")
if self.instrument:
print(" candles show candles")
print(" price shows price for selected instrument")
print(" order place limit order -u # of units -p price")
print(" orderbook print orderbook")
print(" positionbook print positionbook")
print("")
"""Print help."""
dim_if_no_ticker = Style.DIM if not self.instrument else ""
reset_style_if_no_ticker = Style.RESET_ALL if not self.instrument else ""
help_text = f"""
summary shows account summary
calendar show calendar
list list order history
pending get information on pending orders
cancel cancel a pending order by ID -i order ID
positions get open positions
trades list open trades
closetrade close a trade by id
Loaded instrument: {self.instrument if self.instrument else ""}
from select the "from" currency in a forex pair
to select the "to" currency in a forex pair
{dim_if_no_ticker}
candles show candles
price shows price for selected instrument
order place limit order -u # of units -p price
orderbook print orderbook
positionbook print positionbook
{reset_style_if_no_ticker}
"""
print(help_text)
def switch(self, an_input: str):
"""Process and dispatch input
"""Process and dispatch input.
Returns
-------
MENU_GO_BACK, MENU_QUIT, MENU_RESET
MENU_GO_BACK - Show main context menu again
MENU_QUIT - Quit terminal
MENU_RESET - Reset terminal and go back to same previous menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.fx_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
os.system("cls||clear")
return None
# 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"
return getattr(
self,
@@ -124,100 +176,541 @@ class OandaController:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help Command"""
self.print_help()
def call_cls(self, _):
"""Process cls command."""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return MENU_GO_BACK
def call_home(self, _):
"""Process home command."""
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command."""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - exit the program"""
return MENU_QUIT
"""Process quit menu command."""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command."""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit"]
def call_reset(self, _):
"""Process Reset command - reset the program"""
return MENU_RESET
"""Process reset command."""
if len(self.queue) > 0:
self.queue.insert(0, "oanda")
self.queue.insert(0, "forex")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "reset", "forex", "oanda"]
# COMMANDS
@try_except
def call_to(self, other_args: List[str]):
"""Process 'to' command."""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="to",
description='Select the "to" currency symbol in a forex pair',
)
parser.add_argument(
"-n",
"--name",
help="To currency",
type=av_model.check_valid_forex_currency,
dest="to_symbol",
)
if (
other_args
and "-n" not in other_args[0]
and "--name" not in other_args[0]
and "-h" not in other_args
):
other_args.insert(0, "-n")
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
self.to_symbol = ns_parser.to_symbol.upper()
self.instrument = f"{self.from_symbol}_{self.to_symbol}"
print(
f"\nSelected pair\nFrom: {self.from_symbol}\nTo: {self.to_symbol}\n\n"
)
return self.queue
@try_except
def call_from(self, other_args: List[str]):
"""Process 'from' command."""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="to",
description='Select the "from" currency symbol in a forex pair',
)
parser.add_argument(
"-n",
"--name",
help="From currency",
type=av_model.check_valid_forex_currency,
dest="from_symbol",
)
if (
other_args
and "-n" not in other_args[0]
and "--name" not in other_args[0]
and "-h" not in other_args
):
other_args.insert(0, "-n")
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
self.from_symbol = ns_parser.from_symbol.upper()
self.instrument = f"{self.from_symbol}_{self.to_symbol}"
print(
f"\nSelected pair\nFrom: {self.from_symbol}\nTo: {self.to_symbol}\n\n"
)
return self.queue
@try_except
def call_price(self, other_args):
"""Process Price Command"""
oanda_view.get_fx_price(account, self.instrument, other_args)
def call_load(self, other_args):
self.instrument = oanda_view.load(other_args)
"""Process Price Command."""
parser = argparse.ArgumentParser(
add_help=False,
prog="price",
description="Get price for selected instrument.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
oanda_view.get_fx_price(account, self.instrument)
return self.queue
@try_except
def call_summary(self, other_args):
"""Process account summary command"""
oanda_view.get_account_summary(account, other_args)
"""Process account summary command."""
parser = argparse.ArgumentParser(
add_help=False,
prog="summary",
description="Print some information about your account.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
oanda_view.get_account_summary(account)
return self.queue
@try_except
def call_orderbook(self, other_args):
"""Process Oanda Order Book"""
oanda_view.get_order_book(self.instrument, other_args)
"""Process Oanda Order Book."""
parser = argparse.ArgumentParser(
add_help=False,
prog="orderbook",
description="Plot an orderbook for an instrument if Oanda provides one.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
oanda_view.get_order_book(account, self.instrument)
return self.queue
@try_except
def call_positionbook(self, other_args):
"""Process Oanda Position Book"""
oanda_view.get_position_book(self.instrument, other_args)
"""Process Oanda Position Book."""
parser = argparse.ArgumentParser(
add_help=False,
prog="positionbook",
description="Plot a position book for an instrument if Oanda provides one.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
oanda_view.get_position_book(account, self.instrument)
return self.queue
@try_except
def call_list(self, other_args: List[str]):
"""Process list orders command"""
oanda_view.list_orders(account, other_args)
"""Process list orders command."""
parser = argparse.ArgumentParser(
add_help=False,
prog="list",
description="List order history",
)
parser.add_argument(
"-s",
"--state",
dest="state",
action="store",
default="ALL",
type=str,
required=False,
help="List orders that have a specific state.",
)
parser.add_argument(
"-l",
"--limit",
dest="limit",
action="store",
default=20,
type=int,
required=False,
help="Limit the number of orders to retrieve.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
order_state = ns_parser.state.upper()
order_count = ns_parser.limit
oanda_view.list_orders(account, order_state, order_count)
return self.queue
@try_except
def call_order(self, other_args: List[str]):
"""Place limit order"""
oanda_view.create_order(account, self.instrument, other_args)
"""Place limit order."""
parser = argparse.ArgumentParser(
add_help=False,
prog="order",
description="Create order",
)
parser.add_argument(
"-u",
"--unit",
dest="units",
action="store",
type=int,
required="-h" not in other_args,
help="The number of units to place in the order request. Positive for "
+ "a long position and negative for a short position.",
)
parser.add_argument(
"-p",
"--price",
dest="price",
action="store",
type=check_non_negative_float,
required="-h" not in other_args,
help="The price to set for the limit order.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
price = ns_parser.price
units = ns_parser.units
oanda_view.create_order(account, self.instrument, price, units)
return self.queue
@try_except
def call_cancel(self, other_args: List[str]):
"""Cancel pending order by ID"""
oanda_view.cancel_pending_order(account, other_args)
"""Cancel pending order by ID."""
parser = argparse.ArgumentParser(
add_help=False,
prog="cancel",
description="Cancel a Pending Order.",
)
parser.add_argument(
"-i",
"--id",
dest="orderID",
action="store",
required="-h" not in other_args,
type=str,
help="The pending order ID to cancel.",
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-i")
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
orderID = ns_parser.orderID
oanda_view.cancel_pending_order(account, orderID)
return self.queue
@try_except
def call_positions(self, other_args):
"""Get Open Positions"""
oanda_view.get_open_positions(account, other_args)
"""Get Open Positions."""
parser = argparse.ArgumentParser(
add_help=False,
prog="positions",
description="Get information about open positions.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
oanda_view.get_open_positions(account)
return self.queue
@try_except
def call_pending(self, other_args):
"""See up to 25 pending orders"""
oanda_view.get_pending_orders(account, other_args)
def call_closetrade(self, other_args: List[str]):
"""Close a trade by id"""
oanda_view.close_trade(account, other_args)
def call_candles(self, other_args: List[str]):
oanda_view.show_candles(account, self.instrument, other_args)
"""See up to 25 pending orders."""
parser = argparse.ArgumentParser(
add_help=False,
prog="pending",
description="Get information about pending orders.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
oanda_view.get_pending_orders(account)
return self.queue
@try_except
def call_trades(self, other_args):
"""List open trades"""
oanda_view.get_open_trades(account, other_args)
"""List open trades."""
parser = argparse.ArgumentParser(
add_help=False,
prog="trades",
description="Get information about open trades.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
oanda_view.get_open_trades(account)
return self.queue
@try_except
def call_closetrade(self, other_args: List[str]):
"""Close a trade by id."""
parser = argparse.ArgumentParser(
add_help=False,
prog="closetrade",
description="Close a trade by id.",
)
parser.add_argument(
"-i",
"--id",
dest="orderID",
action="store",
type=str,
required=False,
help="The Trade ID to close. ",
)
parser.add_argument(
"-u",
"--units",
dest="units",
action="store",
required=False,
help="The number of units on the trade to close. If not set it "
+ "defaults to all units. ",
)
if other_args:
if "-i" not in other_args[0]:
other_args.insert(0, "-i")
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
orderID = ns_parser.orderID
units = ns_parser.units
oanda_view.close_trade(account, orderID, units)
return self.queue
@try_except
def call_candles(self, other_args: List[str]):
"""Plot candle chart for a loaded currency pair."""
parser = argparse.ArgumentParser(
add_help=False,
prog="candles",
description="Display Candle Data",
)
parser.add_argument(
"-g",
"--granularity",
dest="granularity",
action="store",
type=str,
default="D",
required=False,
help="The timeframe to get for the candle chart (Seconds: S5, S10, S15, S30 "
+ "Minutes: M1, M2, M4, M5, M10, M15, M30 Hours: H1, H2, H3, H4, H6, H8, H12 "
+ "Day (default): D, Week: W Month: M",
)
parser.add_argument(
"-l",
"--limit",
dest="candlecount",
action="store",
default=180,
type=int,
required=False,
help="The number of candles to retrieve. Default:180 ",
)
parser.add_argument(
"-a",
"--ad",
dest="ad",
action="store_true",
help="Adds ad (Accumulation/Distribution Indicator) to the chart",
)
parser.add_argument(
"-b",
"--bbands",
dest="bbands",
action="store_true",
help="Adds Bollinger Bands to the chart",
)
parser.add_argument(
"-c",
"--cci",
dest="cci",
action="store_true",
help="Adds cci (Commodity Channel Index) to the chart",
)
parser.add_argument(
"-e",
"--ema",
dest="ema",
action="store_true",
help="Adds ema (Exponential Moving Average) to the chart",
)
parser.add_argument(
"-o",
"--obv",
dest="obv",
action="store_true",
help="Adds obv (On Balance Volume) to the chart",
)
parser.add_argument(
"-r",
"--rsi",
dest="rsi",
action="store_true",
help="Adds rsi (Relative Strength Index) to the chart",
)
parser.add_argument(
"-s",
"--sma",
dest="sma",
action="store_true",
help="Adds sma (Simple Moving Average) to the chart",
)
parser.add_argument(
"-v",
"--vwap",
dest="vwap",
action="store_true",
help="Adds vwap (Volume Weighted Average Price) to the chart",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
oanda_view.show_candles(
self.instrument,
granularity=ns_parser.granularity.upper(),
candlecount=ns_parser.candlecount,
additional_charts={
"ad": ns_parser.ad,
"bbands": ns_parser.bbands,
"cci": ns_parser.cci,
"ema": ns_parser.ema,
"obv": ns_parser.obv,
"rsi": ns_parser.rsi,
"sma": ns_parser.sma,
"vwap": ns_parser.vwap,
},
)
return self.queue
@try_except
def call_calendar(self, other_args: List[str]):
"""Call calendar"""
oanda_view.calendar(self.instrument, other_args)
"""Call calendar."""
parser = argparse.ArgumentParser(
add_help=False,
prog="calendar",
description="Show Calendar Data",
)
parser.add_argument(
"-d",
"--days",
dest="days",
action="store",
type=int,
default=7,
required=False,
help="The number of days to search for, up to 30 forward or backward "
+ "use negative numbers to search back. ",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
days = ns_parser.days
oanda_view.calendar(self.instrument, days)
return self.queue
def menu():
"""Forex Menu"""
oanda_controller = OandaController()
oanda_controller.call_help(None)
def menu(queue: List[str] = None):
"""Oanda Forex Menu."""
oanda_controller = OandaController(queue)
an_input = "HELP_ME"
while True:
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in oanda_controller.CHOICES}
)
# There is a command in the queue
if oanda_controller.queue and len(oanda_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if oanda_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(oanda_controller.queue) > 1:
return oanda_controller.queue[1:]
return []
an_input = session.prompt(
f"{get_flair()} (forex)(oanda)> ",
completer=completer,
)
# Consume 1 element from the queue
an_input = oanda_controller.queue[0]
oanda_controller.queue = oanda_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 oanda_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /forex/oanda/ $ {an_input}")
# Get input command from user
else:
an_input = input(f"{get_flair()} (forex)(oanda)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
oanda_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and oanda_controller.completer:
an_input = session.prompt(
f"{get_flair()} /forex/oanda/ $ ",
completer=oanda_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /forex/oanda/ $ ")
try:
process_input = oanda_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
oanda_controller.queue = oanda_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exit\n")
continue
print(
f"\nThe command '{an_input}' doesn't exist on the /forex/oanda menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
oanda_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:])}"
)
if candidate_input == an_input:
an_input = ""
oanda_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
oanda_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -1,2 +1,642 @@
"""Oanda Model"""
"""Oanda Model."""
__docformat__ = "numpy"
import json
from datetime import datetime
from typing import Dict, Union
import pandas as pd
from oandapyV20 import API
from oandapyV20.endpoints import (
accounts,
forexlabs,
instruments,
orders,
positions,
pricing,
trades,
)
from oandapyV20.exceptions import V20Error
from gamestonk_terminal import config_terminal as cfg
client = API(access_token=cfg.OANDA_TOKEN, environment=cfg.OANDA_ACCOUNT_TYPE)
account = cfg.OANDA_ACCOUNT
def fx_price_request(
accountID: str = account, instrument: Union[str, None] = None
) -> Union[Dict[str, str], bool]:
"""Request price for a forex pair.
Parameters
----------
accountID : str, optional
Oanda account ID, by default cfg.OANDA_ACCOUNT
instrument : Union[str, None]
The loaded currency pair, by default None
Returns
-------
Union[Dict[str, str], bool]
The currency pair price or False
"""
if accountID == "REPLACE_ME":
print("Error: Oanda account credentials are required.")
return False
if instrument is None:
print("Error: An instrument should be loaded before running this command.")
return False
try:
parameters = {"instruments": instrument}
request = pricing.PricingInfo(accountID=accountID, params=parameters)
response = client.request(request)
return response
except V20Error as e:
d_error = json.loads(e.msg)
print(d_error["errorMessage"], "\n")
return False
def account_summary_request(accountID: str = account) -> Union[pd.DataFrame, bool]:
"""Request Oanda account summary.
Parameters
----------
accountID : str, optional
Oanda account ID, by default cfg.OANDA_ACCOUNT
Returns
-------
Union[pd.DataFrame, bool]
Account summary data or False
"""
if accountID == "REPLACE_ME":
print("Error: Oanda account credentials are required.")
return False
try:
request = accounts.AccountSummary(accountID=accountID)
response = client.request(request)
df_summary = pd.DataFrame(
[
{"Type": "Balance", "Value": response["account"]["balance"]},
{"Type": "NAV", "Value": response["account"]["NAV"]},
{
"Type": "Unrealized P/L",
"Value": response["account"]["unrealizedPL"],
},
{"Type": "Total P/L", "Value": response["account"]["pl"]},
{
"Type": "Open Trade Count",
"Value": response["account"]["openTradeCount"],
},
{
"Type": "Margin Available",
"Value": response["account"]["marginAvailable"],
},
{"Type": "Margin Used", "Value": response["account"]["marginUsed"]},
{
"Type": "Margin Closeout",
"Value": response["account"]["marginCloseoutNAV"],
},
{
"Type": "Margin Closeout Percent",
"Value": response["account"]["marginCloseoutPercent"],
},
{
"Type": "Margin Closeout Position Value",
"Value": response["account"]["marginCloseoutPositionValue"],
},
]
)
return df_summary
except V20Error as e:
d_error = json.loads(e.msg)
print(d_error["errorMessage"], "\n")
return False
def orderbook_plot_data_request(
instrument: Union[str, None] = None, accountID: str = account
) -> Union[pd.DataFrame, bool]:
"""Request order book data for plotting.
Parameters
----------
instrument : Union[str, None]
The loaded currency pair, by default None
accountID : str, optional
Oanda account ID, by default cfg.OANDA_ACCOUNT
Returns
-------
Union[pd.DataFrame, bool]
Order book data or False
"""
if accountID == "REPLACE_ME":
print("Error: Oanda account credentials are required.")
return False
if instrument is None:
print("Error: An instrument should be loaded before running this command.")
return False
parameters = {"bucketWidth": "1"}
try:
request = instruments.InstrumentsOrderBook(
instrument=instrument, params=parameters
)
response = client.request(request)
df_orderbook_data = pd.DataFrame.from_dict(response["orderBook"]["buckets"])
return df_orderbook_data
except V20Error as e:
d_error = json.loads(e.msg)
print(d_error["errorMessage"], "\n")
return False
def positionbook_plot_data_request(
instrument: Union[str, None] = None, accountID: str = account
) -> Union[pd.DataFrame, bool]:
"""Request position book data for plotting.
Parameters
----------
instrument : Union[str, None]
The loaded currency pair, by default None
accountID : str, optional
Oanda account ID, by default cfg.OANDA_ACCOUNT
Returns
-------
Union[pd.DataFrame, bool]
Position book data or False
"""
if accountID == "REPLACE_ME":
print("Error: Oanda account credentials are required.")
return False
if instrument is None:
print("Error: An instrument should be loaded before running this command.")
return False
try:
request = instruments.InstrumentsPositionBook(instrument=instrument)
response = client.request(request)
df_positionbook_data = pd.DataFrame.from_dict(
response["positionBook"]["buckets"]
)
return df_positionbook_data
except V20Error as e:
d_error = json.loads(e.msg)
print(d_error["errorMessage"], "\n")
return False
def order_history_request(
order_state: str, order_count: int, accountID: str = account
) -> Union[pd.DataFrame, bool]:
"""Request the orders list from Oanda.
Parameters
----------
order_state : str
Filter orders by a specific state ("PENDING", "CANCELLED", etc.)
order_count : int
Limit the number of orders to retrieve
accountID : str, optional
Oanda account ID, by default cfg.OANDA_ACCOUNT
"""
if accountID == "REPLACE_ME":
print("Error: Oanda account credentials are required.")
return False
parameters: Dict[str, Union[str, int]] = {}
parameters["state"] = order_state
parameters["count"] = order_count
try:
request = orders.OrderList(accountID, parameters)
response = client.request(request)
df_order_list = pd.DataFrame.from_dict(response["orders"])
df_order_list = df_order_list[
["id", "instrument", "units", "price", "state", "type"]
]
return df_order_list
except KeyError:
print("No orders were found\n")
return False
except V20Error as e:
d_error = json.loads(e.msg)
print(d_error["errorMessage"], "\n")
return False
def create_order_request(
price: int,
units: int,
instrument: Union[str, None] = None,
accountID: str = account,
) -> Union[pd.DataFrame, bool]:
"""Request creation of buy/sell trade order.
Parameters
----------
instrument : Union[str, None]
The loaded currency pair, by default None
price : int
The price to set for the limit order.
units : int
The number of units to place in the order request.
accountID : str, optional
Oanda account ID, by default cfg.OANDA_ACCOUNT
Returns
-------
Union[pd.DataFrame, bool]
Orders data or False
"""
if accountID == "REPLACE_ME":
print("Error: Oanda account credentials are required.")
return False
if instrument is None:
print("Error: An instrument should be loaded before running this command.")
return False
if "JPY" in instrument or "THB" in instrument or "HUF" in instrument:
price = round(price, 3)
else:
price = round(price, 5)
data = {
"order": {
"price": price,
"instrument": instrument,
"units": units,
"type": "LIMIT",
"timeInForce": "GTC",
"positionFill": "DEFAULT",
}
}
try:
request = orders.OrderCreate(accountID, data)
response = client.request(request)
order_data = []
order_data.append(
{
"Order ID": response["orderCreateTransaction"]["id"],
"Instrument": response["orderCreateTransaction"]["instrument"],
"Price": response["orderCreateTransaction"]["price"],
"Units": response["orderCreateTransaction"]["units"],
}
)
df_orders = pd.DataFrame.from_dict(order_data)
return df_orders
except V20Error as e:
d_error = json.loads(e.msg)
print(d_error["errorMessage"], "\n")
return False
except Exception as e:
print(e)
return False
def cancel_pending_order_request(
orderID: str, accountID: str = account
) -> Union[str, bool]:
"""Request cancellation of a pending order.
Parameters
----------
orderID : str
The pending order ID to cancel.
accountID : str, optional
Oanda account ID, by default cfg.OANDA_ACCOUNT
"""
if accountID == "REPLACE_ME":
print("Error: Oanda account credentials are required.")
return False
try:
request = orders.OrderCancel(accountID, orderID)
response = client.request(request)
order_id = response["orderCancelTransaction"]["orderID"]
return order_id
except V20Error as e:
d_error = json.loads(e.msg)
print(d_error["errorMessage"], "\n")
return False
def open_positions_request(accountID: str = account) -> Union[pd.DataFrame, bool]:
"""Request information on open positions.
Parameters
----------
accountID : str, optional
Oanda account ID, by default cfg.OANDA_ACCOUNT
"""
if accountID == "REPLACE_ME":
print("Error: Oanda account credentials are required.")
return False
try:
request = positions.OpenPositions(accountID)
response = client.request(request)
position_data = []
for i in range(len(response["positions"])):
position_data.append(
{
"Instrument": response["positions"][i]["instrument"],
"Long Units": response["positions"][i]["long"]["units"],
"Total Long P/L": response["positions"][i]["long"]["units"],
"Unrealized Long P/L": response["positions"][i]["long"][
"unrealizedPL"
],
"Short Units": response["positions"][i]["short"]["units"],
"Total Short P/L": response["positions"][i]["short"]["pl"],
"Short Unrealized P/L": response["positions"][i]["short"][
"unrealizedPL"
],
}
)
df_positions = pd.DataFrame.from_dict(position_data)
return df_positions
except V20Error as e:
d_error = json.loads(e.msg)
print(d_error["errorMessage"], "\n")
return False
def pending_orders_request(accountID: str = account) -> Union[pd.DataFrame, bool]:
"""Request information on pending orders.
Parameters
----------
accountID : str, optional
Oanda account ID, by default cfg.OANDA_ACCOUNT
Returns
-------
Union[pd.DataFrame, bool]
Pending orders data or False
"""
if accountID == "REPLACE_ME":
print("Error: Oanda account credentials are required.")
return False
try:
request = orders.OrdersPending(accountID)
response = client.request(request)
pending_data = []
for i in range(len(response["orders"])):
pending_data.append(
{
"Order ID": response["orders"][i]["id"],
"Instrument": response["orders"][i]["instrument"],
"Price": response["orders"][i]["price"],
"Units": response["orders"][i]["units"],
"Time Created": response["orders"][i]["createTime"][:10]
+ " "
+ response["orders"][i]["createTime"][11:19],
"Time In Force": response["orders"][i]["timeInForce"],
}
)
if len(pending_data) == 0:
return pd.DataFrame()
df_pending = pd.DataFrame.from_dict(pending_data)
return df_pending
except V20Error as e:
d_error = json.loads(e.msg)
print(d_error["errorMessage"], "\n")
return False
def open_trades_request(accountID: str = account) -> Union[pd.DataFrame, bool]:
"""Request open trades data.
Parameters
----------
accountID : str, optional
Oanda account ID, by default cfg.OANDA_ACCOUNT
Returns
-------
Union[pd.DataFrame, bool]
Open trades data or False
"""
if accountID == "REPLACE_ME":
print("Error: Oanda account credentials are required.")
return False
try:
request = trades.OpenTrades(accountID)
response = client.request(request)
if "trades" in response and len(response["trades"]) > 0:
df_trades = pd.DataFrame.from_dict(response["trades"])
df_trades = df_trades[
[
"id",
"instrument",
"initialUnits",
"currentUnits",
"price",
"unrealizedPL",
]
]
df_trades = df_trades.rename(
columns={
"id": "ID",
"instrument": "Instrument",
"initialUnits": "Initial Units",
"currentUnits": "Current Units",
"price": "Entry Price",
"unrealizedPL": "Unrealized P/L",
}
)
else:
df_trades = pd.DataFrame()
return df_trades
except V20Error as e:
d_error = json.loads(e.msg)
print(d_error["errorMessage"], "\n")
return False
def close_trades_request(
orderID: str, units: Union[int, None], accountID: str = account
) -> Union[pd.DataFrame, bool]:
"""Close a trade.
Parameters
----------
orderID : str
ID of the order to close
units : Union[int, None]
Number of units to close. If empty default to all.
accountID : str, optional
Oanda account ID, by default cfg.OANDA_ACCOUNT
Returns
-------
Union[pd.DataFrame, bool]
Close trades data or False
"""
if accountID == "REPLACE_ME":
print("Error: Oanda account credentials are required.")
return False
data = {}
if units is not None:
data["units"] = units
try:
request = trades.TradeClose(accountID, orderID, data)
response = client.request(request)
close_data = []
close_data.append(
{
"OrderID": response["orderCreateTransaction"]["tradeClose"]["tradeID"],
"Instrument": response["orderFillTransaction"]["instrument"],
"Units": response["orderCreateTransaction"]["units"],
"Price": response["orderFillTransaction"]["price"],
"P/L": response["orderFillTransaction"]["pl"],
}
)
if len(close_data) == 0:
return pd.DataFrame()
df_trades = pd.DataFrame.from_dict(close_data)
return df_trades
except V20Error as e:
d_error = json.loads(e.msg)
print(d_error["errorMessage"], "\n")
return False
def get_candles_dataframe(
instrument: Union[str, None] = None, granularity: str = "D", candlecount: int = 180
) -> Union[pd.DataFrame, bool]:
"""Request data for candle chart.
Parameters
----------
instrument : str
Loaded currency pair code
granularity : str, optional
Data granularity, by default "D"
candlecount : int, optional
Limit for the number of data points, by default 180
Returns
-------
Union[pd.DataFrame, bool]
Candle chart data or False
"""
if instrument is None:
print("Error: An instrument should be loaded before running this command.")
return False
parameters = {
"granularity": granularity,
"count": candlecount,
}
try:
request = instruments.InstrumentsCandles(instrument, params=parameters)
response = client.request(request)
candles_data = []
for i in range(len(response["candles"])):
candles_data.append(
{
"Date": response["candles"][i]["time"][:10]
+ " "
+ response["candles"][i]["time"][11:19],
"Open": float(response["candles"][i]["mid"]["o"]),
"High": float(response["candles"][i]["mid"]["h"]),
"Low": float(response["candles"][i]["mid"]["l"]),
"Close": float(response["candles"][i]["mid"]["c"]),
"Volume": response["candles"][i]["volume"],
}
)
if len(candles_data) == 0:
df_candles = pd.DataFrame()
else:
df_candles = pd.DataFrame(candles_data)
df_candles.set_index("Date", inplace=True)
df_candles.index = pd.to_datetime(df_candles.index)
return df_candles
except V20Error as e:
d_error = json.loads(e.msg)
print(d_error["errorMessage"], "\n")
return False
def get_calendar_request(
days: int, instrument: Union[str, None] = None
) -> Union[pd.DataFrame, bool]:
"""Request data of significant events calendar.
Parameters
----------
instrument : Union[str, None]
The loaded currency pair, by default None
days : int
Number of days in advance
Returns
-------
Union[pd.DataFrame, bool]
Calendar events data or False
"""
if instrument is None:
print("Error: An instrument should be loaded before running this command.")
return False
parameters = {"instrument": instrument, "period": str(days * 86400 * -1)}
try:
request = forexlabs.Calendar(params=parameters)
response = client.request(request)
l_data = []
for i in enumerate(response):
if "forecast" in response[i[0]]:
forecast = response[i[0]]["forecast"]
if response[i[0]]["unit"] != "Index":
forecast += response[i[0]]["unit"]
else:
forecast = ""
if "market" in response[i[0]]:
market = response[i[0]]["market"]
if response[i[0]]["unit"] != "Index":
market += response[i[0]]["unit"]
else:
market = ""
if "actual" in response[i[0]]:
actual = response[i[0]]["actual"]
if response[i[0]]["unit"] != "Index":
actual += response[i[0]]["unit"]
else:
actual = ""
if "previous" in response[i[0]]:
previous = response[i[0]]["previous"]
if response[i[0]]["unit"] != "Index":
previous += response[i[0]]["unit"]
else:
previous = ""
if "impact" in response[i[0]]:
impact = response[i[0]]["impact"]
else:
impact = ""
l_data.append(
{
"Title": response[i[0]]["title"],
"Time": datetime.fromtimestamp(response[i[0]]["timestamp"]),
"Impact": impact,
"Forecast": forecast,
"Market Forecast": market,
"Currency": response[i[0]]["currency"],
"Region": response[i[0]]["region"],
"Actual": actual,
"Previous": previous,
}
)
if len(l_data) == 0:
df_calendar = pd.DataFrame()
else:
df_calendar = pd.DataFrame(l_data)
return df_calendar
except V20Error as e:
d_error = json.loads(e.msg)
print(d_error["message"], "\n")
return False

File diff suppressed because it is too large Load Diff

View File

@@ -97,6 +97,14 @@ def check_non_negative(value) -> int:
return new_value
def check_non_negative_float(value) -> float:
"""Argparse type to check non negative int"""
new_value = float(value)
if new_value < 0:
raise argparse.ArgumentTypeError(f"{value} is negative")
return new_value
def check_positive_list(value) -> List[int]:
"""Argparse type to return list of positive ints"""
list_of_nums = value.split(",")
@@ -570,7 +578,12 @@ def parse_known_args_and_warn(
if gtff.USE_CLEAR_AFTER_CMD:
system_clear()
(ns_parser, l_unknown_args) = parser.parse_known_args(other_args)
try:
(ns_parser, l_unknown_args) = parser.parse_known_args(other_args)
except SystemExit:
# In case the command has required argument that isn't specified
print("")
return None
if ns_parser.help:
parser.print_help()
@@ -789,6 +802,7 @@ def try_except(f):
return f(*args, **kwargs)
except Exception as e:
logger.exception("%s", type(e).__name__)
return []
return inner

View File

@@ -2,8 +2,10 @@
__docformat__ = "numpy"
import argparse
from typing import List
from typing import List, Union
import difflib
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.menu import session
from gamestonk_terminal.portfolio.brokers.ally import ally_view
@@ -12,39 +14,55 @@ from gamestonk_terminal.helper_funcs import (
parse_known_args_and_warn,
try_except,
system_clear,
EXPORT_ONLY_RAW_DATA_ALLOWED,
)
class AllyController:
CHOICES = [
"?",
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
ALLY_CHOICES = ["holdings", "history", "balances"]
ALLY_STOCK_CHOICES = ["quote", "movers"]
CHOICES_COMMANDS = ["holdings", "history", "balances", "quote", "movers"]
CHOICES += CHOICES_COMMANDS
list_choices = [
"toplosers",
"toppctlosers",
"topvolume",
"topactive",
"topgainers",
"toppctgainers",
]
def __init__(self):
def __init__(self, queue: List[str] = None):
"""CONSTRUCTOR"""
self._ally_parser = argparse.ArgumentParser(add_help=False, prog="ally")
self.CHOICES.extend(self.ALLY_CHOICES)
self.CHOICES.extend(self.ALLY_STOCK_CHOICES)
self._ally_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["movers"]["-t"] = {c: None for c in self.list_choices}
self.completer = NestedCompleter.from_nested_dict(choices)
self.queue = queue or list()
def print_help(self):
"""Print help"""
help_text = """
Ally:
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon the program
holdings show account holdings
history show history of your account
balances show balance details of account
@@ -61,28 +79,40 @@ Stock Information:
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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._ally_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -90,17 +120,53 @@ Stock Information:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu."""
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program."""
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "ally")
self.queue.insert(0, "bro")
self.queue.insert(0, "portfolio")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit", "reset", "portfolio", "bro", "ally"]
@try_except
def call_holdings(self, other_args: List[str]):
@@ -111,18 +177,12 @@ Stock Information:
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description="Display info about your trading accounts on Ally",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
ally_view.display_holdings(export=ns_parser.export)
if ns_parser:
ally_view.display_holdings(export=ns_parser.export)
return self.queue
@try_except
def call_history(self, other_args: List[str]):
@@ -134,25 +194,21 @@ Stock Information:
description="""Account transaction history""",
)
parser.add_argument(
"-n",
"--num",
dest="num",
"-l",
"--limit",
dest="limit",
default=15,
type=int,
help="Number of recent transactions to show",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
ally_view.display_history(n_to_show=ns_parser.num, export=ns_parser.export)
if ns_parser:
ally_view.display_history(
n_to_show=ns_parser.limit, export=ns_parser.export
)
return self.queue
@try_except
def call_balances(self, other_args: List[str]):
@@ -163,18 +219,12 @@ Stock Information:
prog="balances",
description="""Account balance details""",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
ally_view.display_balances(export=ns_parser.export)
if ns_parser:
ally_view.display_balances(export=ns_parser.export)
return self.queue
@try_except
def call_quote(self, other_args: List[str]):
@@ -185,7 +235,7 @@ Stock Information:
prog="quote",
description="""Get stock quote""",
)
if other_args and "-" not in other_args[0]:
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-t")
parser.add_argument(
"-t",
@@ -196,9 +246,9 @@ Stock Information:
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
ally_view.display_stock_quote(ns_parser.ticker)
if ns_parser:
ally_view.display_stock_quote(ns_parser.ticker)
return self.queue
@try_except
def call_movers(self, other_args: List[str]):
@@ -210,17 +260,10 @@ Stock Information:
description="""Get stock movers""",
)
parser.add_argument(
"-l",
"--list",
"-t",
"--type",
help="List to get movers of",
choices=[
"toplosers",
"toppctlosers",
"topvolume",
"topactive",
"topgainers",
"toppctgainers",
],
choices=self.list_choices,
default="topactive",
dest="list_type",
)
@@ -238,51 +281,93 @@ Stock Information:
dest="exchange",
)
parser.add_argument(
"-n", "--num", help="Number to show", type=int, default=15, dest="num"
"-l", "--limit", help="Number to show", type=int, default=15, dest="limit"
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
ally_view.display_top_lists(
list_type=ns_parser.list_type,
exchange=ns_parser.exchange,
num_to_show=ns_parser.num,
export=ns_parser.export,
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
ally_view.display_top_lists(
list_type=ns_parser.list_type,
exchange=ns_parser.exchange,
num_to_show=ns_parser.limit,
export=ns_parser.export,
)
return self.queue
def menu():
ally_controller = AllyController()
ally_controller.print_help()
def menu(queue: List[str] = None):
"""Ally Menu"""
ally_controller = AllyController(queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if ally_controller.queue and len(ally_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if ally_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(ally_controller.queue) > 1:
return ally_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = ally_controller.queue[0]
ally_controller.queue = ally_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 ally_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /portfolio/bro/ally/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in ally_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (bro)>(ally)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (bro)>(ally)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
ally_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and ally_controller.completer:
an_input = session.prompt(
f"{get_flair()} /portfolio/bro/ally/ $ ",
completer=ally_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /portfolio/bro/ally/ $ ")
try:
process_input = ally_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
ally_controller.queue = ally_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
continue
print(
f"\nThe command '{an_input}' doesn't exist on the /portfolio/bro/ally menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
ally_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:])}"
)
if candidate_input == an_input:
an_input = ""
ally_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
ally_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -3,8 +3,11 @@ __docformat__ = "numpy"
# pylint: disable=R1710
import argparse
from typing import List, Union, Set
import difflib
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import get_flair, system_clear
from gamestonk_terminal.menu import session
@@ -18,26 +21,43 @@ from gamestonk_terminal.portfolio.brokers.coinbase import coinbase_controller
class BrokersController:
"""Brokers Controller"""
CHOICES = ["?", "cls", "help", "q", "quit"]
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS: List = []
BROKERS = ["cb", "ally", "rh", "degiro"]
CHOICES += BROKERS
CHOICES += BROKERS + CHOICES_COMMANDS
def __init__(self):
def __init__(self, queue: List[str] = None):
self.bro_parser = argparse.ArgumentParser(add_help=False, prog="bro")
self.bro_parser.add_argument("cmd", choices=self.CHOICES)
self.broker_list = set()
self.broker_list: Set = set()
self.merged_holdings = None
if queue:
self.queue = queue
else:
self.queue = list()
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)
def print_help(self):
"""Print help"""
help_string = """
What would you like to do?
cls clear screen
?/help show this menu again
q quit this menu, and shows back to >Portfolio, logs out of brokers
quit quit to abandon program, logs out of brokers
Brokers:
> ally Ally Invest Menu
@@ -54,28 +74,40 @@ Crypto Brokers:
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.bro_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -83,41 +115,65 @@ Crypto Brokers:
lambda _: "Command not recognized!",
)(other_args)
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
def call_q(self, _):
"""Process q command - quit the menu"""
return False
return self.queue
def call_quit(self, _):
"""Process quit command - quit the program"""
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "bro")
self.queue.insert(0, "portfolio")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "reset", "portfolio", "bro"]
def call_degiro(self, _):
"""Process degiro command."""
if degiro_controller.menu():
return True
print("")
return degiro_controller.menu(self.queue)
def call_ally(self, _):
"""Process degiro command."""
if ally_controller.menu():
return True
print("")
"""Process ally command."""
return ally_controller.menu(self.queue)
def call_rh(self, _):
"""Process degiro command."""
if robinhood_controller.menu():
return True
print("")
"""Process rh command."""
return robinhood_controller.menu(self.queue)
def call_cb(self, _):
"""Process degiro command."""
if coinbase_controller.menu():
return True
print("")
return coinbase_controller.menu(self.queue)
# TODO: Consistent way of merging across brokers including crypto
# def call_login(self, other_args):
@@ -145,9 +201,8 @@ Crypto Brokers:
# print(f"{broker} not supported")
def menu():
def menu(queue: List[str] = None):
"""Brokers Menu"""
bro_controller = BrokersController()
print(
"\nUSE THIS MENU AT YOUR OWN DISCRETION\n"
" - This menu is the only one in the entire repository that has access to your broker accounts. "
@@ -158,27 +213,76 @@ def menu():
"is impossible for us to check the coding standards and security of each of these modules. "
"Hence why adding this disclaimer here."
)
bro_controller.print_help()
bro_controller = BrokersController(queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if bro_controller.queue and len(bro_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if bro_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(bro_controller.queue) > 1:
return bro_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = bro_controller.queue[0]
bro_controller.queue = bro_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 bro_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /portfolio/bro/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in bro_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (portfolio)>(bro)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (portfolio)>(bro)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
bro_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and bro_controller.completer:
an_input = session.prompt(
f"{get_flair()} /portfolio/bro/ $ ",
completer=bro_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /portfolio/bro/ $ ")
try:
process_input = bro_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
bro_controller.queue = bro_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
continue
print(
f"\nThe command '{an_input}' doesn't exist on the /portfolio/bro menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
bro_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:])}"
)
if candidate_input == an_input:
an_input = ""
bro_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
bro_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -3,6 +3,9 @@ __docformat__ = "numpy"
# pylint: disable=R0904, C0302, W0622
import argparse
from typing import List, Union
import difflib
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.menu import session
@@ -15,38 +18,63 @@ from gamestonk_terminal.helper_funcs import (
check_positive,
try_except,
system_clear,
EXPORT_ONLY_RAW_DATA_ALLOWED,
)
class CoinbaseController:
CHOICES = [
"?",
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CB_CHOICES = ["account", "history", "orders", "deposits"]
CHOICES_COMMANDS = ["account", "history", "orders", "deposits"]
order_sortby = [
"product_id",
"side",
"price",
"size",
"type",
"created_at",
"status",
]
deposit_sort = [
"created_at",
"amount",
]
def __init__(self):
def __init__(self, queue: List[str] = None):
"""CONSTRUCTOR"""
self._cb_parser = argparse.ArgumentParser(add_help=False, prog="cb")
self.CHOICES.extend(self.CB_CHOICES)
self.CHOICES.extend(self.CHOICES_COMMANDS)
self._cb_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["orders"]["-s"] = {c: None for c in self.order_sortby}
choices["orders"]["--sortby"] = {c: None for c in self.order_sortby}
choices["deposits"]["-s"] = {c: None for c in self.deposit_sort}
choices["deposits"]["--sortby"] = {c: None for c in self.deposit_sort}
self.completer = NestedCompleter.from_nested_dict(choices)
self.queue = queue if queue else list()
def print_help(self):
"""Print help"""
help_text = """
Coinbase:
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon the program
account show balance of your account
history show history of your account
deposits show all your deposits or internal transfers
@@ -59,28 +87,40 @@ Coinbase:
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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._cb_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -88,17 +128,53 @@ Coinbase:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu."""
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program."""
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "cb")
self.queue.insert(0, "bro")
self.queue.insert(0, "portfolio")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit", "reset", "portfolio", "bro", "cb"]
@try_except
def call_account(self, other_args):
@@ -124,28 +200,21 @@ Coinbase:
dest="currency",
help="Currency to display value in.",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
if other_args and other_args[0][0] != "-":
other_args.insert(0, "--acc")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
coinbase_view.display_account(
currency=ns_parser.currency,
export=ns_parser.export,
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
coinbase_view.display_account(
currency=ns_parser.currency,
export=ns_parser.export,
)
return self.queue
@try_except
def call_history(self, other_args):
"""Process account command"""
@@ -172,27 +241,20 @@ Coinbase:
default=20,
type=check_positive,
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
if other_args and other_args[0][0] != "-":
other_args.insert(0, "--acc")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
coinbase_view.display_history(
ns_parser.account, ns_parser.export, ns_parser.limit
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
coinbase_view.display_history(
ns_parser.account, ns_parser.export, ns_parser.limit
)
return self.queue
@try_except
def call_orders(self, other_args):
"""Process orders command"""
@@ -217,15 +279,7 @@ Coinbase:
type=str,
help="Sort by given column. Default: created_at",
default="created_at",
choices=[
"product_id",
"side",
"price",
"size",
"type",
"created_at",
"status",
],
choices=self.order_sortby,
)
parser.add_argument(
"--descend",
@@ -234,27 +288,19 @@ Coinbase:
dest="descend",
default=False,
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
if other_args and other_args[0][0] != "-":
other_args.insert(0, "--acc")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
coinbase_view.display_orders(
ns_parser.limit, ns_parser.sortby, ns_parser.descend, ns_parser.export
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
coinbase_view.display_orders(
ns_parser.limit, ns_parser.sortby, ns_parser.descend, ns_parser.export
)
return self.queue
@try_except
def call_deposits(self, other_args):
"""Process deposits command"""
@@ -288,10 +334,7 @@ Coinbase:
type=str,
help="Sort by given column. Default: created_at",
default="created_at",
choices=[
"created_at",
"amount",
],
choices=self.deposit_sort,
)
parser.add_argument(
"--descend",
@@ -300,51 +343,91 @@ Coinbase:
dest="descend",
default=False,
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
coinbase_view.display_deposits(
ns_parser.limit,
ns_parser.sortby,
ns_parser.type,
ns_parser.descend,
ns_parser.export,
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
coinbase_view.display_deposits(
ns_parser.limit,
ns_parser.sortby,
ns_parser.type,
ns_parser.descend,
ns_parser.export,
)
return self.queue
def menu():
cb_controller = CoinbaseController()
cb_controller.print_help()
def menu(queue: List[str] = None):
"""Coinbase Menu"""
cb_controller = CoinbaseController(queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if cb_controller.queue and len(cb_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if cb_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(cb_controller.queue) > 1:
return cb_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = cb_controller.queue[0]
cb_controller.queue = cb_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 cb_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /portfolio/bro/cb/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in cb_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (bro)>(cb)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (bro)>(cb)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
cb_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and cb_controller.completer:
an_input = session.prompt(
f"{get_flair()} /portfolio/bro/cb/ $ ",
completer=cb_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /portfolio/bro/cb/ $ ")
try:
process_input = cb_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
cb_controller.queue = cb_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
continue
print(
f"\nThe command '{an_input}' doesn't exist on the /portfolio/bro/cb menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
cb_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:])}"
)
if candidate_input == an_input:
an_input = ""
cb_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
cb_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -1,5 +1,7 @@
# IMPORTATION STANDARD
import argparse
from typing import List, Union
import difflib
# IMPORTATION THIRDPARTY
from prompt_toolkit.completion import NestedCompleter
@@ -18,9 +20,20 @@ from gamestonk_terminal.portfolio.brokers.degiro.degiro_view import DegiroView
class DegiroController:
CHOICES = [
"?",
CHOICES = CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = [
"cancel",
"companynews",
"create",
@@ -30,22 +43,123 @@ class DegiroController:
"logout",
"lookup",
"pending",
"q",
"quit",
"topnews",
"update",
"help",
]
CHOICES += CHOICES_COMMANDS
def __init__(self):
def __init__(self, queue: List[str] = None):
self.__degiro_view = DegiroView()
self.__degiro_parser = argparse.ArgumentParser(
add_help=False,
prog="degiro",
)
self.__degiro_parser.add_argument("cmd", choices=self.CHOICES)
self.completer: Union[None, NestedCompleter] = None
def cancel(self, l_args):
if session and gtff.USE_PROMPT_TOOLKIT:
choices: dict = {c: {} for c in self.CHOICES}
self.completer = NestedCompleter.from_nested_dict(choices)
self.queue = queue if queue else list()
def switch(self, an_input: str):
"""Process and dispatch input
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.__degiro_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"
return getattr(
self,
"call_" + known_args.cmd,
lambda _: "Command not recognized!",
)(other_args)
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
DegiroView.help_display()
return self.queue
def call_quit(self, _):
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "degiro")
self.queue.insert(0, "bro")
self.queue.insert(0, "portfolio")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit", "reset", "portfolio", "bro", "degiro"]
def cancel(self, other_args: List[str]):
"""Cancel an order using the `id`."""
# PARSE ARGS
@@ -58,11 +172,12 @@ class DegiroController:
help="Order's id.",
type=str,
)
ns_parser = parse_known_args_and_warn(parser, l_args)
ns_parser = parse_known_args_and_warn(parser, other_args)
self.__degiro_view.cancel(ns_parser=ns_parser)
return self.queue
def companynews(self, l_args):
def companynews(self, other_args: List[str]):
"""Display news related to a company using its ISIN."""
# PARSE ARGS
@@ -75,11 +190,12 @@ class DegiroController:
type=str,
help="ISIN code of the company.",
)
ns_parser = parse_known_args_and_warn(parser, l_args)
ns_parser = parse_known_args_and_warn(parser, other_args)
self.__degiro_view.companynews(ns_parser=ns_parser)
return self.queue
def create(self, l_args):
def create(self, other_args: List[str]):
"""Create an order."""
# PARSE ARGS
@@ -153,15 +269,12 @@ class DegiroController:
required=False,
type=str,
)
ns_parser = parse_known_args_and_warn(parser, l_args)
ns_parser = parse_known_args_and_warn(parser, other_args)
self.__degiro_view.create(ns_parser=ns_parser)
return self.queue
def help(self, _):
"""Show the help menu."""
DegiroView.help_display()
def hold(self, l_args):
def hold(self, other_args):
"""Display held products."""
# PARSE ARGS
@@ -169,11 +282,12 @@ class DegiroController:
add_help=False,
prog="hold",
)
ns_parser = parse_known_args_and_warn(parser, l_args)
ns_parser = parse_known_args_and_warn(parser, other_args)
self.__degiro_view.hold(ns_parser=ns_parser)
return self.queue
def lastnews(self, l_args):
def lastnews(self, other_args: List[str]):
"""Display latest news."""
# PARSE ARGS
@@ -189,11 +303,12 @@ class DegiroController:
help="Number of news to display.",
required=False,
)
ns_parser = parse_known_args_and_warn(parser, l_args)
ns_parser = parse_known_args_and_warn(parser, other_args)
self.__degiro_view.lastnews(ns_parser=ns_parser)
return self.queue
def login(self, l_args):
def login(self, other_args: List[str]):
"""Connect to Degiro's API."""
# PARSE ARGS
@@ -229,11 +344,12 @@ class DegiroController:
default=None,
help="TOTP SECRET (2FA).",
)
ns_parser = parse_known_args_and_warn(parser, l_args)
ns_parser = parse_known_args_and_warn(parser, other_args)
self.__degiro_view.login(ns_parser=ns_parser)
return self.queue
def logout(self, l_args):
def logout(self, other_args: List[str]):
"""Log out from Degiro's API."""
# PARSE ARGS
@@ -241,11 +357,11 @@ class DegiroController:
add_help=False,
prog="logout",
)
ns_parser = parse_known_args_and_warn(parser, l_args)
ns_parser = parse_known_args_and_warn(parser, other_args)
self.__degiro_view.logout(ns_parser=ns_parser)
def lookup(self, l_args):
def lookup(self, other_args: List[str]):
"""Search for products by their name."""
# PARSING ARGS
@@ -272,11 +388,12 @@ class DegiroController:
default=0,
help="To use an offset.",
)
ns_parser = parse_known_args_and_warn(parser, l_args)
ns_parser = parse_known_args_and_warn(parser, other_args)
self.__degiro_view.lookup(ns_parser=ns_parser)
return self.queue
def pending(self, l_args):
def pending(self, other_args: List[str]):
"""Display pending orders."""
# PARSING ARGS
@@ -284,19 +401,12 @@ class DegiroController:
add_help=False,
prog="pending",
)
ns_parser = parse_known_args_and_warn(parser, l_args)
ns_parser = parse_known_args_and_warn(parser, other_args)
self.__degiro_view.pending(ns_parser=ns_parser)
return self.queue
def q(self, _):
"""Process Q command - quit the menu."""
return False
def quit(self, _):
"""Process Quit command - quit the program."""
return True
def topnews(self, l_args):
def topnews(self, other_args: List[str]):
"""Display top news."""
# PARSING ARGS
@@ -304,11 +414,12 @@ class DegiroController:
add_help=False,
prog="topnews",
)
ns_parser = parse_known_args_and_warn(parser, l_args)
ns_parser = parse_known_args_and_warn(parser, other_args)
self.__degiro_view.topnews(ns_parser=ns_parser)
return self.queue
def update(self, l_args):
def update(self, other_args: List[str]):
"""Update an order."""
# PARSING ARGS
@@ -328,79 +439,86 @@ class DegiroController:
required=True,
type=float,
)
ns_parser = parse_known_args_and_warn(parser, l_args)
ns_parser = parse_known_args_and_warn(parser, other_args)
self.__degiro_view.update(ns_parser=ns_parser)
def switch(self, an_input: str):
"""Process and dispatch input
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
"""
try:
degiro_parser = self.__degiro_parser
# Empty command
if not an_input:
print("")
return None
(known_args, other_args) = degiro_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
DegiroView.help_display()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
return getattr(
self,
"call_" + known_args.cmd,
lambda _: "Command not recognized!",
)(other_args)
except Exception as e:
print(e)
print("")
return None
return self.queue
def menu():
def menu(queue: List[str] = None):
"""Degiro Menu"""
# SETUP CONTROLLER
degiro_controller = DegiroController()
degiro_controller.help(None)
degiro_controller = DegiroController(queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if degiro_controller.queue and len(degiro_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if degiro_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(degiro_controller.queue) > 1:
return degiro_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = degiro_controller.queue[0]
degiro_controller.queue = degiro_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 degiro_controller.CHOICES_COMMANDS
):
print(f"{get_flair()} /portfolio/bro/degiro/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in degiro_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (bro)>(degiro)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (bro)>(degiro)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
DegiroView.help_display()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and degiro_controller.completer:
an_input = session.prompt(
f"{get_flair()} /portfolio/bro/degiro/ $ ",
completer=degiro_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /portfolio/bro/degiro/ $ ")
try:
process_input = degiro_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
degiro_controller.queue = degiro_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
continue
print(
f"\nThe command '{an_input}' doesn't exist on the /portfolio/bro/degiro menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
degiro_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:])}"
)
if candidate_input == an_input:
an_input = ""
degiro_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
degiro_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -44,10 +44,6 @@ class DegiroView:
def help_display():
print(
"\nDegiro:\n"
" help show this help menu again\n"
" q quit degiro standalone menu\n"
" quit quit the app\n"
"\n"
" login connect to degiro's api\n"
" logout disconnect from degiro's api\n"
"\n"

View File

@@ -2,7 +2,9 @@
__docformat__ = "numpy"
import argparse
from typing import List
from typing import List, Union
import difflib
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.menu import session
@@ -15,37 +17,53 @@ from gamestonk_terminal.helper_funcs import (
parse_known_args_and_warn,
try_except,
system_clear,
EXPORT_ONLY_RAW_DATA_ALLOWED,
)
valid_span = ["day", "week", "month", "3month", "year", "5year", "all"]
valid_interval = ["5minute", "10minute", "hour", "day", "week"]
class RobinhoodController:
CHOICES = ["?", "cls", "help", "q", "quit", "login"]
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
Robinhood_CHOICES = ["holdings", "history", "balances"]
CHOICES_COMMANDS = ["holdings", "history", "login"]
def __init__(self):
valid_span = ["day", "week", "month", "3month", "year", "5year", "all"]
valid_interval = ["5minute", "10minute", "hour", "day", "week"]
def __init__(self, queue: List[str] = None):
"""CONSTRUCTOR"""
self._robinhood_parser = argparse.ArgumentParser(
add_help=False, prog="robinhood"
)
self.CHOICES.extend(self.Robinhood_CHOICES)
self._rh_parser = argparse.ArgumentParser(add_help=False, prog="robinhood")
self.CHOICES.extend(self.CHOICES_COMMANDS)
self._robinhood_parser.add_argument("cmd", choices=self.CHOICES)
self._rh_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["history"]["-i"] = {c: None for c in self.valid_interval}
choices["history"]["--interval"] = {c: None for c in self.valid_interval}
choices["history"]["-s"] = {c: None for c in self.valid_span}
choices["history"]["--span"] = {c: None for c in self.valid_span}
self.completer = NestedCompleter.from_nested_dict(choices)
self.queue = queue if queue else list()
def print_help(self):
"""Print help"""
help_text = """
Robinhood:
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon the program
login login to robinhood
holdings show account holdings in stocks
@@ -59,30 +77,40 @@ Robinhood:
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
return self.queue
(known_args, other_args) = self._robinhood_parser.parse_known_args(
an_input.split()
)
# Navigation slash is being used
if "/" in an_input:
actions = an_input.split("/")
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Absolute path is specified
if not actions[0]:
an_input = "home"
# Relative path so execute first instruction
else:
an_input = actions[0]
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# Add all instructions to the queue
for cmd in actions[1:][::-1]:
if cmd:
self.queue.insert(0, cmd)
(known_args, other_args) = self._rh_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"
return getattr(
self,
@@ -90,31 +118,59 @@ Robinhood:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu."""
try:
robinhood_model.logoff()
except Exception:
# Exception would be not logged in - perfectly fine
pass
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program."""
try:
robinhood_model.logoff()
except Exception:
# Exception would be not logged in - perfectly fine
pass
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "rh")
self.queue.insert(0, "bro")
self.queue.insert(0, "portfolio")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit", "reset", "portfolio", "bro", "rh"]
@try_except
def call_login(self, _):
"""Process login"""
robinhood_model.login()
return self.queue
@try_except
def call_holdings(self, other_args: List[str]):
@@ -125,18 +181,12 @@ Robinhood:
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description="Display info about your trading accounts on Robinhood",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
robinhood_view.display_holdings(export=ns_parser.export)
if ns_parser:
robinhood_view.display_holdings(export=ns_parser.export)
return self.queue
@try_except
def call_history(self, other_args: List[str]):
@@ -152,7 +202,7 @@ Robinhood:
"--span",
dest="span",
type=str,
choices=valid_span,
choices=self.valid_span,
default="3month",
help="Span of historical data",
)
@@ -161,52 +211,93 @@ Robinhood:
"--interval",
dest="interval",
default="day",
choices=valid_interval,
choices=self.valid_interval,
type=str,
help="Interval to look at portfolio",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
robinhood_view.display_historical(
interval=ns_parser.interval,
span=ns_parser.span,
export=ns_parser.export,
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
robinhood_view.display_historical(
interval=ns_parser.interval,
span=ns_parser.span,
export=ns_parser.export,
)
return self.queue
def menu():
robinhood_controller = RobinhoodController()
robinhood_controller.print_help()
def menu(queue: List[str] = None):
"""Robinhood Menu"""
rh_controller = RobinhoodController(queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if rh_controller.queue and len(rh_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if rh_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(rh_controller.queue) > 1:
return rh_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = rh_controller.queue[0]
rh_controller.queue = rh_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 rh_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /portfolio/bro/rh/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in robinhood_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (bro)>(rh)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (bro)>(rh)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
rh_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and rh_controller.completer:
an_input = session.prompt(
f"{get_flair()} /portfolio/bro/rh/ $ ",
completer=rh_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /portfolio/bro/rh/ $ ")
try:
process_input = robinhood_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
rh_controller.queue = rh_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
continue
print(
f"\nThe command '{an_input}' doesn't exist on the /portfolio/bro/rh menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
rh_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:])}"
)
if candidate_input == an_input:
an_input = ""
rh_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
rh_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -159,22 +159,20 @@ Portfolio: {self.portfolio_name or None}
dest="path",
)
if other_args and "-" not in other_args[0]:
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 not ns_parser:
return
self.portfolio_name = ns_parser.path
self.portfolio = portfolio_model.load_portfolio(
full_path=os.path.join(portfolios_path, ns_parser.path),
sector=ns_parser.sector,
last_price=ns_parser.last_price,
show_nan=ns_parser.show_nan,
)
if not self.portfolio.empty:
print(f"Successfully loaded: {self.portfolio_name}\n")
if ns_parser:
self.portfolio_name = ns_parser.path
self.portfolio = portfolio_model.load_portfolio(
full_path=os.path.join(portfolios_path, ns_parser.path),
sector=ns_parser.sector,
last_price=ns_parser.last_price,
show_nan=ns_parser.show_nan,
)
if not self.portfolio.empty:
print(f"Successfully loaded: {self.portfolio_name}\n")
@try_except
def call_group(self, other_args):
@@ -185,7 +183,7 @@ Portfolio: {self.portfolio_name or None}
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description="Displays portfolio grouped by a given column",
)
if other_args and "-" not in other_args[0]:
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-g")
parser.add_argument(
"-g",
@@ -213,18 +211,15 @@ Portfolio: {self.portfolio_name or None}
# dest="cols")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if "value" not in self.portfolio.columns:
print(
"'value' column not in portfolio. Either add manually or load without --no_last_price flag\n"
)
return
portfolio_view.display_group_holdings(
portfolio=self.portfolio, group_column=ns_parser.group
)
if ns_parser:
if "value" in self.portfolio.columns:
portfolio_view.display_group_holdings(
portfolio=self.portfolio, group_column=ns_parser.group
)
else:
print(
"'value' column not in portfolio. Either add manually or load without --no_last_price flag\n"
)
@try_except
def call_view(self, other_args):
@@ -244,19 +239,19 @@ Portfolio: {self.portfolio_name or None}
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
available_ports = os.listdir(portfolios_path)
if ns_parser.file_format != "all":
available_ports = [
port
for port in available_ports
if port.endswith(ns_parser.file_format)
]
available_ports = os.listdir(portfolios_path)
if ns_parser.file_format != "all":
available_ports = [
port for port in available_ports if port.endswith(ns_parser.file_format)
]
print("\nAvailable Portfolios:\n")
for port in available_ports:
print(port)
print("")
print("\nAvailable Portfolios:\n")
for port in available_ports:
print(port)
print("")
def menu():

View File

@@ -6,7 +6,7 @@ import difflib
import os
from os import listdir
from os.path import isfile, join
from typing import List
from typing import List, Union
from datetime import datetime
from prompt_toolkit.completion import NestedCompleter
@@ -15,18 +15,13 @@ import pandas as pd
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import (
get_flair,
MENU_GO_BACK,
MENU_QUIT,
MENU_RESET,
try_except,
valid_date,
check_positive_float,
system_clear,
)
from gamestonk_terminal.menu import session
from gamestonk_terminal.portfolio.brokers import bro_controller
# from gamestonk_terminal.portfolio.portfolio_analysis import pa_controller
from gamestonk_terminal.portfolio.portfolio_optimization import po_controller
from gamestonk_terminal.portfolio import (
portfolio_view,
@@ -36,7 +31,7 @@ from gamestonk_terminal.portfolio import (
)
from gamestonk_terminal.helper_funcs import parse_known_args_and_warn
# pylint: disable=R1710,E1101
# pylint: disable=R1710,E1101,C0415
class PortfolioController:
@@ -44,17 +39,23 @@ class PortfolioController:
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_MENUS = [
"bro",
# "pa",
"po",
]
CHOICES_COMMANDS = [
"load",
"save",
"show",
@@ -64,18 +65,26 @@ class PortfolioController:
"rmr",
]
CHOICES += CHOICES_MENUS
CHOICES += CHOICES_MENUS + CHOICES_COMMANDS
def __init__(self):
def __init__(self, queue: List[str] = None):
"""Constructor"""
self.port_parser = argparse.ArgumentParser(add_help=False, prog="portfolio")
self.port_parser.add_argument(
"cmd",
choices=self.CHOICES,
)
self.completer = NestedCompleter.from_nested_dict(
{c: None for c in 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()
self.portfolio = pd.DataFrame(
columns=[
"Name",
@@ -93,13 +102,8 @@ class PortfolioController:
"""Print help"""
help_text = """
What do you want to do?
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon the program
reset reset terminal and reload configs
> bro brokers holdings, \t\t supports: robinhood, ally, degiro
> bro brokers holdings, \t\t supports: robinhood, ally, degiro, coinbase
> po portfolio optimization, \t optimal portfolio weights from pyportfolioopt
Portfolio:
@@ -122,28 +126,40 @@ Graphs:
Returns
-------
MENU_GO_BACK, MENU_QUIT, MENU_RESET
MENU_GO_BACK - Show main context menu again
MENU_QUIT - Quit terminal
MENU_RESET - Reset terminal and go back to same previous menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.port_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -151,46 +167,57 @@ Graphs:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help Command"""
self.print_help()
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return MENU_GO_BACK
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - exit the program"""
return MENU_QUIT
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "q")
return self.queue
return ["q"]
def call_exit(self, _):
"""Process exit terminal command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "q")
self.queue.insert(0, "q")
return self.queue
return ["q", "q"]
def call_reset(self, _):
"""Process Reset command - reset the program"""
return MENU_RESET
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "portfolio")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "reset", "portfolio"]
# MENUS
def call_bro(self, _):
"""Process bro command"""
ret = bro_controller.menu()
if ret is False:
self.print_help()
else:
return True
from gamestonk_terminal.portfolio.brokers import bro_controller
# def call_pa(self, _):
# """Process pa command"""
# ret = pa_controller.menu()
# if ret is False:
# self.print_help()
# else:
# return True
return bro_controller.menu(self.queue)
def call_po(self, _):
"""Process po command"""
ret = po_controller.menu([])
if ret is False:
self.print_help()
else:
return True
return po_controller.menu([], self.queue)
@try_except
def call_load(self, other_args: List[str]):
@@ -217,11 +244,10 @@ Graphs:
other_args.insert(0, "-n")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
self.portfolio = portfolio_model.load_df(ns_parser.name)
print("")
if ns_parser:
self.portfolio = portfolio_model.load_df(ns_parser.name)
print("")
return self.queue
@try_except
def call_save(self, other_args: List[str]):
@@ -244,23 +270,24 @@ Graphs:
other_args.insert(0, "-n")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
if (
".csv" in ns_parser.name
or ".xlsx" in ns_parser.name
or ".json" in ns_parser.name
):
portfolio_model.save_df(self.portfolio, ns_parser.name)
else:
print(
"Please submit as 'filename.filetype' with filetype being csv, xlsx, or json\n"
)
if (
".csv" not in ns_parser.name
and ".xlsx" not in ns_parser.name
and ".json" not in ns_parser.name
):
print(
"Please submit as 'filename.filetype' with filetype being csv, xlsx, or json\n"
)
portfolio_model.save_df(self.portfolio, ns_parser.name)
return self.queue
def call_show(self, _):
"""Process show command"""
portfolio_view.show_df(self.portfolio, False)
return self.queue
def call_add(self, other_args: List[str]):
"""Process add command"""
@@ -318,13 +345,7 @@ Graphs:
type=check_positive_float,
help="Fees paid for transaction",
)
# parser.add_argument(
# "-r",
# "--premium",
# dest="premium",
# type=check_positive_float,
# help="Premium paid/received for the option",
# )
parser.add_argument(
"-a",
"--action",
@@ -369,6 +390,7 @@ Graphs:
self.portfolio = self.portfolio.append([data])
self.portfolio.index = list(range(0, len(self.portfolio.values)))
print(f"{ns_parser.name.upper()} successfully added\n")
return self.queue
def call_rmv(self, _):
"""Process rmv command"""
@@ -381,6 +403,7 @@ Graphs:
print(
f"Invalid index please use an integer between 0 and {len(self.portfolio.index)-1}\n"
)
return self.queue
@try_except
def call_ar(self, other_args: List[str]):
@@ -399,18 +422,19 @@ Graphs:
default="SPY",
help="Choose a ticker to be the market asset",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if self.portfolio.empty:
print("Please add items to the portfolio\n")
return
val, hist = portfolio_model.convert_df(self.portfolio)
if not val.empty:
portfolio_view.Report(val, hist, ns_parser.market, 365).generate_report()
if ns_parser:
if not self.portfolio.empty:
val, hist = portfolio_model.convert_df(self.portfolio)
if not val.empty:
portfolio_view.Report(
val, hist, ns_parser.market, 365
).generate_report()
else:
print("Cannot generate a graph from an empty dataframe\n")
else:
print("Please add items to the portfolio\n")
return self.queue
@try_except
def call_rmr(self, other_args: List[str]):
@@ -429,53 +453,93 @@ Graphs:
default="SPY",
help="Choose a ticker to be the market asset",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
if self.portfolio.empty:
val, _ = portfolio_model.convert_df(self.portfolio)
if not val.empty:
df_m = yfinance_model.get_market(val.index[0], ns_parser.market)
returns, _ = portfolio_model.get_return(val, df_m, 365)
portfolio_view.plot_overall_return(returns, ns_parser.market, True)
else:
print("Cannot generate a graph from an empty dataframe\n")
else:
print("Please add items to the portfolio\n")
if self.portfolio.empty:
print("Please add items to the portfolio\n")
return
val, _ = portfolio_model.convert_df(self.portfolio)
if not val.empty:
df_m = yfinance_model.get_market(val.index[0], ns_parser.market)
returns, _ = portfolio_model.get_return(val, df_m, 365)
portfolio_view.plot_overall_return(returns, ns_parser.market, True)
else:
print("Cannot generate a graph from an empty dataframe\n")
return self.queue
def menu():
def menu(queue: List[str] = None):
"""Portfolio Menu"""
portfolio_controller = PortfolioController()
portfolio_controller.call_help(None)
while True:
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in portfolio_controller.CHOICES}
)
port_controller = PortfolioController(queue)
an_input = "HELP_ME"
an_input = session.prompt(
f"{get_flair()} (portfolio)> ",
completer=completer,
)
while True:
# There is a command in the queue
if port_controller.queue and len(port_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if port_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(port_controller.queue) > 1:
return port_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = port_controller.queue[0]
port_controller.queue = port_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 port_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /portfolio/ $ {an_input}")
# Get input command from user
else:
an_input = input(f"{get_flair()} (portfolio)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
port_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and port_controller.completer:
an_input = session.prompt(
f"{get_flair()} /portfolio/ $ ",
completer=port_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /portfolio/ $ ")
try:
process_input = portfolio_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
port_controller.queue = port_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exit\n")
similar_cmd = difflib.get_close_matches(
an_input, portfolio_controller.CHOICES, n=1, cutoff=0.7
print(
f"\nThe command '{an_input}' doesn't exist on the /portfolio menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
port_controller.CHOICES,
n=1,
cutoff=0.7,
)
if similar_cmd:
print(f"Did you mean '{similar_cmd[0]}'?\n")
continue
if " " in an_input:
candidate_input = (
f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}"
)
if candidate_input == an_input:
an_input = ""
port_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
port_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -2,8 +2,9 @@
__docformat__ = "numpy"
import argparse
from typing import List
import matplotlib.pyplot as plt
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 (
@@ -21,24 +22,24 @@ from gamestonk_terminal.portfolio.portfolio_optimization import (
from gamestonk_terminal.helper_funcs import get_rf
period_choices = [
"1d",
"5d",
"1mo",
"3mo",
"6mo",
"1y",
"2y",
"5y",
"10y",
"ytd",
"max",
]
# pylint: disable=C0302
class PortfolioOptimization:
CHOICES = ["cls", "?", "help", "q", "quit"]
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = [
"select",
"add",
@@ -56,30 +57,146 @@ class PortfolioOptimization:
"yolo",
]
CHOICES += CHOICES_COMMANDS
period_choices = [
"1d",
"5d",
"1mo",
"3mo",
"6mo",
"1y",
"2y",
"5y",
"10y",
"ytd",
"max",
]
yf_info_choices = [
"previousClose",
"regularMarketOpen",
"twoHundredDayAverage",
"trailingAnnualDividendYield",
"payoutRatio",
"volume24Hr",
"regularMarketDayHigh",
"navPrice",
"averageDailyVolume10Day",
"totalAssets",
"regularMarketPreviousClose",
"fiftyDayAverage",
"trailingAnnualDividendRate",
"open",
"toCurrency",
"averageVolume10days",
"expireDate",
"yield",
"algorithm",
"dividendRate",
"exDividendDate",
"beta",
"circulatingSupply",
"regularMarketDayLow",
"priceHint",
"currency",
"trailingPE",
"regularMarketVolume",
"lastMarket",
"maxSupply",
"openInterest",
"marketCap",
"volumeAllCurrencies",
"strikePrice",
"averageVolume",
"priceToSalesTrailing12Months",
"dayLow",
"ask",
"ytdReturn",
"askSize",
"volume",
"fiftyTwoWeekHigh",
"forwardPE",
"fromCurrency",
"fiveYearAvgDividendYield",
"fiftyTwoWeekLow",
"bid",
"dividendYield",
"bidSize",
"dayHigh",
"annualHoldingsTurnover",
"enterpriseToRevenue",
"beta3Year",
"profitMargins",
"enterpriseToEbitda",
"52WeekChange",
"morningStarRiskRating",
"forwardEps",
"revenueQuarterlyGrowth",
"sharesOutstanding",
"fundInceptionDate",
"annualReportExpenseRatio",
"bookValue",
"sharesShort",
"sharesPercentSharesOut",
"heldPercentInstitutions",
"netIncomeToCommon",
"trailingEps",
"lastDividendValue",
"SandP52WeekChange",
"priceToBook",
"heldPercentInsiders",
"shortRatio",
"sharesShortPreviousMonthDate",
"floatShares",
"enterpriseValue",
"fundFamily",
"threeYearAverageReturn",
"lastSplitFactor",
"legalType",
"lastDividendDate",
"morningStarOverallRating",
"earningsQuarterlyGrowth",
"pegRatio",
"lastCapGain",
"shortPercentOfFloat",
"sharesShortPriorMonth",
"impliedSharesOutstanding",
"fiveYearAverageReturn",
"regularMarketPrice",
]
# pylint: disable=dangerous-default-value
def __init__(self, tickers: List[str]):
def __init__(self, tickers: List[str], queue: List[str] = None):
"""Construct Portfolio Optimization"""
self.po_parser = argparse.ArgumentParser(add_help=False, prog="po")
self.po_parser.add_argument("cmd", choices=self.CHOICES)
self.tickers = list(set(tickers))
if queue:
self.queue = queue
else:
self.queue = list()
@staticmethod
def print_help(tickers: List[str]):
self.completer: Union[None, NestedCompleter] = None
if session and gtff.USE_PROMPT_TOOLKIT:
choices: dict = {c: {} for c in self.CHOICES}
choices["property"]["-p"] = {c: None for c in self.yf_info_choices}
choices["property"]["--property"] = {c: None for c in self.yf_info_choices}
for fn in ["maxsharpe", "minvol", "maxquadutil", "effret", "effrisk", "ef"]:
choices[fn]["-p"] = {c: None for c in self.period_choices}
choices[fn]["--period"] = {c: None for c in self.period_choices}
self.completer = NestedCompleter.from_nested_dict(choices)
def print_help(self):
"""Print help"""
help_text = f"""
What would you like to do?
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon program
Portfolio Optimization:
select select list of tickers to be optimized
add add tickers to the list of the tickers to be optimized
rmv remove tickers from the list of the tickers to be optimized
Tickers: {('None', ', '.join(tickers))[bool(tickers)]}
Tickers: {('None', ', '.join(self.tickers))[bool(self.tickers)]}
Optimization:
equal equally weighted
@@ -103,28 +220,40 @@ Mean Variance Optimization:
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.po_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help(self.tickers)
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -132,30 +261,65 @@ Mean Variance Optimization:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help(self.tickers)
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program"""
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "po")
self.queue.insert(0, "portfolio")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "reset", "portfolio", "po"]
def call_select(self, other_args: List[str]):
"""Process select command"""
self.tickers = []
self.add_stocks(other_args)
return self.queue
def call_add(self, other_args: List[str]):
"""Process add command"""
self.add_stocks(other_args)
return self.queue
def call_rmv(self, other_args: List[str]):
"""Process rmv command"""
self.rmv_stocks(other_args)
return self.queue
@try_except
def call_equal(self, other_args: List[str]):
@@ -183,16 +347,15 @@ Mean Variance Optimization:
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return self.queue
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return
optimizer_view.display_equal_weight(
stocks=self.tickers, value=ns_parser.value, pie=ns_parser.pie
)
optimizer_view.display_equal_weight(
stocks=self.tickers, value=ns_parser.value, pie=ns_parser.pie
)
return self.queue
@try_except
def call_mktcap(self, other_args: List[str]):
@@ -219,18 +382,18 @@ Mean Variance Optimization:
help="Display a pie chart for weights",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if len(self.tickers) < 2:
print("Please have at least 2 stocks selected to perform calculations.")
return
if ns_parser:
if len(self.tickers) < 2:
print("Please have at least 2 stocks selected to perform calculations.")
return self.queue
optimizer_view.display_property_weighting(
self.tickers,
s_property="marketCap",
value=ns_parser.value,
pie=ns_parser.pie,
)
optimizer_view.display_property_weighting(
self.tickers,
s_property="marketCap",
value=ns_parser.value,
pie=ns_parser.pie,
)
return self.queue
@try_except
def call_dividend(self, other_args: List[str]):
@@ -257,18 +420,18 @@ Mean Variance Optimization:
help="Display a pie chart for weights",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if len(self.tickers) < 2:
print("Please have at least 2 stocks selected to perform calculations.")
return
if ns_parser:
if len(self.tickers) < 2:
print("Please have at least 2 stocks selected to perform calculations.")
return self.queue
optimizer_view.display_property_weighting(
self.tickers,
s_property="dividendYield",
value=ns_parser.value,
pie=ns_parser.pie,
)
optimizer_view.display_property_weighting(
self.tickers,
s_property="dividendYield",
value=ns_parser.value,
pie=ns_parser.pie,
)
return self.queue
@try_except
def call_property(self, other_args: List[str]):
@@ -285,22 +448,8 @@ Mean Variance Optimization:
required=bool("-h" not in other_args),
type=optimizer_helper.check_valid_property_type,
dest="property",
help="""Property info to weigh. Use one of:
previousClose, regularMarketOpen, twoHundredDayAverage, trailingAnnualDividendYield,
payoutRatio, volume24Hr, regularMarketDayHigh, navPrice, averageDailyVolume10Day, totalAssets,
regularMarketPreviousClose, fiftyDayAverage, trailingAnnualDividendRate, open, toCurrency,
averageVolume10days,expireDate, yield, algorithm, dividendRate, exDividendDate, beta, circulatingSupply,
regularMarketDayLow, priceHint, currency, trailingPE, regularMarketVolume, lastMarket, maxSupply,
openInterest,marketCap, volumeAllCurrencies, strikePrice, averageVolume, priceToSalesTrailing12Months,
dayLow, ask, ytdReturn,askSize,volume, fiftyTwoWeekHigh, forwardPE, fromCurrency, fiveYearAvgDividendYield,
fiftyTwoWeekLow, bid,dividendYield,bidSize, dayHigh, annualHoldingsTurnover, enterpriseToRevenue, beta3Year,
profitMargins, enterpriseToEbitda, 52WeekChange, morningStarRiskRating, forwardEps, revenueQuarterlyGrowth,
sharesOutstanding, fundInceptionDate, annualReportExpenseRatio, bookValue, sharesShort, sharesPercentSharesOut
heldPercentInstitutions, netIncomeToCommon, trailingEps, lastDividendValue, SandP52WeekChange, priceToBook,
heldPercentInsiders, shortRatio, sharesShortPreviousMonthDate, floatShares, enterpriseValue,fundFamily,
threeYearAverageReturn, lastSplitFactor, legalType, lastDividendDate, morningStarOverallRating,
earningsQuarterlyGrowth, pegRatio, lastCapGain, shortPercentOfFloat, sharesShortPriorMonth,
impliedSharesOutstanding, fiveYearAverageReturn, and regularMarketPrice.""",
choices=self.yf_info_choices,
help="""Property info to weigh. Use one of yfinance info options.""",
)
parser.add_argument(
"-v",
@@ -318,18 +467,18 @@ Mean Variance Optimization:
help="Display a pie chart for weights",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if len(self.tickers) < 2:
print("Please have at least 2 stocks selected to perform calculations.")
return
if ns_parser:
if len(self.tickers) < 2:
print("Please have at least 2 stocks selected to perform calculations.")
return self.queue
optimizer_view.display_property_weighting(
self.tickers,
s_property=ns_parser.property,
value=ns_parser.value,
pie=ns_parser.pie,
)
optimizer_view.display_property_weighting(
self.tickers,
s_property=ns_parser.property,
value=ns_parser.value,
pie=ns_parser.pie,
)
return self.queue
@try_except
def call_maxsharpe(self, other_args: List[str]):
@@ -346,7 +495,7 @@ Mean Variance Optimization:
default="3mo",
dest="period",
help="period to get yfinance data from",
choices=period_choices,
choices=self.period_choices,
)
parser.add_argument(
"-v",
@@ -374,19 +523,18 @@ Mean Variance Optimization:
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return
optimizer_view.display_max_sharpe(
stocks=self.tickers,
period=ns_parser.period,
value=ns_parser.value,
rfrate=ns_parser.risk_free_rate,
pie=ns_parser.pie,
)
if ns_parser:
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return self.queue
optimizer_view.display_max_sharpe(
stocks=self.tickers,
period=ns_parser.period,
value=ns_parser.value,
rfrate=ns_parser.risk_free_rate,
pie=ns_parser.pie,
)
return self.queue
@try_except
def call_minvol(self, other_args: List[str]):
@@ -403,7 +551,7 @@ Mean Variance Optimization:
default="3mo",
dest="period",
help="period to get yfinance data from",
choices=period_choices,
choices=self.period_choices,
)
parser.add_argument(
"-v",
@@ -422,19 +570,18 @@ Mean Variance Optimization:
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return self.queue
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return
optimizer_view.display_min_volatility(
stocks=self.tickers,
period=ns_parser.period,
value=ns_parser.value,
pie=ns_parser.pie,
)
optimizer_view.display_min_volatility(
stocks=self.tickers,
period=ns_parser.period,
value=ns_parser.value,
pie=ns_parser.pie,
)
return self.queue
@try_except
def call_maxquadutil(self, other_args: List[str]):
@@ -451,7 +598,7 @@ Mean Variance Optimization:
default="3mo",
dest="period",
help="period to get yfinance data from",
choices=period_choices,
choices=self.period_choices,
)
parser.add_argument(
"-v",
@@ -487,25 +634,26 @@ Mean Variance Optimization:
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return self.queue
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return
if ns_parser.pie and ns_parser.market_neutral:
print(
"Cannot show pie chart for market neutral due to negative weights."
)
return self.queue
if ns_parser.pie and ns_parser.market_neutral:
print("Cannot show pie chart for market neutral due to negative weights.")
return
optimizer_view.display_max_quadratic_utility(
stocks=self.tickers,
period=ns_parser.period,
value=ns_parser.value,
risk_aversion=ns_parser.risk_aversion,
market_neutral=ns_parser.market_neutral,
pie=ns_parser.pie,
)
optimizer_view.display_max_quadratic_utility(
stocks=self.tickers,
period=ns_parser.period,
value=ns_parser.value,
risk_aversion=ns_parser.risk_aversion,
market_neutral=ns_parser.market_neutral,
pie=ns_parser.pie,
)
return self.queue
@try_except
def call_effrisk(self, other_args: List[str]):
@@ -523,7 +671,7 @@ Mean Variance Optimization:
default="3mo",
dest="period",
help="period to get yfinance data from",
choices=period_choices,
choices=self.period_choices,
)
parser.add_argument(
"-v",
@@ -551,7 +699,7 @@ Mean Variance Optimization:
help="Display a pie chart for weights. Only if neutral flag is left False.",
)
if other_args and "-" not in other_args[0]:
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-t")
parser.add_argument(
"-t",
@@ -563,25 +711,26 @@ Mean Variance Optimization:
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return self.queue
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return
if ns_parser.pie and ns_parser.market_neutral:
print(
"Cannot show pie chart for market neutral due to negative weights."
)
return self.queue
if ns_parser.pie and ns_parser.market_neutral:
print("Cannot show pie chart for market neutral due to negative weights.")
return
optimizer_view.display_efficient_risk(
stocks=self.tickers,
period=ns_parser.period,
value=ns_parser.value,
target_volatility=ns_parser.target_volatility,
market_neutral=ns_parser.market_neutral,
pie=ns_parser.pie,
)
optimizer_view.display_efficient_risk(
stocks=self.tickers,
period=ns_parser.period,
value=ns_parser.value,
target_volatility=ns_parser.target_volatility,
market_neutral=ns_parser.market_neutral,
pie=ns_parser.pie,
)
return self.queue
@try_except
def call_effret(self, other_args: List[str]):
@@ -598,7 +747,7 @@ Mean Variance Optimization:
default="3mo",
dest="period",
help="period to get yfinance data from",
choices=period_choices,
choices=self.period_choices,
)
parser.add_argument(
"-v",
@@ -625,7 +774,7 @@ Mean Variance Optimization:
help="Display a pie chart for weights. Only if neutral flag is left False.",
)
if other_args and "-" not in other_args[0]:
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-t")
parser.add_argument(
@@ -637,25 +786,26 @@ Mean Variance Optimization:
help="the desired return of the resulting portfolio",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return self.queue
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return
if ns_parser.pie and ns_parser.market_neutral:
print(
"Cannot show pie chart for market neutral due to negative weights."
)
return self.queue
if ns_parser.pie and ns_parser.market_neutral:
print("Cannot show pie chart for market neutral due to negative weights.")
return
optimizer_view.display_efficient_return(
stocks=self.tickers,
period=ns_parser.period,
value=ns_parser.value,
target_return=ns_parser.target_return,
market_neutral=ns_parser.market_neutral,
pie=ns_parser.pie,
)
optimizer_view.display_efficient_return(
stocks=self.tickers,
period=ns_parser.period,
value=ns_parser.value,
target_return=ns_parser.target_return,
market_neutral=ns_parser.market_neutral,
pie=ns_parser.pie,
)
return self.queue
@try_except
def call_ef(self, other_args):
@@ -673,9 +823,9 @@ Mean Variance Optimization:
default="3mo",
dest="period",
help="period to get yfinance data from",
choices=period_choices,
choices=self.period_choices,
)
if other_args and "-" not in other_args[0]:
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-n")
parser.add_argument(
"-n",
@@ -695,24 +845,24 @@ Mean Variance Optimization:
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return self.queue
if len(self.tickers) < 2:
print("Please have at least 2 loaded tickers to calculate weights.\n")
return
optimizer_view.display_ef(
stocks=self.tickers,
period=ns_parser.period,
n_portfolios=ns_parser.n_port,
risk_free=ns_parser.risk_free,
)
optimizer_view.display_ef(
stocks=self.tickers,
period=ns_parser.period,
n_portfolios=ns_parser.n_port,
risk_free=ns_parser.risk_free,
)
return self.queue
def call_yolo(self, _):
# Easter egg :)
print("DFV YOLO")
print("GME: ALL", "\n")
return self.queue
@try_except
def add_stocks(self, other_args: List[str]):
@@ -735,17 +885,18 @@ Mean Variance Optimization:
other_args.insert(0, "-t")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
tickers = set(self.tickers)
for ticker in ns_parser.add_tickers:
tickers.add(ticker)
if ns_parser:
tickers = set(self.tickers)
for ticker in ns_parser.add_tickers:
tickers.add(ticker)
if self.tickers:
print(f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}")
if self.tickers:
print(
f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}"
)
self.tickers = list(tickers)
print("")
self.tickers = list(tickers)
print("")
@try_except
def rmv_stocks(self, other_args: List[str]):
@@ -768,47 +919,104 @@ Mean Variance Optimization:
other_args.insert(0, "-t")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
tickers = set(self.tickers)
for ticker in ns_parser.rmv_tickers:
tickers.remove(ticker)
tickers = set(self.tickers)
for ticker in ns_parser.rmv_tickers:
tickers.remove(ticker)
if self.tickers:
print(
f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}"
)
if self.tickers:
print(f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}")
self.tickers = list(tickers)
print("")
self.tickers = list(tickers)
print("")
def menu(tickers: List[str]):
def menu(
tickers: List[str],
queue: List[str] = None,
from_submenu: bool = False,
):
"""Portfolio Optimization Menu"""
plt.close("all")
po_controller = PortfolioOptimization(tickers)
po_controller.call_help(tickers)
po_controller = PortfolioOptimization(tickers, queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if po_controller.queue and len(po_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if po_controller.queue[0] in ("q", "..", "quit"):
print("")
# Since we came from another menu we need to quit an additional time
if from_submenu:
# Skip quit
po_controller.queue = po_controller.queue[1:]
po_controller.queue.insert(0, "portfolio")
po_controller.queue.insert(0, "home")
from_submenu = False
return po_controller.queue
if len(po_controller.queue) > 1:
return po_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = po_controller.queue[0]
po_controller.queue = po_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 po_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /portfolio/po/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in po_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (portfolio)>(po)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (portfolio)>(po)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
po_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and po_controller.completer:
an_input = session.prompt(
f"{get_flair()} /portfolio/po/ $ ",
completer=po_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /portfolio/po/ $ ")
try:
plt.close("all")
process_input = po_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
po_controller.queue = po_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
continue
print(
f"\nThe command '{an_input}' doesn't exist on the /portfolio/po menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
po_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:])}"
)
if candidate_input == an_input:
an_input = ""
po_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
po_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -145,7 +145,7 @@
"source": [
"from gamestonk_terminal.stocks.fundamental_analysis import av_view\n",
"\n",
"av_view.display_income_statement(ticker, number=1)"
"av_view.display_income_statement(ticker, limit=1)"
]
},
{
@@ -165,7 +165,7 @@
"source": [
"from gamestonk_terminal.stocks.fundamental_analysis import av_view\n",
"\n",
"av_view.display_income_statement(ticker, number = 1, quarterly=True)"
"av_view.display_income_statement(ticker, limit=1, quarterly=True)"
]
},
{
@@ -184,7 +184,7 @@
"outputs": [],
"source": [
"from gamestonk_terminal.stocks.fundamental_analysis import av_view\n",
"av_view.display_balance_sheet(ticker, number = 1)"
"av_view.display_balance_sheet(ticker, limit=1)"
]
},
{
@@ -203,7 +203,7 @@
"outputs": [],
"source": [
"from gamestonk_terminal.stocks.fundamental_analysis import av_view\n",
"av_view.display_cash_flow(ticker,number=1)"
"av_view.display_cash_flow(ticker, limit=1)"
]
},
{

View File

@@ -1,12 +1,11 @@
"""Papermill Controller Module"""
"""Reports Controller Module."""
__docformat__ = "numpy"
# pylint: disable=R1732
import argparse
import difflib
import os
import subprocess
from typing import List, Union
import webbrowser
from datetime import datetime
from ast import literal_eval
@@ -16,23 +15,22 @@ import papermill as pm
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.menu import session
from gamestonk_terminal.helper_funcs import get_flair, system_clear
from gamestonk_terminal import config_terminal
from gamestonk_terminal.helper_funcs import (
MENU_GO_BACK,
MENU_QUIT,
MENU_RESET,
)
class ReportController:
"""Report Controller class"""
"""Report Controller class."""
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
@@ -64,7 +62,7 @@ class ReportController:
notebook_content = open(notebook_file + ".ipynb").read()
metadata_cell = """"metadata": {\n "tags": [\n "parameters"\n ]\n },\n "outputs":"""
notebook_metadata_content = notebook_content[
notebook_content.find(metadata_cell) :
notebook_content.find(metadata_cell) : # noqa: E203
]
cell_start = 'source": '
cell_end = '"report_name='
@@ -72,7 +70,9 @@ class ReportController:
notebook_metadata_content[
notebook_metadata_content.find(
cell_start
) : notebook_metadata_content.find(cell_end)
) : notebook_metadata_content.find( # noqa: E203
cell_end
)
]
+ "]"
)
@@ -85,83 +85,92 @@ class ReportController:
d_params[report_to_run] = l_params
args = f"<{'> <'.join(l_params)}>"
reports_opts += f" {k}. {report_to_run}{(max_len_name-len(report_to_run))*' '} {args if args != '<>' else ''}\n"
reports_opts += (
f" {k}. {report_to_run}"
+ f"{(max_len_name-len(report_to_run))*' '} "
+ f"{args if args != '<>' else ''}\n"
)
def __init__(self):
"""Constructor"""
def __init__(self, queue: List[str] = None):
"""Construct the Reports Controller."""
self.report_parser = argparse.ArgumentParser(add_help=False, prog="reports")
self.report_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"""
"""Print help."""
help_text = f"""
What do you want to do?
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon the program
reset reset terminal and reload configs
Select one of the following reports:
{self.reports_opts}"""
print(help_text)
def switch(self, an_input: str, proc: subprocess.Popen):
"""Process and dispatch input
def switch(self, an_input: str):
"""Process and dispatch input.
Parameters
-------
----------
an_input : str
string with input arguments
proc : subprocess.Popen
subprocess that calls jupyter notebook for report generation
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.report_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd in ["?", "help"]:
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# Go back to menu above
if known_args.cmd == "q":
proc.kill()
print("")
return MENU_GO_BACK
# Quit terminal
if known_args.cmd == "quit":
proc.kill()
print("")
return MENU_QUIT
# Reset menu
if known_args.cmd == "reset":
print("")
return MENU_RESET
# 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"
if known_args.cmd in ["quit", "help", "reset", "home", "exit"]:
return getattr(
self,
"call_" + known_args.cmd,
lambda _: "Command not recognized!",
)(other_args)
# Execute the requested report
if known_args.cmd in self.d_id_to_report_name:
report_to_run = self.d_id_to_report_name[known_args.cmd]
else:
@@ -169,9 +178,10 @@ Select one of the following reports:
params = self.d_params[report_to_run]
# Check that the number of arguments match. We can't check validity of argument used because
# this depends on what the user will use it for in the notebook. This is a downside of allowing
# the user to have this much flexibility.
# Check that the number of arguments match. We can't check validity of the
# argument used because this depends on what the user will use it for in
# the notebook. This is a downside of allowing the user to have this much
# flexibility.
if len(other_args) != len(params):
print("Wrong number of arguments provided!")
if len(params):
@@ -181,17 +191,22 @@ Select one of the following reports:
else:
print("No argument required.")
print("")
return None
return self.queue
notebook_template = os.path.join(
"gamestonk_terminal", "reports", report_to_run
)
args_to_output = f"_{'_'.join(other_args)}" if "_".join(other_args) else ""
report_output_name = (
f"{datetime.now().strftime('%Y%m%d_%H%M%S')}"
+ "_"
+ f"{report_to_run}{args_to_output}"
)
notebook_output = os.path.join(
"gamestonk_terminal",
"reports",
"stored",
f"{datetime.now().strftime('%Y%m%d_%H%M%S')}_{report_to_run}{args_to_output}",
report_output_name,
)
d_report_params = {}
@@ -207,22 +222,10 @@ Select one of the following reports:
)
if gtff.OPEN_REPORT_AS_HTML:
webbrowser.open(
os.path.join(
f"http://localhost:{config_terminal.PAPERMILL_NOTEBOOK_REPORT_PORT}",
"view",
notebook_output + ".html",
)
)
else:
webbrowser.open(
os.path.join(
f"http://localhost:{config_terminal.PAPERMILL_NOTEBOOK_REPORT_PORT}",
"notebooks",
notebook_output + ".ipynb",
)
report_output_path = os.path.join(
os.path.dirname(__file__), "..", "..", f"{notebook_output}.html"
)
webbrowser.open(f"file://{report_output_path}")
print("")
print(
@@ -232,51 +235,123 @@ Select one of the following reports:
),
"\n",
)
return self.queue
return self.queue
return None
def call_home(self, _):
"""Process home command."""
self.queue.insert(0, "quit")
return self.queue
def call_cls(self, _):
"""Process cls command."""
system_clear()
return self.queue
def call_help(self, _):
"""Process help command."""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process quit menu command."""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "q")
return self.queue
return ["q"]
def call_exit(self, _):
"""Process exit terminal command."""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "q")
self.queue.insert(0, "q")
return self.queue
return ["q", "q"]
def call_reset(self, _):
"""Process reset command."""
if len(self.queue) > 0:
self.queue.insert(0, "reports")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "reset", "reports"]
def menu():
"""Report Menu"""
report_controller = ReportController()
report_controller.print_help()
# Initialize jupyter notebook
cmd = f"jupyter notebook --port={config_terminal.PAPERMILL_NOTEBOOK_REPORT_PORT}"
proc = subprocess.Popen(
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT
)
print(
f"Jupyter notebook environment launched on http://localhost:{config_terminal.PAPERMILL_NOTEBOOK_REPORT_PORT}\n"
)
def menu(queue: List[str] = None):
"""Report Menu."""
report_controller = ReportController(queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if report_controller.queue and len(report_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if report_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(report_controller.queue) > 1:
return report_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = report_controller.queue[0]
report_controller.queue = report_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 report_controller.CHOICES:
print(f"{get_flair()} /reports/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in report_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (reports)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (reports)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
report_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and report_controller.completer:
an_input = session.prompt(
f"{get_flair()} /reports/ $ ",
completer=report_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /reports/ $ ")
try:
process_input = report_controller.switch(an_input, proc)
if process_input is not None:
return process_input
# Process the input command
report_controller.queue = report_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
similar_cmd = difflib.get_close_matches(
an_input, report_controller.CHOICES, n=1, cutoff=0.7
print(
f"\nThe command '{an_input}' doesn't exist on the /reports menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
report_controller.CHOICES,
n=1,
cutoff=0.7,
)
if similar_cmd:
print(f"Did you mean '{similar_cmd[0]}'?\n")
continue
if " " in an_input:
candidate_input = (
f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}"
)
if candidate_input == an_input:
an_input = ""
report_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
report_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -2,7 +2,9 @@
__docformat__ = "numpy"
import argparse
import difflib
import webbrowser
from typing import List, Union
from prompt_toolkit.completion import NestedCompleter
@@ -10,9 +12,6 @@ from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import (
get_flair,
system_clear,
MENU_GO_BACK,
MENU_QUIT,
MENU_RESET,
)
from gamestonk_terminal.menu import session
@@ -23,10 +22,15 @@ class ResourceCollectionController:
# Command choices
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
@@ -42,25 +46,26 @@ class ResourceCollectionController:
CHOICES += CHOICES_COMMANDS
def __init__(self):
def __init__(self, queue: List[str] = None):
"""Constructor"""
self.rc_parser = argparse.ArgumentParser(add_help=False, prog="resources")
self.rc_parser.add_argument(
"cmd",
choices=self.CHOICES,
)
self.queue = queue if queue else list()
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)
@staticmethod
def print_help():
"""Print help"""
help_str = """
What do you want to do?
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon program
reset reset terminal and reload configs
Resource Collection:
hfletters hedge fund letters or reports
arxiv open-access archive for academic articles
finra self-regulatory organization
@@ -76,31 +81,40 @@ What do you want to do?
Returns
-------
MENU_GO_BACK, MENU_QUIT, MENU_RESET
MENU_GO_BACK - Show main context menu again
MENU_QUIT - Quit terminal
MENU_RESET - Reset terminal and go back to same previous menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.rc_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
if other_args:
print(f"The following args were unexpected: {other_args}")
# 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"
return getattr(
self,
@@ -108,83 +122,160 @@ What do you want to do?
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return MENU_GO_BACK
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - exit the program"""
return MENU_QUIT
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit"]
def call_reset(self, _):
"""Process Reset command - reset the program"""
return MENU_RESET
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "resources")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "reset", "resources"]
def call_hfletters(self, _):
"""Process hfletters command"""
webbrowser.open("https://miltonfmr.com/hedge-fund-letters/")
print("")
return self.queue
def call_arxiv(self, _):
"""Process arxiv command"""
webbrowser.open("https://arxiv.org")
print("")
return self.queue
def call_finra(self, _):
"""Process finra command"""
webbrowser.open("https://www.finra.org/#/")
print("")
return self.queue
def call_edgar(self, _):
"""Process edgar command"""
webbrowser.open("https://www.sec.gov/edgar.shtml")
print("")
return self.queue
def call_fred(self, _):
"""Process fred command"""
webbrowser.open("https://fred.stlouisfed.org")
print("")
return self.queue
def call_learn(self, _):
"""Process learn command"""
webbrowser.open("https://moongangcapital.com/free-stock-market-resources/")
print("")
return self.queue
def call_econiverse(self, _):
"""Process econiverse command"""
webbrowser.open("https://econiverse.github.io")
print("")
return self.queue
def menu():
def menu(queue: List[str] = None):
"""Resource Collection Menu"""
rc_controller = ResourceCollectionController()
rc_controller.call_help(None)
rc_controller = ResourceCollectionController(queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if rc_controller.queue and len(rc_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if rc_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(rc_controller.queue) > 1:
return rc_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = rc_controller.queue[0]
rc_controller.queue = rc_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 rc_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /resources/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in rc_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (resources)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (resources)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
rc_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and rc_controller.completer:
an_input = session.prompt(
f"{get_flair()} /resources/ $ ",
completer=rc_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /resources/ $ ")
try:
process_input = rc_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
rc_controller.queue = rc_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
continue
print(
f"\nThe command '{an_input}' doesn't exist on the /resources menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
rc_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:])}"
)
if candidate_input == an_input:
an_input = ""
rc_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
rc_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -3,10 +3,9 @@ __docformat__ = "numpy"
import argparse
import difflib
from typing import List
from typing import List, Union
import matplotlib as mpl
import matplotlib.pyplot as plt
import pandas as pd
from prompt_toolkit.completion import NestedCompleter
@@ -16,10 +15,10 @@ from gamestonk_terminal.helper_funcs import (
get_flair,
parse_known_args_and_warn,
try_except,
EXPORT_ONLY_RAW_DATA_ALLOWED,
system_clear,
)
from gamestonk_terminal.menu import session
from gamestonk_terminal.stocks import stocks_helper
# This code below aims to fix an issue with the fnn module, used by bt module
# which forces matplotlib backend to be 'agg' which doesn't allow to plot
@@ -35,11 +34,23 @@ mpl.use(default_backend)
class BacktestingController:
"""Backtesting Class"""
CHOICES = ["?", "cls", "help", "q", "quit", "load"]
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = ["ema", "ema_cross", "rsi"]
CHOICES += CHOICES_COMMANDS
def __init__(self, ticker: str, stock: pd.DataFrame):
def __init__(self, ticker: str, stock: pd.DataFrame, queue: List[str] = None):
self.ticker = ticker
self.stock = stock
self.bt_parser = argparse.ArgumentParser(add_help=False, prog="bt")
@@ -47,17 +58,18 @@ class BacktestingController:
"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)
self.queue = queue if queue else list()
def print_help(self):
"""Print help"""
help_text = f"""
Backtesting:
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon program
load load new ticker to analyze
Ticker: {self.ticker.upper()}
ema buy when price exceeds EMA(l)
@@ -71,28 +83,40 @@ Ticker: {self.ticker.upper()}
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.bt_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -100,54 +124,56 @@ Ticker: {self.ticker.upper()}
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program"""
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["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",
)
# 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")
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit"]
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "bt")
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")
return self.queue
df_stock_candidate = stocks_helper.load(
ns_parser.ticker,
)
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()
reset_commands = ["quit", "quit", "reset", "stocks"]
if self.ticker:
reset_commands.append(f"load {self.ticker}")
reset_commands.append("bt")
return reset_commands
@try_except
def call_ema(self, other_args: List[str]):
@@ -179,27 +205,20 @@ Ticker: {self.ticker.upper()}
help="Flag to not show buy and hold comparison",
dest="no_bench",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
bt_view.display_simple_ema(
ticker=self.ticker,
df_stock=self.stock,
ema_length=ns_parser.length,
spy_bt=ns_parser.spy,
no_bench=ns_parser.no_bench,
export=ns_parser.export,
)
bt_view.display_simple_ema(
ticker=self.ticker,
df_stock=self.stock,
ema_length=ns_parser.length,
spy_bt=ns_parser.spy,
no_bench=ns_parser.no_bench,
export=ns_parser.export,
)
return self.queue
@try_except
def call_ema_cross(self, other_args: List[str]):
@@ -247,33 +266,27 @@ Ticker: {self.ticker.upper()}
dest="shortable",
help="Flag that disables the short sell",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser.long < ns_parser.short:
print("Short EMA period is longer than Long EMA period\n")
return self.queue
if ns_parser.long < ns_parser.short:
print("Short EMA period is longer than Long EMA period\n")
return
bt_view.display_ema_cross(
ticker=self.ticker,
df_stock=self.stock,
short_ema=ns_parser.short,
long_ema=ns_parser.long,
spy_bt=ns_parser.spy,
no_bench=ns_parser.no_bench,
shortable=ns_parser.shortable,
export=ns_parser.export,
)
bt_view.display_ema_cross(
ticker=self.ticker,
df_stock=self.stock,
short_ema=ns_parser.short,
long_ema=ns_parser.long,
spy_bt=ns_parser.spy,
no_bench=ns_parser.no_bench,
shortable=ns_parser.shortable,
export=ns_parser.export,
)
return self.queue
@try_except
def call_rsi(self, other_args: List[str]):
@@ -330,69 +343,99 @@ Ticker: {self.ticker.upper()}
dest="shortable",
help="Flag that disables the short sell",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
if ns_parser.high < ns_parser.low:
print("Low RSI value is higher than Low RSI value\n")
return self.queue
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser.high < ns_parser.low:
print("Low RSI value is higher than Low RSI value\n")
return
bt_view.display_rsi_strategy(
ticker=self.ticker,
df_stock=self.stock,
periods=ns_parser.periods,
low_rsi=ns_parser.low,
high_rsi=ns_parser.high,
spy_bt=ns_parser.spy,
no_bench=ns_parser.no_bench,
shortable=ns_parser.shortable,
export=ns_parser.export,
)
bt_view.display_rsi_strategy(
ticker=self.ticker,
df_stock=self.stock,
periods=ns_parser.periods,
low_rsi=ns_parser.low,
high_rsi=ns_parser.high,
spy_bt=ns_parser.spy,
no_bench=ns_parser.no_bench,
shortable=ns_parser.shortable,
export=ns_parser.export,
)
return self.queue
def menu(ticker: str, stock: pd.DataFrame):
def menu(ticker: str, stock: pd.DataFrame, queue: List[str] = None):
"""Backtesting Menu"""
plt.close("all")
bt_controller = BacktestingController(ticker, stock)
bt_controller.call_help(None)
bt_controller = BacktestingController(ticker, stock, queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if bt_controller.queue and len(bt_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if bt_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(bt_controller.queue) > 1:
return bt_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = bt_controller.queue[0]
bt_controller.queue = bt_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 bt_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /stocks/bt/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in bt_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (stocks)>(bt)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (stocks)>(bt)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
bt_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and bt_controller.completer:
an_input = session.prompt(
f"{get_flair()} /stocks/bt/ $ ",
completer=bt_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /stocks/bt/ $ ")
try:
plt.close("all")
process_input = bt_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
bt_controller.queue = bt_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
similar_cmd = difflib.get_close_matches(
an_input, bt_controller.CHOICES, n=1, cutoff=0.7
print(
f"\nThe command '{an_input}' doesn't exist on the /stocks/bt menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
bt_controller.CHOICES,
n=1,
cutoff=0.7,
)
if similar_cmd:
print(f"Did you mean '{similar_cmd[0]}'?\n")
if " " in an_input:
candidate_input = (
f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}"
)
if candidate_input == an_input:
an_input = ""
bt_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
continue
print(f" Replacing by '{an_input}'.")
bt_controller.queue.insert(0, an_input)
else:
print("\n")

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@ import pandas as pd
import seaborn as sns
from pandas.plotting import register_matplotlib_converters
from sklearn.preprocessing import MinMaxScaler
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.config_plot import PLOT_DPI
from gamestonk_terminal.helper_funcs import export_data, plot_autoscale
@@ -76,6 +77,8 @@ def display_historical(
ax.set_xlim([df_similar.index[0], df_similar.index[-1]])
plt.gcf().autofmt_xdate()
fig.tight_layout()
if gtff.USE_ION:
plt.ion()
plt.show()
export_data(
export, os.path.dirname(os.path.abspath(__file__)), "historical", df_similar
@@ -96,8 +99,6 @@ def display_volume(
List of similar tickers
start : str, optional
Start date of comparison, by default 1 year ago
normalize : bool, optional
Boolean to normalize all stock prices using MinMax defaults True
export : str, optional
Format to export historical prices, by default ""
"""

View File

@@ -1,16 +1,14 @@
""" Dark Pool Shorts Controller """
""" Dark Pool and Shorts Controller Module """
__docformat__ = "numpy"
import argparse
import difflib
from typing import List
from typing import List, Union
from datetime import datetime, timedelta
from colorama import Style
import pandas as pd
from matplotlib import pyplot as plt
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import EXPORT_BOTH_RAW_DATA_AND_FIGURES, get_flair
from gamestonk_terminal.menu import session
from gamestonk_terminal.helper_funcs import (
parse_known_args_and_warn,
@@ -19,6 +17,9 @@ from gamestonk_terminal.helper_funcs import (
check_int_range,
try_except,
system_clear,
get_flair,
EXPORT_BOTH_RAW_DATA_AND_FIGURES,
EXPORT_ONLY_RAW_DATA_ALLOWED,
)
from gamestonk_terminal.stocks import stocks_helper
from gamestonk_terminal.stocks.dark_pool_shorts import (
@@ -33,51 +34,64 @@ from gamestonk_terminal.stocks.dark_pool_shorts import (
class DarkPoolShortsController:
"""Dark Pool Shorts Controller"""
"""Dark Pool Shorts Controller class"""
# Command choices
CHOICES = [
"?",
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"load",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = [
"load",
"shorted",
"hsi",
"prom",
"pos",
"sidtc",
"psi",
"dpotc",
"ftd",
"spos",
"volexch",
]
CHOICES_COMMANDS_WITH_TICKER = ["psi", "dpotc", "ftd", "spos", "volexch"]
CHOICES += CHOICES_COMMANDS
CHOICES += CHOICES_COMMANDS_WITH_TICKER
def __init__(self, ticker: str, start: str, stock: pd.DataFrame):
def __init__(
self, ticker: str, start: str, stock: pd.DataFrame, queue: List[str] = None
):
"""Constructor"""
self.ticker = ticker
self.start = start
self.stock = stock
self.disc_parser = argparse.ArgumentParser(add_help=False, prog="dps")
self.disc_parser.add_argument(
self.dps_parser = argparse.ArgumentParser(add_help=False, prog="dps")
self.dps_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()
self.ticker = ticker
self.start = start
self.stock = stock
def print_help(self):
"""Print help"""
help_text = f"""
Dark Pool Shorts:
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon program
load load a specific stock ticker for analysis
Yahoo Finance:
@@ -110,27 +124,40 @@ NYSE:
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
return self.queue
(known_args, other_args) = self.disc_parser.parse_known_args(an_input.split())
# Navigation slash is being used
if "/" in an_input:
actions = an_input.split("/")
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Absolute path is specified
if not actions[0]:
an_input = "home"
# Relative path so execute first instruction
else:
an_input = actions[0]
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# Add all instructions to the queue
for cmd in actions[1:][::-1]:
if cmd:
self.queue.insert(0, cmd)
(known_args, other_args) = self.dps_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"
return getattr(
self,
@@ -138,18 +165,56 @@ NYSE:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program"""
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
if self.ticker:
self.queue.insert(0, f"load {self.ticker}")
self.queue.insert(0, "dps")
self.queue.insert(0, "stocks")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
reset_commands = ["quit", "quit", "reset", "stocks", "dps"]
if self.ticker:
reset_commands.append(f"load {self.ticker}")
return reset_commands
@try_except
def call_load(self, other_args: List[str]):
"""Process load command"""
parser = argparse.ArgumentParser(
@@ -176,26 +241,24 @@ NYSE:
dest="start",
help="The starting date (format YYYY-MM-DD) of the stock",
)
# For the case where a user uses: 'load BB'
if other_args and "-t" not in other_args and "-h" not in other_args:
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 not ns_parser:
return
if ns_parser:
df_stock_candidate = stocks_helper.load(
ns_parser.ticker,
ns_parser.start,
)
df_stock_candidate = stocks_helper.load(
ns_parser.ticker,
ns_parser.start,
)
if not df_stock_candidate.empty:
self.stock = df_stock_candidate
self.start = ns_parser.start
if "." in ns_parser.ticker:
self.ticker = ns_parser.ticker.upper().split(".")[0]
else:
self.ticker = ns_parser.ticker.upper()
if not df_stock_candidate.empty:
self.stock = df_stock_candidate
self.start = ns_parser.start
if "." in ns_parser.ticker:
self.ticker = ns_parser.ticker.upper().split(".")[0]
else:
self.ticker = ns_parser.ticker.upper()
return self.queue
@try_except
def call_shorted(self, other_args: List[str]):
@@ -207,34 +270,26 @@ NYSE:
description="Print up to 25 top ticker most shorted. [Source: Yahoo Finance]",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
action="store",
dest="num",
dest="limit",
type=check_int_range(1, 25),
default=5,
help="Number of the most shorted stocks to retrieve.",
default=10,
help="Limit of the most shorted stocks to retrieve.",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
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_ONLY_RAW_DATA_ALLOWED
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-n")
if ns_parser:
yahoofinance_view.display_most_shorted(
num_stocks=ns_parser.limit,
export=ns_parser.export,
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
yahoofinance_view.display_most_shorted(
num_stocks=ns_parser.num,
export=ns_parser.export,
)
return self.queue
@try_except
def call_hsi(self, other_args: List[str]):
@@ -252,30 +307,24 @@ NYSE:
""",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
action="store",
dest="n_num",
type=check_positive,
dest="limit",
type=check_int_range(1, 25),
default=10,
help="Number of top stocks to print.",
help="Limit of the top heavily shorted stocks to retrieve.",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
shortinterest_view.high_short_interest(
num=ns_parser.limit,
export=ns_parser.export,
)
shortinterest_view.high_short_interest(
num=ns_parser.n_num,
export=ns_parser.export,
)
return self.queue
@try_except
def call_prom(self, other_args: List[str]):
@@ -284,7 +333,7 @@ NYSE:
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="prom",
description="Display dark pool (ATS) data of tickers with growing trades activity",
description="Display dark pool (ATS) data of tickers with growing trades activity using linear regression.",
)
parser.add_argument(
"-n",
@@ -296,15 +345,16 @@ NYSE:
help="Number of tickers to filter from entire ATS data based on the sum of the total weekly shares quantity.",
)
parser.add_argument(
"-t",
"--top",
"-l",
"--limit",
action="store",
dest="n_top",
dest="limit",
type=check_positive,
default=5,
help="List of tickers from most promising with better linear regression slope.",
default=10,
help="Limit of most promising tickers to display.",
)
parser.add_argument(
"-t",
"--tier",
action="store",
dest="tier",
@@ -313,23 +363,20 @@ NYSE:
default="",
help="Tier to process data from.",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
dest="export",
help="Export dataframe data to csv,json,xlsx file",
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_ONLY_RAW_DATA_ALLOWED
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
finra_view.darkpool_otc(
num=ns_parser.n_num,
promising=ns_parser.limit,
tier=ns_parser.tier,
export=ns_parser.export,
)
finra_view.darkpool_otc(
num=ns_parser.n_num,
promising=ns_parser.n_top,
tier=ns_parser.tier,
export=ns_parser.export,
)
return self.queue
@try_except
def call_pos(self, other_args: List[str]):
@@ -341,12 +388,13 @@ NYSE:
description="Get dark pool short positions. [Source: Stockgrid]",
)
parser.add_argument(
"-n",
"--number",
help="Number of top tickers to show",
"-l",
"--limit",
action="store",
dest="limit",
type=check_positive,
default=10,
dest="num",
help="Limit of tickers to display.",
)
parser.add_argument(
"-s",
@@ -367,23 +415,18 @@ NYSE:
dest="ascending",
help="Data in ascending order",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
stockgrid_view.dark_pool_short_positions(
num=ns_parser.limit,
sort_field=ns_parser.sort_field,
ascending=ns_parser.ascending,
export=ns_parser.export,
)
stockgrid_view.dark_pool_short_positions(
num=ns_parser.num,
sort_field=ns_parser.sort_field,
ascending=ns_parser.ascending,
export=ns_parser.export,
)
return self.queue
@try_except
def call_sidtc(self, other_args: List[str]):
@@ -395,12 +438,13 @@ NYSE:
description="Print short interest and days to cover. [Source: Stockgrid]",
)
parser.add_argument(
"-n",
"--number",
help="Number of top tickers to show",
"-l",
"--limit",
action="store",
dest="limit",
type=check_positive,
default=10,
dest="num",
help="Limit of tickers to display.",
)
parser.add_argument(
"-s",
@@ -411,22 +455,17 @@ NYSE:
default="float",
dest="sort_field",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
stockgrid_view.short_interest_days_to_cover(
num=ns_parser.limit,
sort_field=ns_parser.sort_field,
export=ns_parser.export,
)
stockgrid_view.short_interest_days_to_cover(
num=ns_parser.num,
sort_field=ns_parser.sort_field,
export=ns_parser.export,
)
return self.queue
@try_except
def call_dpotc(self, other_args: List[str]):
@@ -437,24 +476,19 @@ NYSE:
prog="dpotc",
description="Display barchart of dark pool (ATS) and OTC (Non ATS) data. [Source: FINRA]",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if not self.ticker:
print("No ticker loaded.\n")
return
if ns_parser:
if self.ticker:
finra_view.darkpool_ats_otc(
ticker=self.ticker,
export=ns_parser.export,
)
else:
print("No ticker loaded.\n")
finra_view.darkpool_ats_otc(
ticker=self.ticker,
export=ns_parser.export,
)
return self.queue
@try_except
def call_ftd(self, other_args: List[str]):
@@ -498,30 +532,24 @@ NYSE:
dest="raw",
help="Print raw data.",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
if self.ticker:
sec_view.fails_to_deliver(
ticker=self.ticker,
stock=self.stock,
start=ns_parser.start,
end=ns_parser.end,
num=ns_parser.n_num,
raw=ns_parser.raw,
export=ns_parser.export,
)
else:
print("No ticker loaded.\n")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if not self.ticker:
print("No ticker loaded.\n")
return
sec_view.fails_to_deliver(
ticker=self.ticker,
stock=self.stock,
start=ns_parser.start,
end=ns_parser.end,
num=ns_parser.n_num,
raw=ns_parser.raw,
export=ns_parser.export,
)
return self.queue
@try_except
def call_spos(self, other_args: List[str]):
@@ -541,31 +569,27 @@ NYSE:
)
parser.add_argument(
"-r",
"--raw",
action="store_true",
default=False,
help="Flag to print raw data instead",
dest="raw",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if not self.ticker:
print("No ticker loaded.\n")
return
if ns_parser:
if self.ticker:
stockgrid_view.net_short_position(
ticker=self.ticker,
num=ns_parser.num,
raw=ns_parser.raw,
export=ns_parser.export,
)
else:
print("No ticker loaded.\n")
stockgrid_view.net_short_position(
ticker=self.ticker,
num=ns_parser.num,
raw=ns_parser.raw,
export=ns_parser.export,
)
return self.queue
@try_except
def call_psi(self, other_args: List[str]):
@@ -582,13 +606,14 @@ NYSE:
dest="stockgrid",
help="Source of short interest volume",
)
parser.add_argument(
"--nyse",
action="store_true",
default=False,
dest="b_nyse",
help="ONLY QUANDL SOURCE. Data from NYSE flag. Otherwise comes from NASDAQ.",
)
if "quandl" in other_args:
parser.add_argument(
"--nyse",
action="store_true",
default=False,
dest="b_nyse",
help="Data from NYSE flag. Otherwise comes from NASDAQ.",
)
parser.add_argument(
"-n",
"--number",
@@ -599,40 +624,36 @@ NYSE:
)
parser.add_argument(
"-r",
"--raw",
action="store_true",
default=False,
help="Flag to print raw data instead",
dest="raw",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
dest="export",
help="Export dataframe data to csv,json,xlsx file",
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if not self.ticker:
print("No ticker loaded.\n")
return
if ns_parser:
if self.ticker:
if "quandl" in other_args:
quandl_view.short_interest(
ticker=self.ticker,
nyse=ns_parser.b_nyse,
days=ns_parser.num,
raw=ns_parser.raw,
export=ns_parser.export,
)
else:
stockgrid_view.short_interest_volume(
ticker=self.ticker,
num=ns_parser.num,
raw=ns_parser.raw,
export=ns_parser.export,
)
else:
print("No ticker loaded.\n")
if "quandl" in other_args:
quandl_view.short_interest(
ticker=self.ticker,
nyse=ns_parser.b_nyse,
days=ns_parser.num,
raw=ns_parser.raw,
export=ns_parser.export,
)
else:
stockgrid_view.short_interest_volume(
ticker=self.ticker,
num=ns_parser.num,
raw=ns_parser.raw,
export=ns_parser.export,
)
return self.queue
@try_except
def call_volexch(self, other_args: List[str]):
@@ -643,6 +664,7 @@ NYSE:
description="Displays short volume based on exchange.",
)
parser.add_argument(
"-r",
"--raw",
help="Display raw data",
dest="raw",
@@ -677,65 +699,98 @@ NYSE:
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES
)
if not ns_parser:
return
if not self.ticker:
print("No ticker loaded. Use `load ticker` first.")
return
nyse_view.display_short_by_exchange(
ticker=self.ticker,
raw=ns_parser.raw,
sort=ns_parser.sort,
asc=ns_parser.asc,
mpl=ns_parser.mpl,
export=ns_parser.export,
)
if ns_parser:
if self.ticker:
nyse_view.display_short_by_exchange(
ticker=self.ticker,
raw=ns_parser.raw,
sort=ns_parser.sort,
asc=ns_parser.asc,
mpl=ns_parser.mpl,
export=ns_parser.export,
)
else:
print("No ticker loaded. Use `load ticker` first.")
return self.queue
def menu(ticker: str = "", start: str = "", stock: pd.DataFrame = pd.DataFrame()):
"""Dark Pool Shorts Menu
def menu(
ticker: str = "",
start: str = "",
stock: pd.DataFrame = pd.DataFrame(),
queue: List[str] = None,
):
"""Dark Pool Shorts Menu"""
dps_controller = DarkPoolShortsController(ticker, start, stock, queue)
an_input = "HELP_ME"
Parameters
----------
stock : DataFrame
Due diligence stock dataframe
ticker : str
Due diligence ticker symbol
start : str
Start date of the stock data
"""
dps_controller = DarkPoolShortsController(ticker, start, stock)
dps_controller.call_help(None)
# Loop forever and ever
while True:
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in dps_controller.CHOICES}
)
# There is a command in the queue
if dps_controller.queue and len(dps_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if dps_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(dps_controller.queue) > 1:
return dps_controller.queue[1:]
return []
an_input = session.prompt(
f"{get_flair()} (stocks)>(dps)> ",
completer=completer,
)
# Consume 1 element from the queue
an_input = dps_controller.queue[0]
dps_controller.queue = dps_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 dps_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /stocks/dps/ $ {an_input}")
# Get input command from user
else:
an_input = input(f"{get_flair()} (stocks)>(dps)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
dps_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and dps_controller.completer:
an_input = session.prompt(
f"{get_flair()} /stocks/dps/ $ ",
completer=dps_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /stocks/dps/ $ ")
try:
plt.close("all")
process_input = dps_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
dps_controller.queue = dps_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
similar_cmd = difflib.get_close_matches(
an_input, dps_controller.CHOICES, n=1, cutoff=0.7
print(
f"\nThe command '{an_input}' doesn't exist on the /stocks/dps menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
dps_controller.CHOICES,
n=1,
cutoff=0.7,
)
if similar_cmd:
print(f"Did you mean '{similar_cmd[0]}'?\n")
continue
if " " in an_input:
candidate_input = (
f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}"
)
if candidate_input == an_input:
an_input = ""
dps_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
dps_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -34,6 +34,10 @@ def get_high_short_interest() -> DataFrame:
a_high_short_interest_header.append(
high_short_interest_header.text.strip("\n").split("\n")[0]
)
if not a_high_short_interest_header:
return pd.DataFrame()
df_high_short_interest = pd.DataFrame(columns=a_high_short_interest_header)
df_high_short_interest.loc[0] = ["", "", "", "", "", "", ""]

View File

@@ -6,6 +6,7 @@ from tabulate import tabulate
from gamestonk_terminal.helper_funcs import export_data
from gamestonk_terminal.stocks.dark_pool_shorts import shortinterest_model
from gamestonk_terminal import feature_flags as gtff
def high_short_interest(num: int, export: str):
@@ -19,22 +20,30 @@ def high_short_interest(num: int, export: str):
Export dataframe data to csv,json,xlsx file
"""
df_high_short_interest = shortinterest_model.get_high_short_interest()
if df_high_short_interest.empty:
print("No data available.", "\n")
return
df_high_short_interest = df_high_short_interest.iloc[1:].head(n=num)
print(
tabulate(
df_high_short_interest,
headers=df_high_short_interest.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df_high_short_interest,
headers=df_high_short_interest.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df_high_short_interest.to_string())
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"lowfloat",
"hsi",
df_high_short_interest,
)

File diff suppressed because it is too large Load Diff

View File

@@ -48,15 +48,18 @@ def price_target_from_analysts(
if raw:
df_analyst_data.index = df_analyst_data.index.strftime("%d/%m/%Y")
print(
tabulate(
df_analyst_data.sort_index(ascending=False).head(num),
headers=df_analyst_data.columns,
floatfmt=".2f",
showindex=True,
tablefmt="fancy_grid",
if gtff.USE_TABULATE_DF:
print(
tabulate(
df_analyst_data.sort_index(ascending=False).head(num),
headers=df_analyst_data.columns,
floatfmt=".2f",
showindex=True,
tablefmt="fancy_grid",
)
)
)
else:
print(df_analyst_data.head(num).to_string())
else:
# Slice start of ratings
@@ -86,7 +89,7 @@ def price_target_from_analysts(
if gtff.USE_ION:
plt.ion()
plt.gcf().autofmt_xdate()
plt.show()
print("")

View File

@@ -3,7 +3,7 @@ __docformat__ = "numpy"
import argparse
import difflib
from typing import List
from typing import List, Union
from datetime import datetime, timedelta
from pandas.core.frame import DataFrame
from prompt_toolkit.completion import NestedCompleter
@@ -35,9 +35,22 @@ class DueDiligenceController:
"""Due Diligence Controller"""
# Command choices
CHOICES = ["?", "cls", "help", "q", "quit", "load"]
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = [
"load",
"sec",
"rating",
"pt",
@@ -51,20 +64,15 @@ class DueDiligenceController:
CHOICES += CHOICES_COMMANDS
def __init__(self, ticker: str, start: str, interval: str, stock: DataFrame):
"""Constructor
Parameters
----------
ticker : str
Due diligence ticker symbol
start : str
Start date of the stock data
interval : str
Stock data interval
stock : DataFrame
Due diligence stock dataframe
"""
def __init__(
self,
ticker: str,
start: str,
interval: str,
stock: DataFrame,
queue: List[str] = None,
):
"""Constructor"""
self.ticker = ticker
self.start = start
self.interval = interval
@@ -75,17 +83,21 @@ class DueDiligenceController:
"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"]["-i"] = {c: {} for c in stocks_helper.INTERVALS}
choices["load"]["-s"] = {c: {} for c in stocks_helper.SOURCES}
self.completer = NestedCompleter.from_nested_dict(choices)
if queue:
self.queue = queue
else:
self.queue = list()
def print_help(self):
"""Print help"""
help_text = f"""
Due Diligence:
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon program
load load new ticker
Ticker: {self.ticker}
Finviz:
@@ -109,31 +121,47 @@ cathiesark.com
def switch(self, an_input: str):
"""Process and dispatch input
Parameters
-------
an_input : str
string with input arguments
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.dd_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -141,17 +169,57 @@ cathiesark.com
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program"""
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "dd")
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")
return self.queue
reset_commands = ["quit", "quit", "reset", "stocks"]
if self.ticker:
reset_commands.append(f"load {self.ticker}")
reset_commands.append("dd")
return reset_commands
def call_load(self, other_args: List[str]):
"""Process load command"""
@@ -186,29 +254,40 @@ cathiesark.com
dest="interval",
type=int,
default=1440,
choices=[1, 5, 15, 30, 60],
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.",
)
# 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)
if not ns_parser:
return
df_stock_candidate = stocks_helper.load(
ns_parser.ticker,
ns_parser.start,
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
df_stock_candidate = stocks_helper.load(
ticker=ns_parser.ticker,
start=ns_parser.start,
interval=ns_parser.interval,
source=ns_parser.source,
)
if not df_stock_candidate.empty:
self.stock = df_stock_candidate
self.start = ns_parser.start
if "." in ns_parser.ticker:
self.ticker = ns_parser.ticker.upper().split(".")[0]
else:
self.ticker = ns_parser.ticker.upper()
if not df_stock_candidate.empty:
self.stock = df_stock_candidate
self.start = ns_parser.start
if "." in ns_parser.ticker:
self.ticker = ns_parser.ticker.upper().split(".")[0]
else:
self.ticker = ns_parser.ticker.upper()
return self.queue
@try_except
def call_analyst(self, other_args: List[str]):
@@ -221,19 +300,14 @@ cathiesark.com
date, analyst, category, price from, price to, and rating. [Source: Finviz]
""",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
finviz_view.analyst(ticker=self.ticker, export=ns_parser.export)
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
finviz_view.analyst(ticker=self.ticker, export=ns_parser.export)
return self.queue
@try_except
def call_pt(self, other_args: List[str]):
@@ -250,35 +324,31 @@ cathiesark.com
help="Only output raw data",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
action="store",
dest="n_num",
dest="limit",
type=check_positive,
default=10,
help="Number of latest price targets from analysts to print.",
help="Limit of latest price targets from analysts to print.",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
business_insider_view.price_target_from_analysts(
ticker=self.ticker,
start=self.start,
interval=self.interval,
stock=self.stock,
num=ns_parser.n_num,
raw=ns_parser.raw,
export=ns_parser.export,
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_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
business_insider_view.price_target_from_analysts(
ticker=self.ticker,
start=self.start,
interval=self.interval,
stock=self.stock,
num=ns_parser.limit,
raw=ns_parser.raw,
export=ns_parser.export,
)
return self.queue
@try_except
def call_est(self, other_args: List[str]):
@@ -288,22 +358,17 @@ cathiesark.com
prog="est",
description="""Yearly estimates and quarter earnings/revenues. [Source: Business Insider]""",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
business_insider_view.estimates(
ticker=self.ticker,
export=ns_parser.export,
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
business_insider_view.estimates(
ticker=self.ticker,
export=ns_parser.export,
)
return self.queue
@try_except
def call_rot(self, other_args: List[str]):
@@ -316,13 +381,13 @@ cathiesark.com
""",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
action="store",
dest="n_num",
dest="limit",
type=check_positive,
default=10,
help="number of last months",
help="Limit of last months",
)
parser.add_argument(
"--raw",
@@ -330,28 +395,21 @@ cathiesark.com
dest="raw",
help="Only output raw data",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-n")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-l")
finnhub_view.rating_over_time(
ticker=self.ticker,
num=ns_parser.n_num,
raw=ns_parser.raw,
export=ns_parser.export,
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
finnhub_view.rating_over_time(
ticker=self.ticker,
num=ns_parser.limit,
raw=ns_parser.raw,
export=ns_parser.export,
)
return self.queue
@try_except
def call_rating(self, other_args: List[str]):
@@ -366,35 +424,28 @@ cathiesark.com
""",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
action="store",
dest="n_num",
dest="limit",
type=check_positive,
default=10,
help="number of last days to display ratings",
help="limit of last days to display ratings",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-n")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-l")
fmp_view.rating(
ticker=self.ticker,
num=ns_parser.n_num,
export=ns_parser.export,
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
fmp_view.rating(
ticker=self.ticker,
num=ns_parser.limit,
export=ns_parser.export,
)
return self.queue
@try_except
def call_sec(self, other_args: List[str]):
@@ -408,35 +459,29 @@ cathiesark.com
""",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
action="store",
dest="n_num",
dest="limit",
type=check_positive,
default=5,
help="number of latest SEC filings.",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-n")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-l")
marketwatch_view.sec_filings(
ticker=self.ticker,
num=ns_parser.n_num,
export=ns_parser.export,
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
marketwatch_view.sec_filings(
ticker=self.ticker,
num=ns_parser.limit,
export=ns_parser.export,
)
return self.queue
@try_except
def call_supplier(self, other_args: List[str]):
@@ -446,22 +491,16 @@ cathiesark.com
add_help=False,
description="List of suppliers from ticker provided. [Source: CSIMarket]",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
csimarket_view.suppliers(
ticker=self.ticker,
export=ns_parser.export,
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
csimarket_view.suppliers(
ticker=self.ticker,
export=ns_parser.export,
)
return self.queue
def call_customer(self, other_args: List[str]):
"""Process customer command"""
@@ -470,22 +509,16 @@ cathiesark.com
add_help=False,
description="List of customers from ticker provided. [Source: CSIMarket]",
)
parser.add_argument(
"--export",
choices=["csv", "json", "xlsx"],
default="",
type=str,
dest="export",
help="Export dataframe data to csv,json,xlsx file",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
csimarket_view.customers(
ticker=self.ticker,
export=ns_parser.export,
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
csimarket_view.customers(
ticker=self.ticker,
export=ns_parser.export,
)
return self.queue
@try_except
def call_arktrades(self, other_args):
@@ -498,12 +531,12 @@ cathiesark.com
""",
)
parser.add_argument(
"-n",
"--num",
help="Number of rows to show",
dest="num",
default=20,
type=int,
"-l",
"--limi",
help="Limit of rows to show",
dest="limit",
default=10,
type=check_positive,
)
parser.add_argument(
"-s",
@@ -513,20 +546,28 @@ cathiesark.com
help="Flag to show ticker in table",
dest="show_ticker",
)
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 not ns_parser:
return
ark_view.display_ark_trades(
ticker=self.ticker,
num=ns_parser.num,
export=ns_parser.export,
show_ticker=ns_parser.show_ticker,
)
if ns_parser:
ark_view.display_ark_trades(
ticker=self.ticker,
num=ns_parser.limit,
export=ns_parser.export,
show_ticker=ns_parser.show_ticker,
)
return self.queue
def menu(ticker: str, start: str, interval: str, stock: DataFrame):
def menu(
ticker: str,
start: str,
interval: str,
stock: DataFrame,
queue: List[str] = None,
):
"""Due Diligence Menu
Parameters
@@ -539,37 +580,79 @@ def menu(ticker: str, start: str, interval: str, stock: DataFrame):
Stock data interval
stock : DataFrame
Due diligence stock dataframe
queue: List[str]
List with commands in queue to run
"""
dd_controller = DueDiligenceController(ticker, start, interval, stock)
dd_controller.call_help(None)
dd_controller = DueDiligenceController(ticker, start, interval, stock, queue)
an_input = "HELP_ME"
while True:
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in dd_controller.CHOICES}
)
# There is a command in the queue
if dd_controller.queue and len(dd_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if dd_controller.queue[0] in ("q", "..", "quit"):
if len(dd_controller.queue) > 1:
return dd_controller.queue[1:]
return []
an_input = session.prompt(
f"{get_flair()} (stocks)>(dd)> ",
completer=completer,
)
# Consume 1 element from the queue
an_input = dd_controller.queue[0]
dd_controller.queue = dd_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 dd_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /stocks/dd/ $ {an_input}")
# Get input command from user
else:
an_input = input(f"{get_flair()} (stocks)>(dd)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
dd_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and dd_controller.completer:
an_input = session.prompt(
f"{get_flair()} /stocks/dd/ $ ",
completer=dd_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /stocks/dd/ $ ")
try:
process_input = dd_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
dd_controller.queue = dd_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
similar_cmd = difflib.get_close_matches(
an_input, dd_controller.CHOICES, n=1, cutoff=0.7
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,
dd_controller.CHOICES,
n=1,
cutoff=0.7,
)
if similar_cmd:
print(f"Did you mean '{similar_cmd[0]}'?\n")
continue
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 = ""
dd_controller.queue = []
print("\n")
continue
print(f" Replacing by '{an_input}'.")
dd_controller.queue.insert(0, an_input)
else:
print("\n")
an_input = ""
dd_controller.queue = []

View File

@@ -14,19 +14,19 @@ from gamestonk_terminal import feature_flags as gtff
register_matplotlib_converters()
def plot_rating_over_time(rot: pd.DataFrame, ticker: str):
def plot_rating_over_time(df_rot: pd.DataFrame, ticker: str):
"""Plot rating over time
Parameters
----------
rot : pd.DataFrame
df_rot : pd.DataFrame
Rating over time
ticker : str
Ticker associated with ratings
"""
plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI)
rot.sort_values("period", inplace=True)
rot = df_rot.sort_values("period")
plt.plot(pd.to_datetime(rot["period"]), rot["strongBuy"], c="green", lw=3)
plt.plot(pd.to_datetime(rot["period"]), rot["buy"], c="lightgreen", lw=3)
plt.plot(pd.to_datetime(rot["period"]), rot["hold"], c="grey", lw=3)
@@ -82,15 +82,18 @@ def rating_over_time(ticker: str, num: int, raw: bool, export: str):
.rename(columns=d_cols)
.head(num)
)
print(
tabulate(
df_rot_raw,
headers=df_rot_raw.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
if gtff.USE_TABULATE_DF:
print(
tabulate(
df_rot_raw,
headers=df_rot_raw.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
)
)
)
else:
print(df_rot_raw.to_string())
else:
plot_rating_over_time(df_rot.head(num), ticker)

View File

@@ -32,7 +32,12 @@ def get_overview(ticker: str) -> pd.DataFrame:
# If the returned data was successful
if result.status_code == 200:
# Parse json data to dataframe
if "Note" in result.json():
print(result.json()["Note"], "\n")
return pd.DataFrame()
df_fa = pd.json_normalize(result.json())
# Keep json data sorting in dataframe
df_fa = df_fa[list(result.json().keys())].T
df_fa.iloc[5:] = df_fa.iloc[5:].applymap(lambda x: long_number_format(x))
@@ -136,18 +141,24 @@ def get_income_statements(
"""
url = f"https://www.alphavantage.co/query?function=INCOME_STATEMENT&symbol={ticker}&apikey={cfg.API_KEY_ALPHAVANTAGE}"
r = requests.get(url)
statements = r.json()
if quarterly:
# pylint: disable=unbalanced-tuple-unpacking
df_fa = pd.DataFrame(statements["quarterlyReports"])
else:
# pylint: disable=unbalanced-tuple-unpacking
df_fa = pd.DataFrame(statements["annualReports"])
if r.status_code == 200:
statements = r.json()
df_fa = pd.DataFrame()
if quarterly:
if "quarterlyReports" in statements:
df_fa = pd.DataFrame(statements["quarterlyReports"])
else:
if "annualReports" in statements:
df_fa = pd.DataFrame(statements["annualReports"])
df_fa = df_fa.set_index("fiscalDateEnding")
df_fa = df_fa.head(number)
df_fa = df_fa.applymap(lambda x: long_number_format(x))
return df_fa[::-1].T
if df_fa.empty:
return pd.DataFrame()
df_fa = df_fa.set_index("fiscalDateEnding")
df_fa = df_fa.head(number)
df_fa = df_fa.applymap(lambda x: long_number_format(x))
return df_fa[::-1].T
return pd.DataFrame()
def get_balance_sheet(
@@ -171,16 +182,24 @@ def get_balance_sheet(
"""
url = f"https://www.alphavantage.co/query?function=BALANCE_SHEET&symbol={ticker}&apikey={cfg.API_KEY_ALPHAVANTAGE}"
r = requests.get(url)
statements = r.json()
if quarterly:
df_fa = pd.DataFrame(statements["quarterlyReports"])
else:
df_fa = pd.DataFrame(statements["annualReports"])
if r.status_code == 200:
statements = r.json()
df_fa = pd.DataFrame()
if quarterly:
if "quarterlyReports" in statements:
df_fa = pd.DataFrame(statements["quarterlyReports"])
else:
if "annualReports" in statements:
df_fa = pd.DataFrame(statements["annualReports"])
df_fa = df_fa.set_index("fiscalDateEnding")
df_fa = df_fa.head(number)
df_fa = df_fa.applymap(lambda x: long_number_format(x))
return df_fa[::-1].T
if df_fa.empty:
return pd.DataFrame()
df_fa = df_fa.set_index("fiscalDateEnding")
df_fa = df_fa.head(number)
df_fa = df_fa.applymap(lambda x: long_number_format(x))
return df_fa[::-1].T
return pd.DataFrame()
def get_cash_flow(ticker: str, number: int, quarterly: bool = False) -> pd.DataFrame:
@@ -202,16 +221,24 @@ def get_cash_flow(ticker: str, number: int, quarterly: bool = False) -> pd.DataF
"""
url = f"https://www.alphavantage.co/query?function=CASH_FLOW&symbol={ticker}&apikey={cfg.API_KEY_ALPHAVANTAGE}"
r = requests.get(url)
statements = r.json()
if quarterly:
df_fa = pd.DataFrame(statements["quarterlyReports"])
else:
df_fa = pd.DataFrame(statements["annualReports"])
if r.status_code == 200:
statements = r.json()
df_fa = pd.DataFrame()
if quarterly:
if "quarterlyReports" in statements:
df_fa = pd.DataFrame(statements["quarterlyReports"])
else:
if "annualReports" in statements:
df_fa = pd.DataFrame(statements["annualReports"])
df_fa = df_fa.set_index("fiscalDateEnding")
df_fa = df_fa.head(number)
df_fa = df_fa.applymap(lambda x: long_number_format(x))
return df_fa[::-1].T
if df_fa.empty:
return pd.DataFrame()
df_fa = df_fa.set_index("fiscalDateEnding")
df_fa = df_fa.head(number)
df_fa = df_fa.applymap(lambda x: long_number_format(x))
return df_fa[::-1].T
return pd.DataFrame()
def get_earnings(ticker: str, quarterly: bool = False) -> pd.DataFrame:

View File

@@ -21,8 +21,9 @@ def display_overview(ticker: str):
"""
df_fa = av_model.get_overview(ticker)
if df_fa.empty:
print(f"No data found from alphavantage for {ticker}.\n")
print("No API calls left. Try me later", "\n")
return
if gtff.USE_TABULATE_DF:
print(
tabulate(
@@ -45,9 +46,8 @@ def display_key(ticker: str):
Fundamental analysis ticker symbol
"""
df_key = av_model.get_key_metrics(ticker)
if df_key.empty:
print("Issue getting key metrics from alpha vantage.")
print("No API calls left. Try me later", "\n")
return
if gtff.USE_TABULATE_DF:
@@ -59,7 +59,7 @@ def display_key(ticker: str):
def display_income_statement(
ticker: str, number: int, quarterly: bool = False, export: str = ""
ticker: str, limit: int, quarterly: bool = False, export: str = ""
):
"""Alpha Vantage income statement
@@ -67,14 +67,17 @@ def display_income_statement(
----------
ticker : str
Fundamental analysis ticker symbol
number: int
limit: int
Number of past statements
quarterly: bool
Flag to get quarterly instead of annual
export: str
Format to export data
"""
df_income = av_model.get_income_statements(ticker, number, quarterly)
df_income = av_model.get_income_statements(ticker, limit, quarterly)
if df_income.empty:
print("No API calls left. Try me later", "\n")
return
if gtff.USE_TABULATE_DF:
print(tabulate(df_income, headers=df_income.columns, tablefmt="fancy_grid"))
@@ -86,7 +89,7 @@ def display_income_statement(
def display_balance_sheet(
ticker: str, number: int, quarterly: bool = False, export: str = ""
ticker: str, limit: int, quarterly: bool = False, export: str = ""
):
"""Alpha Vantage income statement
@@ -94,14 +97,17 @@ def display_balance_sheet(
----------
ticker : str
Fundamental analysis ticker symbol
number: int
limit: int
Number of past statements
quarterly: bool
Flag to get quarterly instead of annual
export: str
Format to export data
"""
df_balance = av_model.get_balance_sheet(ticker, number, quarterly)
df_balance = av_model.get_balance_sheet(ticker, limit, quarterly)
if df_balance.empty:
print("No API calls left. Try me later", "\n")
return
if gtff.USE_TABULATE_DF:
print(tabulate(df_balance, headers=df_balance.columns, tablefmt="fancy_grid"))
@@ -115,7 +121,7 @@ def display_balance_sheet(
def display_cash_flow(
ticker: str, number: int, quarterly: bool = False, export: str = ""
ticker: str, limit: int, quarterly: bool = False, export: str = ""
):
"""Alpha Vantage income statement
@@ -123,14 +129,17 @@ def display_cash_flow(
----------
ticker : str
Fundamental analysis ticker symbol
number: int
limit: int
Number of past statements
quarterly: bool
Flag to get quarterly instead of annual
export: str
Format to export data
"""
df_cash = av_model.get_cash_flow(ticker, number, quarterly)
df_cash = av_model.get_cash_flow(ticker, limit, quarterly)
if df_cash.empty:
print("No API calls left. Try me later", "\n")
return
if gtff.USE_TABULATE_DF:
print(tabulate(df_cash, headers=df_cash.columns, tablefmt="fancy_grid"))
@@ -141,33 +150,33 @@ def display_cash_flow(
export_data(export, os.path.dirname(os.path.abspath(__file__)), "cash", df_cash)
def display_earnings(ticker: str, number: int, quarterly: bool = False):
def display_earnings(ticker: str, limit: int, quarterly: bool = False):
"""Alpha Vantage earnings
Parameters
----------
ticker : str
Fundamental analysis ticker symbol
number:int
limit:int
Number of events to show
quarterly: bool
Flag to show quarterly instead of annual
"""
df_fa = av_model.get_earnings(ticker, quarterly)
if df_fa.empty:
print("Error getting earnings data.\n")
print("No API calls left. Try me later", "\n")
return
if gtff.USE_TABULATE_DF:
print(
tabulate(
df_fa.head(number),
df_fa.head(limit),
headers=df_fa.columns,
showindex=False,
tablefmt="fancy_grid",
)
)
else:
print(df_fa.head(n=number).T.to_string(header=False))
print(df_fa.head(n=limit).T.to_string(header=False))
print("")

View File

@@ -12,6 +12,7 @@ from openpyxl import Workbook, worksheet
from openpyxl.styles import Font
from sklearn.linear_model import LinearRegression
from bs4 import BeautifulSoup
import regex as re
import yfinance as yf
import pandas as pd
import numpy as np
@@ -114,7 +115,7 @@ class CreateExcelFA:
soup = BeautifulSoup(r.content, "html.parser")
table = soup.find(
"table", attrs={"class": "FinancialTable_table_financial__1RhYq"}
"table", attrs={"class": re.compile("^FinancialTable_table_financial__.*")}
)
head = table.find("thead")
columns = head.find_all("th")
@@ -1288,7 +1289,7 @@ class CreateExcelFA:
soup = BeautifulSoup(r.content, "html.parser")
table = soup.find(
"table", attrs={"class": "FinancialTable_table_financial__1RhYq"}
"table", attrs={"class": re.compile("^FinancialTable_table_financial__.*")}
)
head = table.find("thead")
if head is None:

View File

@@ -2,7 +2,8 @@
__docformat__ = "numpy"
import argparse
from typing import List
import difflib
from typing import List, Union
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal.stocks.fundamental_analysis.financial_modeling_prep import (
@@ -24,7 +25,20 @@ class FinancialModelingPrepController:
"""Financial Modeling Prep Controller"""
# Command choices
CHOICES = ["cls", "?", "help", "q", "quit"]
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = [
"profile",
"quote",
@@ -39,7 +53,13 @@ class FinancialModelingPrepController:
]
CHOICES += CHOICES_COMMANDS
def __init__(self, ticker: str, start: str, interval: str):
def __init__(
self,
ticker: str,
start: str,
interval: str,
queue: List[str] = None,
):
"""Constructor
Parameters
@@ -60,15 +80,20 @@ class FinancialModelingPrepController:
"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_text = f"""
Financial Modeling Prep:
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon program
Financial Modeling Prep Menu:
Ticker: {self.ticker}
@@ -87,31 +112,47 @@ Ticker: {self.ticker}
def switch(self, an_input: str):
"""Process and dispatch input
Parameters
-------
an_input : str
string with input arguments
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.fmp_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -119,17 +160,53 @@ Ticker: {self.ticker}
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program"""
return True
"""Process quit menu command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "fmp")
self.queue.insert(0, "fa")
self.queue.insert(0, "stocks")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit", "reset", "stocks", "fa", "fmp"]
@try_except
def call_profile(self, other_args: List[str]):
@@ -148,9 +225,9 @@ Ticker: {self.ticker}
""",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
fmp_view.display_profile(self.ticker)
if ns_parser:
fmp_view.display_profile(self.ticker)
return self.queue
@try_except
def call_quote(self, other_args: List[str]):
@@ -169,9 +246,9 @@ Ticker: {self.ticker}
""",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
fmp_view.display_quote(self.ticker)
if ns_parser:
fmp_view.display_quote(self.ticker)
return self.queue
@try_except
def call_enterprise(self, other_args: List[str]):
@@ -207,15 +284,15 @@ Ticker: {self.ticker}
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
if ns_parser:
fmp_view.display_enterprise(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
fmp_view.display_enterprise(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
return self.queue
@try_except
def call_dcf(self, other_args: List[str]):
@@ -250,14 +327,14 @@ Ticker: {self.ticker}
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
fmp_view.display_discounted_cash_flow(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
if ns_parser:
fmp_view.display_discounted_cash_flow(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
return self.queue
@try_except
def call_income(self, other_args: List[str]):
@@ -298,14 +375,14 @@ Ticker: {self.ticker}
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
fmp_view.display_income_statement(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
if ns_parser:
fmp_view.display_income_statement(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
return self.queue
@try_except
def call_balance(self, other_args: List[str]):
@@ -351,14 +428,14 @@ Ticker: {self.ticker}
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
fmp_view.display_balance_sheet(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
if ns_parser:
fmp_view.display_balance_sheet(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
return self.queue
@try_except
def call_cash(self, other_args: List[str]):
@@ -402,14 +479,14 @@ Ticker: {self.ticker}
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
fmp_view.display_cash_flow(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
if ns_parser:
fmp_view.display_cash_flow(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
return self.queue
@try_except
def call_metrics(self, other_args: List[str]):
@@ -458,14 +535,14 @@ Ticker: {self.ticker}
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
fmp_view.display_key_metrics(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
if ns_parser:
fmp_view.display_key_metrics(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
return self.queue
@try_except
def call_ratios(self, other_args: List[str]):
@@ -515,14 +592,14 @@ Ticker: {self.ticker}
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
fmp_view.display_financial_ratios(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
if ns_parser:
fmp_view.display_financial_ratios(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
return self.queue
@try_except
def call_growth(self, other_args: List[str]):
@@ -568,17 +645,22 @@ Ticker: {self.ticker}
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
fmp_view.display_financial_statement_growth(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
if ns_parser:
fmp_view.display_financial_statement_growth(
ticker=self.ticker,
number=ns_parser.n_num,
quarterly=ns_parser.b_quarter,
export=ns_parser.export,
)
return self.queue
def menu(ticker: str, start: str, interval: str):
def menu(
ticker: str,
start: str,
interval: str,
queue: List[str] = None,
):
"""Financial Modeling Prep menu
Parameters
@@ -591,29 +673,75 @@ def menu(ticker: str, start: str, interval: str):
Stock data interval
"""
fmp_controller = FinancialModelingPrepController(ticker, start, interval)
fmp_controller.call_help(None)
fmp_controller = FinancialModelingPrepController(ticker, start, interval, queue)
an_input = "HELP_ME"
while True:
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in fmp_controller.CHOICES}
)
# There is a command in the queue
if fmp_controller.queue and len(fmp_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if fmp_controller.queue[0] in ("q", "..", "quit"):
if len(fmp_controller.queue) > 1:
return fmp_controller.queue[1:]
return []
an_input = session.prompt(
f"{get_flair()} (stocks)>(fa)>(fmp)> ",
completer=completer,
)
# Consume 1 element from the queue
an_input = fmp_controller.queue[0]
fmp_controller.queue = fmp_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 fmp_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /stocks/fa/fmp/ $ {an_input}")
# Get input command from user
else:
an_input = input(f"{get_flair()} (stocks)>(fa)>(fmp)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
fmp_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and fmp_controller.completer:
an_input = session.prompt(
f"{get_flair()} /stocks/fa/fmp/ $ ",
completer=fmp_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /stocks/fa/fmp/ $ ")
try:
process_input = fmp_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
fmp_controller.queue = fmp_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
continue
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,
fmp_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 = ""
fmp_controller.queue = []
print("\n")
continue
print(f" Replacing by '{an_input}'.")
fmp_controller.queue.insert(0, an_input)
else:
print("\n")
an_input = ""
fmp_controller.queue = []

View File

@@ -101,7 +101,7 @@ def display_sustainability(ticker: str):
df_sustainability = yahoo_finance_model.get_sustainability(ticker)
if df_sustainability.empty:
print("No sustainability data found.")
print("No sustainability data found.", "\n")
return
if gtff.USE_TABULATE_DF:
@@ -143,13 +143,15 @@ def display_calendar_earnings(ticker: str):
print("")
def display_dividends(ticker: str, num: int = 12, plot: bool = False, export: str = ""):
def display_dividends(
ticker: str, limit: int = 12, plot: bool = False, export: str = ""
):
"""Display historical dividends
Parameters
----------
ticker: str
Stock ticker
num: int
limit: int
Number to show
plot: bool
Plots hitsorical data
@@ -193,7 +195,7 @@ def display_dividends(ticker: str, num: int = 12, plot: bool = False, export: st
if gtff.USE_TABULATE_DF:
print(
tabulate(
div_history.head(num),
div_history.head(limit),
tablefmt="fancy_grid",
headers=["Amount Paid ($)", "Change"],
floatfmt=".2f",

View File

@@ -1,12 +1,11 @@
"""Government Controller Module"""
""" Government Controller Module """
__docformat__ = "numpy"
import argparse
import difflib
from datetime import datetime, timedelta
from typing import List
from typing import List, Union
from colorama import Style
from matplotlib import pyplot as plt
from prompt_toolkit.completion import NestedCompleter
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import get_flair
@@ -29,49 +28,74 @@ from gamestonk_terminal.helper_funcs import (
class GovController:
"""Gov Controller class"""
# Command choices
CHOICES = ["cls", "?", "help", "q", "quit", "load"]
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = [
"load",
"lasttrades",
"topbuys",
"topsells",
"qtrcontracts",
"toplobbying",
]
CHOICES_COMMANDS_TICKER = [
"gtrades",
"lastcontracts",
"contracts",
"histcont",
"lobbying",
]
CHOICES += CHOICES_COMMANDS + CHOICES_COMMANDS_TICKER
CHOICES += CHOICES_COMMANDS
gov_type_choices = ["congress", "senate", "house"]
analysis_choices = ["total", "upmom", "downmom"]
def __init__(
self,
ticker: str,
queue: List[str] = None,
):
"""Constructor"""
self.ticker = ticker
self.gov_parser = argparse.ArgumentParser(add_help=False, prog="gov")
self.gov_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["lasttrades"] = {c: {} for c in self.gov_type_choices}
choices["topbuys"] = {c: {} for c in self.gov_type_choices}
choices["topsells"] = {c: {} for c in self.gov_type_choices}
choices["qtrcontracts"]["-a"] = {c: {} for c in self.analysis_choices}
choices["qtrcontracts"]["--analysis"] = {
c: {} for c in self.analysis_choices
}
self.completer = NestedCompleter.from_nested_dict(choices)
if queue:
self.queue = queue
else:
self.queue = list()
self.ticker = ticker
def print_help(self):
"""Print help"""
dim_no_ticker = Style.DIM if not self.ticker else ""
reset_style = Style.RESET_ALL
help_string = f"""
Government:
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon program
load load a ticker
help_txt = f"""
Explore:
lasttrades last trades
topbuys show most purchased stocks
@@ -80,6 +104,8 @@ Explore:
qtrcontracts quarterly government contracts analysis
toplobbying top corporate lobbying tickers
load load a specific ticker for analysis
Ticker: {self.ticker or None}{dim_no_ticker}
gtrades show government trades for ticker
@@ -87,35 +113,47 @@ Ticker: {self.ticker or None}{dim_no_ticker}
histcont show historical quarterly government contracts for ticker
lobbying corporate lobbying details for ticker{reset_style}
"""
print(help_string)
print(help_txt)
def switch(self, an_input: str):
"""Process and dispatch input
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.gov_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -123,18 +161,51 @@ Ticker: {self.ticker or None}{dim_no_ticker}
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program"""
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "gov")
self.queue.insert(0, "stocks")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "reset", "stocks", "gov"]
@try_except
def call_load(self, other_args: List[str]):
"""Process load command"""
parser = argparse.ArgumentParser(
@@ -161,22 +232,20 @@ Ticker: {self.ticker or None}{dim_no_ticker}
dest="start",
help="The starting date (format YYYY-MM-DD) of the stock",
)
# For the case where a user uses: 'load BB'
if other_args and "-t" not in other_args and "-h" not in other_args:
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 not ns_parser:
return
if ns_parser:
df_stock_candidate = stocks_helper.load(
ns_parser.ticker,
ns_parser.start,
)
if not df_stock_candidate.empty:
self.ticker = ns_parser.ticker.upper()
else:
print("Ticker selected does not exist!", "\n")
df_stock_candidate = stocks_helper.load(
ns_parser.ticker,
ns_parser.start,
)
if not df_stock_candidate.empty:
self.ticker = ns_parser.ticker.upper()
return self.queue
@try_except
def call_lasttrades(self, other_args: List[str]):
@@ -191,7 +260,7 @@ Ticker: {self.ticker or None}{dim_no_ticker}
"-g",
"--govtype",
dest="gov",
choices=["congress", "senate", "house"],
choices=self.gov_type_choices,
type=str,
default="congress",
)
@@ -213,20 +282,20 @@ Ticker: {self.ticker or None}{dim_no_ticker}
default="",
help="Representative",
)
if other_args and "-" not in other_args[0]:
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-g")
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_ONLY_RAW_DATA_ALLOWED
)
if not ns_parser:
return
quiverquant_view.display_last_government(
gov_type=ns_parser.gov,
past_days=ns_parser.past_transactions_days,
representative=ns_parser.representative,
export=ns_parser.export,
)
if ns_parser:
quiverquant_view.display_last_government(
gov_type=ns_parser.gov,
past_days=ns_parser.past_transactions_days,
representative=ns_parser.representative,
export=ns_parser.export,
)
return self.queue
@try_except
def call_topbuys(self, other_args: List[str]):
@@ -241,7 +310,7 @@ Ticker: {self.ticker or None}{dim_no_ticker}
"-g",
"--govtype",
dest="gov",
choices=["congress", "senate", "house"],
choices=self.gov_type_choices,
type=str,
default="congress",
)
@@ -255,13 +324,13 @@ Ticker: {self.ticker or None}{dim_no_ticker}
help="Past transaction months",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
action="store",
dest="num",
dest="limit",
type=check_positive,
default=10,
help="Number of top tickers",
help="Limit of top tickers to display",
)
parser.add_argument(
"--raw",
@@ -270,20 +339,21 @@ Ticker: {self.ticker or None}{dim_no_ticker}
dest="raw",
help="Print raw data.",
)
if other_args and "-" not in other_args[0]:
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-g")
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES
)
if not ns_parser:
return
quiverquant_view.display_government_buys(
gov_type=ns_parser.gov,
past_transactions_months=ns_parser.past_transactions_months,
num=ns_parser.num,
raw=ns_parser.raw,
export=ns_parser.export,
)
if ns_parser:
quiverquant_view.display_government_buys(
gov_type=ns_parser.gov,
past_transactions_months=ns_parser.past_transactions_months,
num=ns_parser.limit,
raw=ns_parser.raw,
export=ns_parser.export,
)
return self.queue
@try_except
def call_topsells(self, other_args: List[str]):
@@ -298,7 +368,7 @@ Ticker: {self.ticker or None}{dim_no_ticker}
"-g",
"--govtype",
dest="gov",
choices=["congress", "senate", "house"],
choices=self.gov_type_choices,
type=str,
default="congress",
)
@@ -312,13 +382,13 @@ Ticker: {self.ticker or None}{dim_no_ticker}
help="Past transaction months",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
action="store",
dest="num",
dest="limit",
type=check_positive,
default=10,
help="Number of top tickers",
help="Limit of top tickers to display",
)
parser.add_argument(
"--raw",
@@ -327,20 +397,21 @@ Ticker: {self.ticker or None}{dim_no_ticker}
dest="raw",
help="Print raw data.",
)
if other_args and "-" not in other_args[0]:
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-g")
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES
)
if not ns_parser:
return
quiverquant_view.display_government_sells(
gov_type=ns_parser.gov,
past_transactions_months=ns_parser.past_transactions_months,
num=ns_parser.num,
raw=ns_parser.raw,
export=ns_parser.export,
)
if ns_parser:
quiverquant_view.display_government_sells(
gov_type=ns_parser.gov,
past_transactions_months=ns_parser.past_transactions_months,
num=ns_parser.limit,
raw=ns_parser.raw,
export=ns_parser.export,
)
return self.queue
@try_except
def call_lastcontracts(self, other_args: List[str]):
@@ -361,13 +432,13 @@ Ticker: {self.ticker or None}{dim_no_ticker}
help="Past transaction days",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
action="store",
dest="num",
dest="limit",
type=check_positive,
default=20,
help="Number of contracts to display",
help="Limit of contracts to display",
)
parser.add_argument(
"-s",
@@ -377,17 +448,20 @@ Ticker: {self.ticker or None}{dim_no_ticker}
default=False,
help="Flag to show total amount of contracts.",
)
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 not ns_parser:
return
quiverquant_view.display_last_contracts(
past_transaction_days=ns_parser.past_transaction_days,
num=ns_parser.num,
sum_contracts=ns_parser.sum,
export=ns_parser.export,
)
if ns_parser:
quiverquant_view.display_last_contracts(
past_transaction_days=ns_parser.past_transaction_days,
num=ns_parser.limit,
sum_contracts=ns_parser.sum,
export=ns_parser.export,
)
return self.queue
@try_except
def call_qtrcontracts(self, other_args: List[str]):
@@ -399,32 +473,46 @@ Ticker: {self.ticker or None}{dim_no_ticker}
description="Look at government contracts [Source: www.quiverquant.com]",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
action="store",
dest="num",
dest="limit",
type=check_positive,
default=5,
help="Number of tickers to get",
help="Limit of tickers to get",
)
parser.add_argument(
"-a",
"--analysis",
action="store",
dest="analysis",
choices=["total", "upmom", "downmom"],
choices=self.analysis_choices,
type=str,
default="total",
help="""Analysis to look at contracts. 'Total' shows summed contracts.
'Upmom' shows highest sloped contacts while 'downmom' shows highest decreasing slopes.""",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
quiverquant_view.display_qtr_contracts(
analysis=ns_parser.analysis, num=ns_parser.num
parser.add_argument(
"--raw",
action="store_true",
default=False,
dest="raw",
help="Print 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_BOTH_RAW_DATA_AND_FIGURES
)
if ns_parser:
quiverquant_view.display_qtr_contracts(
analysis=ns_parser.analysis,
num=ns_parser.limit,
raw=ns_parser.raw,
export=ns_parser.export,
)
return self.queue
@try_except
def call_toplobbying(self, other_args: List[str]):
@@ -436,13 +524,13 @@ Ticker: {self.ticker or None}{dim_no_ticker}
description="Top lobbying. [Source: www.quiverquant.com]",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
action="store",
dest="num",
dest="limit",
type=check_positive,
default=10,
help="Number to show",
help="Limit of stocks to display",
)
parser.add_argument(
"--raw",
@@ -454,19 +542,12 @@ Ticker: {self.ticker or None}{dim_no_ticker}
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES
)
if not ns_parser:
return
if ns_parser:
quiverquant_view.display_top_lobbying(
num=ns_parser.limit, raw=ns_parser.raw, export=ns_parser.export
)
quiverquant_view.display_top_lobbying(
num=ns_parser.num, raw=ns_parser.raw, export=ns_parser.export
)
def _check_ticker(self):
"""Check if ticker loaded"""
if self.ticker:
return True
print("No ticker loaded. Use `load <ticker>` first.\n")
return False
return self.queue
@try_except
def call_gtrades(self, other_args: List[str]):
@@ -490,7 +571,7 @@ Ticker: {self.ticker or None}{dim_no_ticker}
"-g",
"--govtype",
dest="gov",
choices=["congress", "senate", "house"],
choices=self.gov_type_choices,
type=str,
default="congress",
)
@@ -501,20 +582,24 @@ Ticker: {self.ticker or None}{dim_no_ticker}
dest="raw",
help="Print raw data.",
)
if other_args and "-" not in other_args[0]:
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-g")
ns_parser = parse_known_args_and_warn(parser, other_args)
ns_parser = parse_known_args_and_warn(
parser, other_args, EXPORT_BOTH_RAW_DATA_AND_FIGURES
)
if ns_parser:
if self.ticker:
quiverquant_view.display_government_trading(
ticker=self.ticker,
gov_type=ns_parser.gov,
past_transactions_months=ns_parser.past_transactions_months,
raw=ns_parser.raw,
export=ns_parser.export,
)
else:
print("No ticker loaded. Use `load <ticker>` first.\n")
if not ns_parser:
return
if self._check_ticker():
quiverquant_view.display_government_trading(
ticker=self.ticker,
gov_type=ns_parser.gov,
past_transactions_months=ns_parser.past_transactions_months,
raw=ns_parser.raw,
)
return self.queue
@try_except
def call_contracts(self, other_args: List[str]):
@@ -541,16 +626,23 @@ Ticker: {self.ticker or None}{dim_no_ticker}
dest="raw",
help="Print raw data.",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
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, EXPORT_BOTH_RAW_DATA_AND_FIGURES
)
if ns_parser:
if self.ticker:
quiverquant_view.display_contracts(
ticker=self.ticker,
past_transaction_days=ns_parser.past_transaction_days,
raw=ns_parser.raw,
export=ns_parser.export,
)
else:
print("No ticker loaded. Use `load <ticker>` first.\n")
if self._check_ticker():
quiverquant_view.display_contracts(
ticker=self.ticker,
past_transaction_days=ns_parser.past_transaction_days,
raw=ns_parser.raw,
)
return self.queue
@try_except
def call_histcont(self, other_args: List[str]):
@@ -561,17 +653,25 @@ Ticker: {self.ticker or None}{dim_no_ticker}
prog="histcont",
description="Quarterly-contracts historical [Source: www.quiverquant.com]",
)
parser.add_argument(
"--raw",
action="store_true",
default=False,
dest="raw",
help="Print raw data.",
)
ns_parser = parse_known_args_and_warn(
parser, other_args, export_allowed=EXPORT_BOTH_RAW_DATA_AND_FIGURES
)
if not ns_parser:
return
if ns_parser:
if self.ticker:
quiverquant_view.display_hist_contracts(
ticker=self.ticker, raw=ns_parser.raw, export=ns_parser.export
)
else:
print("No ticker loaded. Use `load <ticker>` first.\n")
if self._check_ticker():
quiverquant_view.display_hist_contracts(
ticker=self.ticker, export=ns_parser.export
)
return self.queue
@try_except
def call_lobbying(self, other_args: List[str]):
@@ -583,55 +683,99 @@ Ticker: {self.ticker or None}{dim_no_ticker}
description="Lobbying details [Source: www.quiverquant.com]",
)
parser.add_argument(
"-n",
"--num",
"-l",
"--limit",
action="store",
dest="num",
dest="limit",
type=check_positive,
default=10,
help="Number of events to show",
help="Limit of events to show",
)
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 not ns_parser:
return
if ns_parser:
if self.ticker:
quiverquant_view.display_lobbying(
ticker=self.ticker,
num=ns_parser.limit,
)
else:
print("No ticker loaded. Use `load <ticker>` first.\n")
if self._check_ticker():
quiverquant_view.display_lobbying(ticker=self.ticker, num=ns_parser.num)
return self.queue
def menu(ticker: str):
def menu(ticker: str, queue: List[str] = None):
"""Government Menu"""
gov_controller = GovController(ticker)
gov_controller.call_help(None)
gov_controller = GovController(ticker, queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if gov_controller.queue and len(gov_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if gov_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(gov_controller.queue) > 1:
return gov_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = gov_controller.queue[0]
gov_controller.queue = gov_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 gov_controller.CHOICES_COMMANDS:
print(f"{get_flair()} /stocks/gov/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in gov_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (stocks)>(gov)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (stocks)>(gov)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
gov_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and gov_controller.completer:
an_input = session.prompt(
f"{get_flair()} /stocks/gov/ $ ",
completer=gov_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /stocks/gov/ $ ")
try:
plt.close("all")
process_input = gov_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
gov_controller.queue = gov_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
similar_cmd = difflib.get_close_matches(
an_input, gov_controller.CHOICES, n=1, cutoff=0.7
print(
f"\nThe command '{an_input}' doesn't exist on the /stocks/gov menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
gov_controller.CHOICES,
n=1,
cutoff=0.7,
)
if similar_cmd:
print(f"Did you mean '{similar_cmd[0]}'?\n")
continue
if " " in an_input:
candidate_input = (
f"{similar_cmd[0]} {' '.join(an_input.split(' ')[1:])}"
)
if candidate_input == an_input:
an_input = ""
gov_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
gov_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -318,7 +318,7 @@ def display_government_sells(
fig.tight_layout()
plt.show()
print("")
export_data(export, os.path.dirname(os.path.abspath(__file__)), "top_sells", df_gov)
export_data(export, os.path.dirname(os.path.abspath(__file__)), "topsells", df_gov)
def display_last_contracts(
@@ -386,9 +386,7 @@ def display_last_contracts(
fig.tight_layout()
plt.show()
print("")
export_data(
export, os.path.dirname(os.path.abspath(__file__)), "last_contracts", df
)
export_data(export, os.path.dirname(os.path.abspath(__file__)), "lastcontracts", df)
def plot_government(government: pd.DataFrame, ticker: str, gov_type: str):
@@ -431,7 +429,11 @@ def plot_government(government: pd.DataFrame, ticker: str, gov_type: str):
def display_government_trading(
ticker: str, gov_type: str, past_transactions_months: int = 6, raw: bool = False
ticker: str,
gov_type: str,
past_transactions_months: int = 6,
raw: bool = False,
export: str = "",
):
"""Government trading for specific ticker [Source: quiverquant.com]
@@ -445,6 +447,8 @@ def display_government_trading(
Number of months to get transactions for
raw: bool
Show raw output of trades
export: str
Format to export data
"""
df_gov = quiverquant_model.get_government_trading(gov_type, ticker)
@@ -500,8 +504,10 @@ def display_government_trading(
)
else:
print(df_gov.to_string())
else:
plot_government(df_gov, ticker, gov_type)
plot_government(df_gov, ticker, gov_type)
export_data(export, os.path.dirname(os.path.abspath(__file__)), "gtrades", df_gov)
print("")
@@ -535,17 +541,6 @@ def display_contracts(
df_contracts.drop_duplicates(inplace=True)
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
df_contracts.groupby("Date").sum().div(1000).plot(kind="bar", rot=0, ax=ax)
ax.set_ylabel("Amount ($1k)")
ax.set_title(f"Sum of latest government contracts to {ticker}")
fig.tight_layout()
export_data(export, os.path.dirname(os.path.abspath(__file__)), "contracts")
if gtff.USE_ION:
plt.ion()
if raw:
if gtff.USE_TABULATE_DF:
print(
@@ -560,11 +555,25 @@ def display_contracts(
else:
print(df_contracts.to_string())
plt.show()
else:
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
df_contracts.groupby("Date").sum().div(1000).plot(kind="bar", rot=0, ax=ax)
ax.set_ylabel("Amount ($1k)")
ax.set_title(f"Sum of latest government contracts to {ticker}")
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%Y/%m/%d"))
plt.gcf().autofmt_xdate()
fig.tight_layout()
if gtff.USE_ION:
plt.ion()
plt.show()
export_data(
export, os.path.dirname(os.path.abspath(__file__)), "contracts", df_contracts
)
print("")
def display_qtr_contracts(analysis: str, num: int, export: str = ""):
def display_qtr_contracts(analysis: str, num: int, raw: bool = False, export: str = ""):
"""Quarterly contracts [Source: quiverquant.com]
Parameters
@@ -573,6 +582,8 @@ def display_qtr_contracts(analysis: str, num: int, export: str = ""):
Analysis to perform. Either 'total', 'upmom' 'downmom'
num: int
Number to show
raw: bool
Flag to display raw data
export: str
Format to export data
"""
@@ -583,66 +594,68 @@ def display_qtr_contracts(analysis: str, num: int, export: str = ""):
return
tickers = quiverquant_model.analyze_qtr_contracts(analysis, num)
if analysis in {"upmom", "downmom"}:
if gtff.USE_TABULATE_DF:
print(
tabulate(
pd.DataFrame(tickers.values),
headers=["tickers"],
tablefmt="fancy_grid",
showindex=False,
if analysis in ("upmom", "downmom"):
if raw:
if gtff.USE_TABULATE_DF:
print(
tabulate(
pd.DataFrame(tickers.values),
headers=["tickers"],
tablefmt="fancy_grid",
showindex=False,
)
)
)
else:
print(tickers.to_string())
else:
print(tickers.to_string())
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
max_amount = 0
quarter_ticks = []
for symbol in tickers:
amounts = (
df_contracts[df_contracts["Ticker"] == symbol]
.sort_values(by=["Year", "Qtr"])["Amount"]
.values
)
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
max_amount = 0
quarter_ticks = []
for symbol in tickers:
amounts = (
df_contracts[df_contracts["Ticker"] == symbol]
.sort_values(by=["Year", "Qtr"])["Amount"]
.values
)
qtr = (
df_contracts[df_contracts["Ticker"] == symbol]
.sort_values(by=["Year", "Qtr"])["Qtr"]
.values
)
year = (
df_contracts[df_contracts["Ticker"] == symbol]
.sort_values(by=["Year", "Qtr"])["Year"]
.values
)
qtr = (
df_contracts[df_contracts["Ticker"] == symbol]
.sort_values(by=["Year", "Qtr"])["Qtr"]
.values
)
year = (
df_contracts[df_contracts["Ticker"] == symbol]
.sort_values(by=["Year", "Qtr"])["Year"]
.values
)
ax.plot(np.arange(0, len(amounts)), amounts / 1_000_000, "-*", lw=2, ms=15)
ax.plot(
np.arange(0, len(amounts)), amounts / 1_000_000, "-*", lw=2, ms=15
)
if len(amounts) > max_amount:
max_amount = len(amounts)
quarter_ticks = [
f"{quarter[0]} - Q{quarter[1]} " for quarter in zip(year, qtr)
]
if len(amounts) > max_amount:
max_amount = len(amounts)
quarter_ticks = [
f"{quarter[0]} - Q{quarter[1]} " for quarter in zip(year, qtr)
]
ax.set_xlim([-0.5, max_amount - 0.5])
ax.set_xticks(np.arange(0, max_amount))
ax.set_xticklabels(quarter_ticks)
ax.grid()
ax.legend(tickers)
titles = {
"upmom": "Highest increasing quarterly Government Contracts",
"downmom": "Highest decreasing quarterly Government Contracts",
}
ax.set_title(titles[analysis])
ax.set_xlabel("Date")
ax.set_ylabel("Amount ($1M)")
fig.tight_layout()
export_data(export, os.path.dirname(os.path.abspath(__file__)), "qtr_contracts")
ax.set_xlim([-0.5, max_amount - 0.5])
ax.set_xticks(np.arange(0, max_amount))
ax.set_xticklabels(quarter_ticks)
ax.grid()
ax.legend(tickers)
titles = {
"upmom": "Highest increasing quarterly Government Contracts",
"downmom": "Highest decreasing quarterly Government Contracts",
}
ax.set_title(titles[analysis])
ax.set_xlabel("Date")
ax.set_ylabel("Amount ($1M)")
fig.tight_layout()
if gtff.USE_ION:
plt.ion()
if gtff.USE_ION:
plt.ion()
plt.show()
plt.show()
elif analysis == "total":
if gtff.USE_TABULATE_DF:
@@ -654,16 +667,21 @@ def display_qtr_contracts(analysis: str, num: int, export: str = ""):
else:
print(tickers.to_string())
export_data(
export, os.path.dirname(os.path.abspath(__file__)), "qtrcontracts", df_contracts
)
print("")
def display_hist_contracts(ticker: str, export: str = ""):
def display_hist_contracts(ticker: str, raw: bool = False, export: str = ""):
"""Show historical quarterly government contracts [Source: quiverquant.com]
Parameters
----------
ticker: str
Ticker to get congress trading data from
raw: bool
Flag to display raw data
export: str
Format to export data
"""
@@ -683,23 +701,39 @@ def display_hist_contracts(ticker: str, export: str = ""):
quarter_ticks = [
f"{quarter[0]}" if quarter[1] == 1 else "" for quarter in zip(year, qtr)
]
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
ax.plot(np.arange(0, len(amounts)), amounts / 1000, "-*", lw=2, ms=15)
if raw:
if gtff.USE_TABULATE_DF:
print(
tabulate(
df_contracts,
headers=df_contracts.columns,
tablefmt="fancy_grid",
showindex=False,
)
)
else:
print(df_contracts.to_string())
ax.set_xlim([-0.5, len(amounts) - 0.5])
ax.set_xticks(np.arange(0, len(amounts)))
ax.set_xticklabels(quarter_ticks)
ax.grid()
ax.set_title(f"Historical Quarterly Government Contracts for {ticker.upper()}")
ax.set_xlabel("Date")
ax.set_ylabel("Amount ($1k)")
fig.tight_layout()
if gtff.USE_ION:
plt.ion()
else:
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
plt.show()
export_data(export, os.path.dirname(os.path.abspath(__file__)), "hist_cont")
ax.plot(np.arange(0, len(amounts)), amounts / 1000, "-*", lw=2, ms=15)
ax.set_xlim([-0.5, len(amounts) - 0.5])
ax.set_xticks(np.arange(0, len(amounts)))
ax.set_xticklabels(quarter_ticks)
ax.grid()
ax.set_title(f"Historical Quarterly Government Contracts for {ticker.upper()}")
ax.set_xlabel("Date")
ax.set_ylabel("Amount ($1k)")
fig.tight_layout()
if gtff.USE_ION:
plt.ion()
plt.show()
export_data(export, os.path.dirname(os.path.abspath(__file__)), "histcont")
print("")
@@ -726,16 +760,6 @@ def display_top_lobbying(num: int, raw: bool = False, export: str = ""):
lobbying_by_ticker = pd.DataFrame(
df_lobbying.groupby("Ticker")["Amount"].agg("sum")
).sort_values(by="Amount", ascending=False)
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
lobbying_by_ticker.head(num).plot(kind="bar", ax=ax)
ax.set_xlabel("Ticker")
ax.set_ylabel("Total Amount ($100k)")
ax.set_title(f"Corporate Lobbying Spent since {df_lobbying['Date'].min()}")
fig.tight_layout()
if gtff.USE_ION:
plt.ion()
plt.show()
if raw:
if gtff.USE_TABULATE_DF:
@@ -750,9 +774,20 @@ def display_top_lobbying(num: int, raw: bool = False, export: str = ""):
)
else:
print(lobbying_by_ticker.head(num).to_string())
else:
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
lobbying_by_ticker.head(num).plot(kind="bar", ax=ax)
ax.set_xlabel("Ticker")
ax.set_ylabel("Total Amount ($100k)")
ax.set_title(f"Corporate Lobbying Spent since {df_lobbying['Date'].min()}")
fig.tight_layout()
if gtff.USE_ION:
plt.ion()
plt.show()
print("")
export_data(
export, os.path.dirname(os.path.abspath(__file__)), "top_lobbying", df_lobbying
export, os.path.dirname(os.path.abspath(__file__)), "lobbying", df_lobbying
)

View File

@@ -6,6 +6,7 @@ import pandas as pd
from tabulate import tabulate
from gamestonk_terminal.stocks.insider import finviz_model
from gamestonk_terminal.helper_funcs import export_data
from gamestonk_terminal import feature_flags as gtff
def last_insider_activity(ticker: str, num: int, export: str):
@@ -36,15 +37,18 @@ def last_insider_activity(ticker: str, num: int, export: str):
]
]
print(
tabulate(
df.head(num),
tablefmt="fancy_grid",
floatfmt=".2f",
headers=list(df.columns),
showindex=True,
if gtff.USE_TABULATE_DF:
print(
tabulate(
df.head(num),
tablefmt="fancy_grid",
floatfmt=".2f",
headers=list(df.columns),
showindex=True,
)
)
)
else:
print(df.to_string())
print("")
export_data(

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,5 @@
import argparse
import os
import textwrap
from typing import List
import itertools
from bs4 import BeautifulSoup
import requests
@@ -9,10 +8,8 @@ import pandas as pd
from tabulate import tabulate
from colorama import Fore, Style
from gamestonk_terminal.helper_funcs import (
check_positive,
parse_known_args_and_warn,
patch_pandas_text_adjustment,
try_except,
export_data,
)
from gamestonk_terminal.stocks.insider.openinsider_model import (
get_open_insider_link,
@@ -125,37 +122,18 @@ def green_highlight(values):
return [f"{Fore.GREEN}{val}{Style.RESET_ALL}" for val in values]
@try_except
def print_insider_data(other_args: List[str], type_insider: str):
def print_insider_data(type_insider: str, limit: int = 10, export: str = ""):
"""Print insider data
Parameters
----------
other_args : List[str]
Command line arguments to be processed with argparse
type_insider: str
Insider type of data
limit: int
Limit of data rows to display
export: str
Export data format
"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog=type_insider,
description=f"Print {d_open_insider[type_insider].replace('-', ' ')} [Source: OpenInsider]",
)
parser.add_argument(
"-n",
"--num",
action="store",
dest="num",
type=check_positive,
default=20,
help="Number of datarows to display",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
response = requests.get(f"http://openinsider.com/{d_open_insider[type_insider]}")
soup = BeautifulSoup(response.text, "html.parser")
table = soup.find("table", {"class": "tinytable"})
@@ -172,7 +150,7 @@ def print_insider_data(other_args: List[str], type_insider: str):
row = [tr.text.strip() for tr in td if tr.text.strip()]
res.append(row)
df = pd.DataFrame(res).dropna().head(n=ns_parser.num)
df = pd.DataFrame(res).dropna().head(n=limit)
df.columns = [
"X",
@@ -208,15 +186,21 @@ def print_insider_data(other_args: List[str], type_insider: str):
lambda x: "\n".join(textwrap.wrap(x, width=20)) if isinstance(x, str) else x
)
print(
tabulate(
df,
headers=df.columns,
tablefmt="fancy_grid",
stralign="right",
showindex=False,
if gtff.USE_TABULATE_DF:
print(
tabulate(
df,
headers=df.columns,
tablefmt="fancy_grid",
stralign="right",
showindex=False,
)
)
)
else:
print(df.to_string())
export_data(export, os.path.dirname(os.path.abspath(__file__)), type_insider, df)
l_chars = [list(chars) for chars in df["X"].values]
l_uchars = np.unique(list(itertools.chain(*l_chars)))
@@ -225,56 +209,30 @@ def print_insider_data(other_args: List[str], type_insider: str):
print("")
@try_except
def print_insider_filter(other_args: List[str], preset_loaded: str):
"""Print insider filter based on loaded preset
def print_insider_filter(
preset_loaded: str,
ticker: str,
limit: int = 10,
links: bool = False,
export: str = "",
):
"""Print insider filter based on loaded preset. [Source: OpenInsider]
Parameters
----------
other_args : List[str]
Command line arguments to be processed with argparse
preset_loaded: str
preset_loaded : str
Loaded preset filter
ticker : str
Stock ticker
limit : int
Limit of rows of data to display
links : bool
Flag to show hyperlinks
export : str
Format to export data
"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="filter",
description="Print open insider filtered data using loaded preset, or selected ticker. [Source: OpenInsider]",
)
parser.add_argument(
"-n",
"--num",
action="store",
dest="num",
type=check_positive,
default=20,
help="Number of datarows to display",
)
parser.add_argument(
"-t",
"--ticker",
action="store",
dest="ticker",
type=str,
default="",
help="Filter latest insiders from this ticker",
)
parser.add_argument(
"-l",
"--links",
action="store_true",
default=False,
help="Flag to show hyperlinks",
dest="links",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser.ticker:
link = f"http://openinsider.com/screener?s={ns_parser.ticker}"
if ticker:
link = f"http://openinsider.com/screener?s={ticker}"
else:
link = get_open_insider_link(preset_loaded)
@@ -282,25 +240,23 @@ def print_insider_filter(other_args: List[str], preset_loaded: str):
print("")
return
df_insider = get_open_insider_data(
link, has_company_name=bool(not ns_parser.ticker)
)
df_insider = get_open_insider_data(link, has_company_name=bool(not ticker))
df_insider_orig = df_insider.copy()
if df_insider.empty:
print("")
print("No insider data found\n")
return
if ns_parser.links:
if links:
df_insider = df_insider[["Ticker Link", "Insider Link", "Filing Link"]].head(
ns_parser.num
limit
)
else:
df_insider = df_insider.drop(
columns=["Filing Link", "Ticker Link", "Insider Link"]
).head(ns_parser.num)
).head(limit)
if gtff.USE_COLOR and not ns_parser.links:
if gtff.USE_COLOR and not links:
if not df_insider[df_insider["Trade Type"] == "S - Sale"].empty:
df_insider[df_insider["Trade Type"] == "S - Sale"] = df_insider[
df_insider["Trade Type"] == "S - Sale"
@@ -330,9 +286,26 @@ def print_insider_filter(other_args: List[str], preset_loaded: str):
df_insider = df_insider.drop(columns=["Filing Date"])
print("")
print(df_insider.to_string(index=False))
if gtff.USE_TABULATE_DF:
print(
tabulate(
df_insider,
headers=df_insider.columns,
tablefmt="fancy_grid",
)
)
else:
print(df_insider.to_string(index=False))
if not ns_parser.links:
if export:
if preset_loaded:
cmd = "filter"
if ticker:
cmd = "lis"
export_data(export, os.path.dirname(os.path.abspath(__file__)), cmd, df_insider)
if not links:
l_chars = [list(chars) for chars in df_insider_orig["X"].values]
l_uchars = np.unique(list(itertools.chain(*l_chars)))
print("")

File diff suppressed because it is too large Load Diff

View File

@@ -2,13 +2,13 @@
__docformat__ = "numpy"
import argparse
from typing import List, Dict
import matplotlib.pyplot as plt
import difflib
from typing import List, Dict, Union
from prompt_toolkit.completion import NestedCompleter
from colorama import Style
from gamestonk_terminal import feature_flags as gtff
from gamestonk_terminal.helper_funcs import (
check_non_negative,
get_flair,
parse_known_args_and_warn,
try_except,
@@ -19,25 +19,42 @@ from gamestonk_terminal.stocks.options.yfinance_model import get_option_chain, g
from gamestonk_terminal.stocks.options.yfinance_view import plot_payoff
class PayoffController:
"""Payoff Controller class."""
# pylint: disable=R0902
CHOICES = ["cls", "?", "help", "q", "quit"]
class PayoffController:
"""Payoff Controller class"""
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = [
"list",
"add",
"rmv",
"pick",
"plot",
"sop",
]
CHOICES += CHOICES_COMMANDS
# pylint: disable=dangerous-default-value
def __init__(self, ticker: str, expiration: str):
"""Construct Payoff"""
underlying_asset_choices = ["long", "short", "none"]
self.payoff_parser = argparse.ArgumentParser(add_help=False, prog="po")
def __init__(self, ticker: str, expiration: str, queue: List[str] = None):
"""Construct"""
self.payoff_parser = argparse.ArgumentParser(add_help=False, prog="payoff")
self.payoff_parser.add_argument("cmd", choices=self.CHOICES)
self.chain = get_option_chain(ticker, expiration)
self.calls = list(
zip(
@@ -52,10 +69,28 @@ class PayoffController:
)
)
self.ticker = ticker
self.current_price = get_price(ticker)
self.expiration = expiration
self.options: List[Dict[str, str]] = []
self.underlying = 0
self.current_price = get_price(ticker)
self.call_index_choices = range(len(self.calls))
self.put_index_choices = range(len(self.puts))
self.completer: Union[None, NestedCompleter] = None
if session and gtff.USE_PROMPT_TOOLKIT:
self.choices: dict = {c: {} for c in self.CHOICES}
self.choices["pick"] = {c: {} for c in self.underlying_asset_choices}
self.choices["add"] = {
str(c): {} for c in list(range(max(len(self.puts), len(self.calls))))
}
if queue:
self.queue = queue
else:
self.queue = list()
def print_help(self):
"""Print help"""
@@ -67,23 +102,19 @@ class PayoffController:
text = "Short"
help_text = f"""
What do you want to do?
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon program
Ticker: {self.ticker or None}
Expiry: {self.expiration or None}
Current Ticker: {self.ticker or None}
Current Expiry: {self.expiration or None}
pick long, short, or none (default) underlying asset
Underlying Asset: {text}
list list available strike prices for calls and puts
pick long, short, or none (default) underlying asset
add add option to the list of the options to be plotted
rmv remove option from the list of the options to be plotted
add add option to the list of the options to be plotted{Style.DIM if not self.options else ""}
rmv remove option from the list of the options to be plotted{Style.RESET_ALL}
sop selected options
plot show the option payoff diagram
"""
print(help_text)
@@ -93,28 +124,40 @@ Underlying Asset: {text}
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.payoff_parser.parse_known_args(an_input.split())
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -122,107 +165,89 @@ Underlying Asset: {text}
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program"""
return True
def call_list(self, _):
"""Lists available calls and puts"""
length = max(len(self.calls), len(self.puts)) - 1
print(
"Add an option using the index on the left, and not the actual strike price"
)
print("#\tcall\tput")
for i in range(length):
call = self.calls[i][0] if i < len(self.calls) else ""
put = self.puts[i][0] if i < len(self.puts) else ""
print(f"{i}\t{call}\t{put}")
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_add(self, other_args: List[str]):
"""Process add command"""
self.add_option(other_args)
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit", "quit"]
def call_rmv(self, other_args: List[str]):
"""Process rmv command"""
self.rmv_option(other_args)
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "payoff")
if self.expiration:
self.queue.insert(0, f"exp {self.expiration}")
if self.ticker:
self.queue.insert(0, f"load {self.ticker}")
self.queue.insert(0, "options")
self.queue.insert(0, "stocks")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_pick(self, other_args: List[str]):
"""Process pick command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="long",
description="This function plots option payoff diagrams",
)
parser.add_argument(
"-t",
"--type",
dest="type",
type=str,
help="choose what you would like to do with the underlying asset, choose from: long, short, or none",
required="-h" not in other_args,
)
reset_commands = ["quit", "quit", "quit", "reset", "stocks", "options"]
if self.ticker:
reset_commands.append(f"load {self.ticker}")
if self.expiration:
reset_commands.append(f"exp -d {self.expiration}")
reset_commands.append("payoff")
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-t")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser.type == "long":
self.underlying = 1
elif ns_parser.type == "none":
self.underlying = 0
elif ns_parser.type == "short":
self.underlying = -1
self.print_help()
return reset_commands
@try_except
def call_plot(self, other_args):
"""Process plot command"""
def call_list(self, other_args):
"""Lists available calls and puts"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="plot",
description="This function plots option payoff diagrams",
prog="list",
description="""Lists available calls and puts.""",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
plot_payoff(
self.current_price,
self.options,
self.underlying,
self.ticker,
self.expiration,
)
def show_setup(self, nl: bool = False):
"""Shows the current assets to display in the diagram"""
print("#\tType\tHold\tStrike\tCost")
for i, o in enumerate(self.options):
sign = "Long" if o["sign"] == 1 else "Short"
print(f"{i}\t{o['type']}\t{sign}\t{o['strike']}\t{o['cost']}")
if nl:
if ns_parser:
length = max(len(self.calls), len(self.puts)) - 1
print("#\tcall\tput")
for i in range(length):
call = self.calls[i][0] if i < len(self.calls) else ""
put = self.puts[i][0] if i < len(self.puts) else ""
print(f"{i}\t{call}\t{put}")
print("")
return self.queue
@try_except
def add_option(self, other_args: List[str]):
"""Add an option to the diagram"""
def call_add(self, other_args: List[str]):
"""Process add command"""
parser = argparse.ArgumentParser(
add_help=False,
prog="add",
@@ -248,70 +273,61 @@ Underlying Asset: {text}
"-i",
"--index",
dest="index",
type=int,
type=check_non_negative,
help="list index of the option",
required="-h" not in other_args and "-k" not in other_args,
choices=self.put_index_choices
if "-p" in other_args
else self.call_index_choices,
)
parser.add_argument(
"-k",
"--strike",
dest="strike",
type=float,
help="strike price for the option",
default=None,
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-i")
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-i")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
opt_type = "put" if ns_parser.put else "call"
sign = -1 if ns_parser.short else 1
options_list = self.puts if ns_parser.put else self.calls
opt_type = "put" if ns_parser.put else "call"
sign = -1 if ns_parser.short else 1
options_list = self.puts if ns_parser.put else self.calls
if ns_parser.strike is None:
index = ns_parser.index
elif float(ns_parser.strike) in [float(x[0]) for x in options_list]:
index = filter(
lambda x: float(x[0]) == float(ns_parser.strike), options_list
)
index = options_list.index(list(index)[0])
print(index)
else:
print("Invalid strike price, please select a valid price from the list")
return
try:
strike = options_list[index][0]
cost = options_list[index][1]
except IndexError:
print("Please use the index, and not the strike price\n")
return
if ns_parser.index < len(options_list):
strike = options_list[ns_parser.index][0]
cost = options_list[ns_parser.index][1]
option = {"type": opt_type, "sign": sign, "strike": strike, "cost": cost}
self.options.append(option)
option = {
"type": opt_type,
"sign": sign,
"strike": strike,
"cost": cost,
}
self.options.append(option)
self.show_setup(True)
print("#\tType\tHold\tStrike\tCost")
for i, o in enumerate(self.options):
asset: str = "Long" if o["sign"] == 1 else "Short"
print(f"{i}\t{o['type']}\t{asset}\t{o['strike']}\t{o['cost']}")
print("")
else:
print("Please use a valid index\n")
return self.queue
@try_except
def rmv_option(self, other_args: List[str]):
"""Remove one of the options from the diagram"""
def call_rmv(self, other_args: List[str]):
"""Process rmv command"""
parser = argparse.ArgumentParser(
add_help=False,
prog="rmv",
description="""Remove one of the options to be shown in the payoff.""",
)
parser.add_argument(
"-k",
"--strike",
dest="strike",
type=int,
help="strike price for option",
"-i",
"--index",
dest="index",
type=check_non_negative,
help="index of the option to remove",
required=bool("-h" not in other_args and len(self.options) > 0),
choices=range(len(self.options)),
)
parser.add_argument(
"-a",
"--all",
@@ -320,52 +336,179 @@ Underlying Asset: {text}
help="remove all of the options",
default=False,
)
if other_args:
if "-" not in other_args[0]:
other_args.insert(0, "-k")
if other_args and "-" not in other_args[0][0]:
other_args.insert(0, "-i")
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if ns_parser:
if self.options:
if ns_parser.all:
self.options = []
else:
if ns_parser.index < len(self.options):
del self.options[ns_parser.index]
else:
print("Please use a valid index.\n")
if ns_parser.all:
self.options = []
print("#\tType\tHold\tStrike\tCost")
for i, o in enumerate(self.options):
sign = "Long" if o["sign"] == 1 else "Short"
print(f"{i}\t{o['type']}\t{sign}\t{o['strike']}\t{o['cost']}")
print("")
else:
try:
del self.options[ns_parser.strike]
except IndexError:
print("Please use the index, and not the strike price\n")
return
print("No options have been selected, removing them is not possible\n")
self.show_setup(True)
return self.queue
def call_pick(self, other_args: List[str]):
"""Process pick command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="long",
description="This function plots option payoff diagrams",
)
parser.add_argument(
"-t",
"--type",
dest="underlyingtype",
type=str,
help="Choose what you would like to do with the underlying asset",
required="-h" not in other_args,
choices=self.underlying_asset_choices,
)
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:
if ns_parser.underlyingtype == "long":
self.underlying = 1
elif ns_parser.underlyingtype == "none":
self.underlying = 0
elif ns_parser.underlyingtype == "short":
self.underlying = -1
print("")
return self.queue
@try_except
def call_sop(self, other_args):
"""Process sop command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="sop",
description="Displays selected option",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
print("#\tType\tHold\tStrike\tCost")
for i, o in enumerate(self.options):
sign = "Long" if o["sign"] == 1 else "Short"
print(f"{i}\t{o['type']}\t{sign}\t{o['strike']}\t{o['cost']}")
print("")
return self.queue
@try_except
def call_plot(self, other_args):
"""Process plot command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="plot",
description="This function plots option payoff diagrams",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
plot_payoff(
self.current_price,
self.options,
self.underlying,
self.ticker,
self.expiration,
)
return self.queue
def menu(ticker: str, expiration: str):
"""Options Payoff Menu"""
plt.close("all")
payoff_controller = PayoffController(ticker, expiration)
payoff_controller.call_help(None)
def menu(ticker: str, expiration: str, queue: List[str] = None):
"""Payoff Menu"""
payoff_controller = PayoffController(ticker, expiration, queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if payoff_controller.queue and len(payoff_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if payoff_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(payoff_controller.queue) > 1:
return payoff_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = payoff_controller.queue[0]
payoff_controller.queue = payoff_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 payoff_controller.CHOICES_COMMANDS
):
print(f"{get_flair()} /stocks/options/payoff/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in payoff_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (stocks)>(options)>(payoff)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (stocks)>(options)>(payoff)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
payoff_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and payoff_controller.choices:
if payoff_controller.options:
payoff_controller.choices["rmv"] = {
str(c): {} for c in range(len(payoff_controller.options))
}
completer = NestedCompleter.from_nested_dict(payoff_controller.choices)
an_input = session.prompt(
f"{get_flair()} /stocks/options/payoff/ $ ",
completer=completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /stocks/options/payoff/ $ ")
try:
plt.close("all")
process_input = payoff_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
payoff_controller.queue = payoff_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
continue
print(
f"\nThe command '{an_input}' doesn't exist on the /stocks/options/payoff menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
payoff_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:])}"
)
if candidate_input == an_input:
an_input = ""
payoff_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
payoff_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -2,8 +2,8 @@
__docformat__ = "numpy"
import argparse
from typing import List
import matplotlib.pyplot as plt
import difflib
from typing import List, Union
import pandas as pd
from tabulate import tabulate
from prompt_toolkit.completion import NestedCompleter
@@ -20,9 +20,21 @@ from gamestonk_terminal.stocks.options import yfinance_view
class PricingController:
"""Pricing Controller class."""
"""Pricing Controller class"""
CHOICES = ["cls", "?", "help", "q", "quit"]
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = [
"add",
"rmv",
@@ -31,13 +43,29 @@ class PricingController:
]
CHOICES += CHOICES_COMMANDS
# pylint: disable=dangerous-default-value
def __init__(self, ticker: str, selected_date: str, prices: pd.DataFrame):
"""Construct Pricing"""
def __init__(
self,
ticker: str,
selected_date: str,
prices: pd.DataFrame,
queue: List[str] = None,
):
"""Construct"""
self.pricing_parser = argparse.ArgumentParser(add_help=False, prog="pricing")
self.pricing_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()
self.ticker = ticker
self.selected_date = selected_date
self.prices = prices
@@ -45,18 +73,12 @@ class PricingController:
def print_help(self):
"""Print help"""
help_text = f"""
What do you want to do?
cls clear screen
?/help show this menu again
q quit this menu, and shows back to main menu
quit quit to abandon program
Ticker: {self.ticker or None}
Expiry: {self.selected_date or None}
Current Ticker: {self.ticker or None}
Current Expiry: {self.selected_date or None}
Options Pricing:
add add an expected price to the list
rmv remove an expected price from the list
show show the listed of expected prices
rnval risk neutral valuation for an option
"""
@@ -67,30 +89,42 @@ Options Pricing:
Returns
-------
True, False or None
False - quit the menu
True - quit the program
None - continue in the menu
List[str]
List of commands in the queue to execute
"""
# Empty command
if not an_input:
print("")
return None
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.pricing_parser.parse_known_args(
an_input.split()
)
# Help menu again
if known_args.cmd == "?":
self.print_help()
return None
# Clear screen
if known_args.cmd == "cls":
system_clear()
return None
# 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"
return getattr(
self,
@@ -98,17 +132,65 @@ Options Pricing:
lambda _: "Command not recognized!",
)(other_args)
def call_help(self, _):
"""Process Help command"""
self.print_help()
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_q(self, _):
"""Process Q command - quit the menu"""
return False
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process Quit command - quit the program"""
return True
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "pricing")
if self.selected_date:
self.queue.insert(0, f"exp {self.selected_date}")
if self.ticker:
self.queue.insert(0, f"load {self.ticker}")
self.queue.insert(0, "options")
self.queue.insert(0, "stocks")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
reset_commands = ["quit", "quit", "quit", "reset", "stocks", "options"]
if self.ticker:
reset_commands.append(f"load {self.ticker}")
if self.selected_date:
reset_commands.append(f"exp -d {self.selected_date}")
reset_commands.append("pricing")
return reset_commands
@try_except
def call_add(self, other_args: List[str]):
@@ -135,18 +217,21 @@ Options Pricing:
dest="chance",
help="Chance that the stock is at a given projected price",
)
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 not ns_parser:
return
if ns_parser.price in self.prices["Price"].to_list():
df = self.prices[(self.prices["Price"] != ns_parser.price)]
else:
df = self.prices
if ns_parser:
if ns_parser.price in self.prices["Price"].to_list():
df = self.prices[(self.prices["Price"] != ns_parser.price)]
else:
df = self.prices
new = {"Price": ns_parser.price, "Chance": ns_parser.chance}
df = df.append(new, ignore_index=True)
self.prices = df.sort_values("Price")
print("")
new = {"Price": ns_parser.price, "Chance": ns_parser.chance}
df = df.append(new, ignore_index=True)
self.prices = df.sort_values("Price")
print("")
return self.queue
@try_except
def call_rmv(self, other_args: List[str]):
@@ -173,36 +258,45 @@ Options Pricing:
dest="all",
help="Remove all prices from the list",
)
if other_args and "-" not in other_args[0]:
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 not ns_parser:
return
if ns_parser.all:
self.prices = pd.DataFrame(columns=["Price", "Chance"])
return
if ns_parser:
if ns_parser.all:
self.prices = pd.DataFrame(columns=["Price", "Chance"])
else:
self.prices = self.prices[(self.prices["Price"] != ns_parser.price)]
print("")
self.prices = self.prices[(self.prices["Price"] != ns_parser.price)]
print("")
return self.queue
@try_except
def call_show(self, _):
def call_show(self, other_args):
"""Process show command"""
print(f"Estimated price(s) of {self.ticker} at {self.selected_date}")
if gtff.USE_TABULATE_DF:
print(
tabulate(
self.prices,
headers=self.prices.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(self.prices.to_string, "\n")
print("")
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="show",
description="Display prices",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if ns_parser:
print(f"Estimated price(s) of {self.ticker} at {self.selected_date}")
if gtff.USE_TABULATE_DF:
print(
tabulate(
self.prices,
headers=self.prices.columns,
floatfmt=".2f",
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(self.prices.to_string, "\n")
return self.queue
@try_except
def call_rnval(self, other_args: List[str]):
@@ -245,53 +339,104 @@ Options Pricing:
help="The risk-free rate to use",
)
ns_parser = parse_known_args_and_warn(parser, other_args)
if not ns_parser:
return
if not self.ticker and not self.selected_date:
print("Ticker and expiration required. \n")
return
if sum(self.prices["Chance"]) != 1:
print("Total chances must equal one\n")
return
if ns_parser:
if self.ticker:
if self.selected_date:
if sum(self.prices["Chance"]) == 1:
yfinance_view.risk_neutral_vals(
self.ticker,
self.selected_date,
ns_parser.put,
self.prices,
ns_parser.mini,
ns_parser.maxi,
ns_parser.risk,
)
else:
print("Total chances must equal one\n")
else:
print("No expiry loaded. First use `exp {expiry date}`\n")
else:
print("No ticker loaded. First use `load <ticker>`\n")
yfinance_view.risk_neutral_vals(
self.ticker,
self.selected_date,
ns_parser.put,
self.prices,
ns_parser.mini,
ns_parser.maxi,
ns_parser.risk,
)
return self.queue
def menu(ticker: str, selected_date: str, prices: pd.DataFrame):
"""Options Pricing Menu"""
plt.close("all")
pricing_controller = PricingController(ticker, selected_date, prices)
pricing_controller.call_help(None)
def menu(
ticker: str, selected_date: str, prices: pd.DataFrame, queue: List[str] = None
):
"""Pricing Menu"""
pricing_controller = PricingController(ticker, selected_date, prices, queue)
an_input = "HELP_ME"
while True:
# There is a command in the queue
if pricing_controller.queue and len(pricing_controller.queue) > 0:
# If the command is quitting the menu we want to return in here
if pricing_controller.queue[0] in ("q", "..", "quit"):
print("")
if len(pricing_controller.queue) > 1:
return pricing_controller.queue[1:]
return []
# Consume 1 element from the queue
an_input = pricing_controller.queue[0]
pricing_controller.queue = pricing_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 pricing_controller.CHOICES_COMMANDS
):
print(f"{get_flair()} /stocks/options/pricing/ $ {an_input}")
# Get input command from user
if session and gtff.USE_PROMPT_TOOLKIT:
completer = NestedCompleter.from_nested_dict(
{c: None for c in pricing_controller.CHOICES}
)
an_input = session.prompt(
f"{get_flair()} (stocks)>(options)>(pricing)> ",
completer=completer,
)
else:
an_input = input(f"{get_flair()} (stocks)>(options)>(pricing)> ")
# Display help menu when entering on this menu from a level above
if an_input == "HELP_ME":
pricing_controller.print_help()
# Get input from user using auto-completion
if session and gtff.USE_PROMPT_TOOLKIT and pricing_controller.completer:
an_input = session.prompt(
f"{get_flair()} /stocks/options/pricing/ $ ",
completer=pricing_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /stocks/options/pricing/ $ ")
try:
plt.close("all")
process_input = pricing_controller.switch(an_input)
if process_input is not None:
return process_input
# Process the input command
pricing_controller.queue = pricing_controller.switch(an_input)
except SystemExit:
print("The command selected doesn't exist\n")
continue
print(
f"\nThe command '{an_input}' doesn't exist on the /stocks/options/pricing menu.",
end="",
)
similar_cmd = difflib.get_close_matches(
an_input.split(" ")[0] if " " in an_input else an_input,
pricing_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:])}"
)
if candidate_input == an_input:
an_input = ""
pricing_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
pricing_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -0,0 +1,393 @@
""" Options Screener Controller Module """
__docformat__ = "numpy"
import argparse
import difflib
import os
from typing import List, Union
from colorama import Style
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,
check_positive,
get_flair,
parse_known_args_and_warn,
system_clear,
try_except,
)
from gamestonk_terminal.menu import session
from gamestonk_terminal.portfolio.portfolio_optimization import po_controller
from gamestonk_terminal.stocks.comparison_analysis import ca_controller
from gamestonk_terminal.stocks.options import syncretism_view
# pylint: disable=E1121
class ScreenerController:
"""Screener Controller class"""
# Command choices
CHOICES = [
"cls",
"home",
"h",
"?",
"help",
"q",
"quit",
"..",
"exit",
"r",
"reset",
]
CHOICES_COMMANDS = ["view", "set", "scr"]
CHOICES_MENUS = [
"ca",
"po",
]
CHOICES += CHOICES_COMMANDS + CHOICES_MENUS
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")
]
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}
self.completer = NestedCompleter.from_nested_dict(choices)
self.preset = "high_IV"
self.screen_tickers: List = list()
if queue:
self.queue = queue
else:
self.queue = list()
def print_help(self):
"""Print help"""
help_text = f"""
view view available presets (or one in particular)
set set one of the available presets
PRESET: {self.preset}
scr screen data from this preset
{Style.NORMAL if self.screen_tickers else Style.DIM}
Last screened tickers: {', '.join(self.screen_tickers)}
> ca take these to comparison analysis menu
> po take these to portoflio optimization menu{Style.RESET_ALL}
"""
print(help_text)
def switch(self, an_input: str):
"""Process and dispatch input
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"
return getattr(
self,
"call_" + known_args.cmd,
lambda _: "Command not recognized!",
)(other_args)
def call_cls(self, _):
"""Process cls command"""
system_clear()
return self.queue
def call_home(self, _):
"""Process home command"""
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
def call_help(self, _):
"""Process help command"""
self.print_help()
return self.queue
def call_quit(self, _):
"""Process quit menu command"""
print("")
if len(self.queue) > 0:
self.queue.insert(0, "quit")
return self.queue
return ["quit"]
def call_exit(self, _):
"""Process exit terminal command"""
# additional quit for when we come to this menu through a relative path
if len(self.queue) > 0:
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit"]
def call_reset(self, _):
"""Process reset command"""
if len(self.queue) > 0:
self.queue.insert(0, "scr")
self.queue.insert(0, "options")
self.queue.insert(0, "stocks")
self.queue.insert(0, "reset")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
self.queue.insert(0, "quit")
return self.queue
return ["quit", "quit", "quit", "reset", "stocks", "options", "scr"]
@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:
syncretism_view.view_available_presets(
preset=ns_parser.preset, presets_path=self.presets_path
)
else:
for preset in self.preset_choices:
print(preset)
print("")
return self.queue
@try_except
def call_set(self, other_args: List[str]):
"""Process set command"""
parser = argparse.ArgumentParser(
add_help=False,
prog="set",
description="""Set preset from custom and default ones.""",
)
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("")
return self.queue
@try_except
def call_scr(self, other_args: List[str]):
"""Process scr command"""
parser = argparse.ArgumentParser(
add_help=False,
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
prog="scr",
description="""Screener filter output from https://ops.syncretism.io/index.html.
Where: CS: Contract Symbol; S: Symbol, T: Option Type; Str: Strike; Exp v: Expiration;
IV: Implied Volatility; LP: Last Price; B: Bid; A: Ask; V: Volume; OI: Open Interest;
Y: Yield; MY: Monthly Yield; SMP: Regular Market Price; SMDL: Regular Market Day Low;
SMDH: Regular Market Day High; LU: Last Trade Date; LC: Last Crawl; ITM: In The Money;
PC: Price Change; PB: Price-to-book. """,
)
parser.add_argument(
"-p",
"--preset",
action="store",
dest="preset",
type=str,
default="template",
help="Filter presets",
choices=self.preset_choices,
)
parser.add_argument(
"-l",
"--limit",
type=check_positive,
default=10,
help="Limit of random entries to display. Default shows all",
dest="limit",
)
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, EXPORT_ONLY_RAW_DATA_ALLOWED
)
if ns_parser:
self.screen_tickers = syncretism_view.view_screener_output(
preset=ns_parser.preset,
presets_path=self.presets_path,
n_show=ns_parser.limit,
export=ns_parser.export,
)
return self.queue
@try_except
def call_po(self, _):
"""Call the portfolio optimization menu with selected tickers"""
if self.screen_tickers:
return po_controller.menu(self.screen_tickers, from_submenu=True)
print("Some tickers must be screened first through one of the presets!\n")
return self.queue
@try_except
def call_ca(self, _):
"""Call the comparison analysis menu with selected tickers"""
if self.screen_tickers:
return ca_controller.menu(
self.screen_tickers, self.queue, from_submenu=True
)
print("Some tickers must be screened first through one of the presets!\n")
return self.queue
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"):
print("")
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()} /stocks/options/screen/ $ {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()} /stocks/options/screen/ $ ",
completer=scr_controller.completer,
search_ignore_case=True,
)
# Get input from user without auto-completion
else:
an_input = input(f"{get_flair()} /stocks/options/screen/ $ ")
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 /stocks/options/screen 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:])}"
)
if candidate_input == an_input:
an_input = ""
scr_controller.queue = []
print("\n")
continue
an_input = candidate_input
else:
an_input = similar_cmd[0]
print(f" Replacing by '{an_input}'.")
scr_controller.queue.insert(0, an_input)
else:
print("\n")

View File

@@ -1,8 +1,9 @@
"""Helper functions for scraping options data"""
"""Syncretistm View module"""
__docformat__ = "numpy"
import configparser
import os
from typing import List
import matplotlib.pyplot as plt
from tabulate import tabulate
@@ -35,7 +36,7 @@ def view_available_presets(preset: str, presets_path: str):
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))
max_len = len(max(d_filters, key=len)) + 2
for key, value in d_filters.items():
print(f"{key}{(max_len-len(key))*' '}: {value}")
print("")
@@ -45,7 +46,9 @@ def view_available_presets(preset: str, presets_path: str):
print("")
def view_screener_output(preset: str, presets_path: str, n_show: int, export: str):
def view_screener_output(
preset: str, presets_path: str, n_show: int, export: str
) -> List:
"""Print the output of screener
Parameters
@@ -58,11 +61,16 @@ def view_screener_output(preset: str, presets_path: str, n_show: int, export: st
Number of randomly sorted rows to display
export: str
Format for export file
Returns
-------
List
List of tickers screened
"""
df_res, error_msg = syncretism_model.get_screener_output(preset, presets_path)
if error_msg:
print(error_msg, "\n")
return
return []
export_data(
export,
@@ -74,15 +82,20 @@ def view_screener_output(preset: str, presets_path: str, n_show: int, export: st
if n_show > 0:
df_res = df_res.sample(n_show)
print(
tabulate(
df_res,
headers=df_res.columns,
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
if gtff.USE_TABULATE_DF:
print(
tabulate(
df_res,
headers=df_res.columns,
showindex=False,
tablefmt="fancy_grid",
),
"\n",
)
else:
print(df_res.to_string())
return df_res["S"].values.tolist()
# pylint:disable=too-many-arguments
@@ -127,14 +140,6 @@ def view_historical_greeks(
if raw:
print(tabulate(df.tail(n_show), headers=df.columns, tablefmt="fancy_grid"))
if export:
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"grhist",
df,
)
fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfp.PLOT_DPI)
im1 = ax.plot(df.index, df[greek], c="firebrick", label=greek)
ax.set_ylabel(greek)
@@ -156,4 +161,10 @@ def view_historical_greeks(
plt.legend(ims, labels, loc=0)
fig.tight_layout(pad=1)
plt.show()
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"grhist",
df,
)
print("")

View File

@@ -116,14 +116,6 @@ def display_chains(
columns = to_display + ["strike", "option_type"]
chains_df = chains_df[columns].rename(columns=column_map)
if export:
export_data(
export,
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"chains",
chains_df,
)
if min_sp == -1:
min_strike = np.percentile(chains_df["strike"], 25)
else:
@@ -197,6 +189,13 @@ def display_chains(
"\n",
)
export_data(
export,
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
"chains",
chains_df,
)
def plot_oi(
ticker: str,
@@ -228,12 +227,6 @@ def plot_oi(
"""
options = tradier_model.get_option_chains(ticker, expiry)
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"oi_tr",
options,
)
current_price = tradier_model.last_price(ticker)
if min_sp == -1:
@@ -301,7 +294,12 @@ def plot_oi(
fig.tight_layout(pad=1)
plt.show()
plt.style.use("default")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"oi_tr",
options,
)
print("")
@@ -335,12 +333,6 @@ def plot_vol(
"""
options = tradier_model.get_option_chains(ticker, expiry)
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"vol_tr",
options,
)
current_price = tradier_model.last_price(ticker)
if min_sp == -1:
@@ -398,7 +390,12 @@ def plot_vol(
fig.tight_layout(pad=1)
plt.show()
plt.style.use("default")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"vol_tr",
options,
)
print("")
@@ -429,12 +426,6 @@ def plot_volume_open_interest(
"""
current_price = tradier_model.last_price(ticker)
options = tradier_model.get_option_chains(ticker, expiry)
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"voi_tr",
options,
)
calls = options[options.option_type == "call"][
["strike", "volume", "open_interest"]
@@ -587,7 +578,12 @@ def plot_volume_open_interest(
if gtff.USE_ION:
plt.ion()
plt.show()
plt.style.use("default")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"voi_tr",
options,
)
print("")

View File

@@ -115,24 +115,21 @@ def plot_oi(
ls="-",
c="g",
)
ax.axvline(
current_price, lw=2, c="k", ls="--", label="Current Price", alpha=0.7
)
ax.axvline(max_pain, lw=3, c="k", label=f"Max Pain: {max_pain}", alpha=0.7)
ax.grid("on")
ax.set_xlabel("Strike Price")
ax.set_ylabel("Open Interest (1k) ")
ax.set_xlim(min_strike, max_strike)
ax.axvline(current_price, lw=2, c="k", ls="--", label="Current Price", alpha=0.7)
ax.axvline(max_pain, lw=3, c="k", label=f"Max Pain: {max_pain}", alpha=0.7)
ax.grid("on")
ax.set_xlabel("Strike Price")
ax.set_ylabel("Open Interest (1k) ")
ax.set_xlim(min_strike, max_strike)
if gtff.USE_ION:
plt.ion()
if gtff.USE_ION:
plt.ion()
ax.set_title(f"Open Interest for {ticker.upper()} expiring {expiry}")
plt.legend(loc=0)
fig.tight_layout(pad=1)
ax.set_title(f"Open Interest for {ticker.upper()} expiring {expiry}")
plt.legend(loc=0)
fig.tight_layout(pad=1)
plt.show()
plt.style.use("default")
print("")
@@ -165,12 +162,6 @@ def plot_vol(
Format to export file
"""
options = yfinance_model.get_option_chain(ticker, expiry)
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"vol_yf",
options,
)
calls = options.calls
puts = options.puts
current_price = float(yf.Ticker(ticker).info["regularMarketPrice"])
@@ -228,7 +219,12 @@ def plot_vol(
fig.tight_layout(pad=1)
plt.show()
plt.style.use("default")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"vol_yf",
options,
)
print("")
@@ -259,12 +255,6 @@ def plot_volume_open_interest(
"""
options = yfinance_model.get_option_chain(ticker, expiry)
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"voi_yf",
options,
)
calls = options.calls
puts = options.puts
current_price = float(yf.Ticker(ticker).info["regularMarketPrice"])
@@ -415,14 +405,39 @@ def plot_volume_open_interest(
if gtff.USE_ION:
plt.ion()
plt.show()
plt.style.use("default")
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"voi_yf",
options,
)
print("")
def plot_plot(
ticker: str, expiration: str, put: bool, x: str, y: str, custom: str
ticker: str, expiration: str, put: bool, x: str, y: str, custom: str, export: str
) -> None:
"""Generate a graph custom graph based on user input"""
"""Generate a graph custom graph based on user input
Parameters
----------
ticker: str
Stock ticker
expiration: str
Option expiration
min_sp: float
Min strike price
put: bool
put option instead of call
x: str
variable to display in x axis
y: str
variable to display in y axis
custom: str
type of plot
export: str
type of data to export
"""
convert = {
"ltd": "lastTradeDate",
"s": "strike",
@@ -465,7 +480,11 @@ def plot_plot(
plt.gca().yaxis.set_major_locator(mdates.DayLocator(interval=1))
elif varis[y]["format"]:
ax.yaxis.set_major_formatter(varis[y]["format"])
if gtff.USE_ION:
plt.ion()
plt.show()
export_data(export, os.path.dirname(os.path.abspath(__file__)), "plot")
print("")
def plot_payoff(
@@ -489,12 +508,14 @@ def plot_payoff(
ax.xaxis.set_major_formatter("${x:.2f}")
ax.yaxis.set_major_formatter("${x:.2f}")
plt.legend()
if gtff.USE_ION:
plt.ion()
plt.show()
print("")
def show_parity(
ticker: str, exp: str, put: bool, ask: bool, mini: float, maxi: float
ticker: str, exp: str, put: bool, ask: bool, mini: float, maxi: float, export: str
) -> None:
"""Prints options and whether they are under or over priced [Source: Yahoo Finance]
@@ -512,7 +533,8 @@ def show_parity(
Minimum strike price to show
maxi : float
Maximum strike price to show
export : str
Export data
"""
r_date = datetime.strptime(exp, "%Y-%m-%d").date()
delta = (r_date - date.today()).days
@@ -593,6 +615,12 @@ def show_parity(
else:
print(show.to_string(index=False))
export_data(
export,
os.path.dirname(os.path.abspath(__file__)),
"parity",
show,
)
print("")

Some files were not shown because too many files have changed in this diff Show More