From a13be8d678feefb5384aabddd89f283c66ec784f Mon Sep 17 00:00:00 2001 From: jmaslek Date: Wed, 3 Nov 2021 09:44:24 -0400 Subject: [PATCH] Redo some forex menu with basic commands (#903) * Make oanda menu and add some alpha vantage forex * ignore csv for codespell * Added load and candle plot for forex * Add hugo stuff for forex commands * fix merge error Co-authored-by: James Maslek --- .github/workflows/test.yml | 2 +- .pre-commit-config.yaml | 2 +- .../forex/av_forex_currencies.csv | 160 +++ gamestonk_terminal/forex/av_model.py | 114 +++ gamestonk_terminal/forex/av_view.py | 80 ++ gamestonk_terminal/forex/forex_controller.py | 276 +++--- .../forex/oanda/oanda_controller.py | 221 +++++ gamestonk_terminal/forex/oanda/oanda_model.py | 2 + gamestonk_terminal/forex/oanda/oanda_view.py | 917 ++++++++++++++++++ website/content/forex/candle/_index.md | 10 + website/content/forex/load/_index.md | 16 + website/content/forex/quote/_index.md | 10 + website/content/forex/select/_index.md | 14 + website/data/menu/main.yml | 7 + 14 files changed, 1720 insertions(+), 111 deletions(-) create mode 100644 gamestonk_terminal/forex/av_forex_currencies.csv create mode 100644 gamestonk_terminal/forex/av_model.py create mode 100644 gamestonk_terminal/forex/av_view.py create mode 100644 gamestonk_terminal/forex/oanda/oanda_controller.py create mode 100644 gamestonk_terminal/forex/oanda/oanda_model.py create mode 100644 gamestonk_terminal/forex/oanda/oanda_view.py create mode 100644 website/content/forex/candle/_index.md create mode 100644 website/content/forex/load/_index.md create mode 100644 website/content/forex/quote/_index.md create mode 100644 website/content/forex/select/_index.md diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4dd27c59787..950254a53e8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,7 +30,7 @@ jobs: - run: pip install types-pytz types-requests types-termcolor types-tabulate types-PyYAML types-python-dateutil - run: bandit -r . || true - run: black --check . - - run: codespell --ignore-words-list=pres,hsi,hist,mape,ba,ser,coo --quiet-level=2 --skip=".git" --skip="*.yaml" --skip="*.ipynb" --skip="*.json" --skip="*.js" --skip="*.css" --skip="*.scss" --skip="*.html" --skip="*.ini" --skip="*.lock" + - run: codespell --ignore-words-list=pres,hsi,hist,mape,ba,ser,coo --quiet-level=2 --skip=".git" --skip="*.yaml" --skip="*.ipynb" --skip="*.json" --skip="*.js" --skip="*.css" --skip="*.scss" --skip="*.html" --skip="*.ini" --skip="*.lock" --skip="*.csv" - run: flake8 . --count --ignore=E203,W503 --max-line-length=122 --show-source --statistics - run: mypy --ignore-missing-imports --exclude '/setup\.py$' . - run: shopt -s globstar && pyupgrade --py36-plus **/*.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 811fe787de4..d55231aaf63 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: hooks: - id: codespell entry: codespell - args: [ '--ignore-words-list=pres,coo,navagation,ser,buil,operatio,mape,ba,hist,yeld,ether,hsi', '--quiet-level=2', '--skip=".git,*.html,*.yaml,*.json,./tests"' ] + args: [ '--ignore-words-list=pres,coo,navagation,ser,buil,operatio,mape,ba,hist,yeld,ether,hsi', '--quiet-level=2', '--skip=".git,*.html,*.yaml,*.json,./tests,*.csv"' ] - repo: https://github.com/pre-commit/mirrors-mypy rev: 'v0.812' hooks: diff --git a/gamestonk_terminal/forex/av_forex_currencies.csv b/gamestonk_terminal/forex/av_forex_currencies.csv new file mode 100644 index 00000000000..2d5ee3dd6e3 --- /dev/null +++ b/gamestonk_terminal/forex/av_forex_currencies.csv @@ -0,0 +1,160 @@ +currency code,currency name +AED,United Arab Emirates Dirham +AFN,Afghan Afghani +ALL,Albanian Lek +AMD,Armenian Dram +ANG,Netherlands Antillean Guilder +AOA,Angolan Kwanza +ARS,Argentine Peso +AUD,Australian Dollar +AWG,Aruban Florin +AZN,Azerbaijani Manat +BAM,Bosnia-Herzegovina Convertible Mark +BBD,Barbadian Dollar +BDT,Bangladeshi Taka +BGN,Bulgarian Lev +BHD,Bahraini Dinar +BIF,Burundian Franc +BMD,Bermudan Dollar +BND,Brunei Dollar +BOB,Bolivian Boliviano +BRL,Brazilian Real +BSD,Bahamian Dollar +BTN,Bhutanese Ngultrum +BWP,Botswanan Pula +BZD,Belize Dollar +CAD,Canadian Dollar +CDF,Congolese Franc +CHF,Swiss Franc +CLF,Chilean Unit of Account UF +CLP,Chilean Peso +CNH,Chinese Yuan Offshore +CNY,Chinese Yuan +COP,Colombian Peso +CUP,Cuban Peso +CVE,Cape Verdean Escudo +CZK,Czech Republic Koruna +DJF,Djiboutian Franc +DKK,Danish Krone +DOP,Dominican Peso +DZD,Algerian Dinar +EGP,Egyptian Pound +ERN,Eritrean Nakfa +ETB,Ethiopian Birr +EUR,Euro +FJD,Fijian Dollar +FKP,Falkland Islands Pound +GBP,British Pound Sterling +GEL,Georgian Lari +GHS,Ghanaian Cedi +GIP,Gibraltar Pound +GMD,Gambian Dalasi +GNF,Guinean Franc +GTQ,Guatemalan Quetzal +GYD,Guyanaese Dollar +HKD,Hong Kong Dollar +HNL,Honduran Lempira +HRK,Croatian Kuna +HTG,Haitian Gourde +HUF,Hungarian Forint +ICP,Internet Computer +IDR,Indonesian Rupiah +ILS,Israeli New Sheqel +INR,Indian Rupee +IQD,Iraqi Dinar +IRR,Iranian Rial +ISK,Icelandic Krona +JEP,Jersey Pound +JMD,Jamaican Dollar +JOD,Jordanian Dinar +JPY,Japanese Yen +KES,Kenyan Shilling +KGS,Kyrgystani Som +KHR,Cambodian Riel +KMF,Comorian Franc +KPW,North Korean Won +KRW,South Korean Won +KWD,Kuwaiti Dinar +KYD,Cayman Islands Dollar +KZT,Kazakhstani Tenge +LAK,Laotian Kip +LBP,Lebanese Pound +LKR,Sri Lankan Rupee +LRD,Liberian Dollar +LSL,Lesotho Loti +LYD,Libyan Dinar +MAD,Moroccan Dirham +MDL,Moldovan Leu +MGA,Malagasy Ariary +MKD,Macedonian Denar +MMK,Myanma Kyat +MNT,Mongolian Tugrik +MOP,Macanese Pataca +MRO,Mauritanian Ouguiya (pre-2018) +MRU,Mauritanian Ouguiya +MUR,Mauritian Rupee +MVR,Maldivian Rufiyaa +MWK,Malawian Kwacha +MXN,Mexican Peso +MYR,Malaysian Ringgit +MZN,Mozambican Metical +NAD,Namibian Dollar +NGN,Nigerian Naira +NOK,Norwegian Krone +NPR,Nepalese Rupee +NZD,New Zealand Dollar +OMR,Omani Rial +PAB,Panamanian Balboa +PEN,Peruvian Nuevo Sol +PGK,Papua New Guinean Kina +PHP,Philippine Peso +PKR,Pakistani Rupee +PLN,Polish Zloty +PYG,Paraguayan Guarani +QAR,Qatari Rial +RON,Romanian Leu +RSD,Serbian Dinar +RUB,Russian Ruble +RUR,Old Russian Ruble +RWF,Rwandan Franc +SAR,Saudi Riyal +SBDf,Solomon Islands Dollar +SCR,Seychellois Rupee +SDG,Sudanese Pound +SDR,Special Drawing Rights +SEK,Swedish Krona +SGD,Singapore Dollar +SHIB,Shiba Inu +SHP,Saint Helena Pound +SLL,Sierra Leonean Leone +SOS,Somali Shilling +SRD,Surinamese Dollar +SYP,Syrian Pound +SZL,Swazi Lilangeni +THB,Thai Baht +TJS,Tajikistani Somoni +TMT,Turkmenistani Manat +TND,Tunisian Dinar +TOP,Tongan Pa'anga +TRY,Turkish Lira +TTD,Trinidad and Tobago Dollar +TWD,New Taiwan Dollar +TZS,Tanzanian Shilling +UAH,Ukrainian Hryvnia +UGX,Ugandan Shilling +USD,United States Dollar +UYU,Uruguayan Peso +UZS,Uzbekistan Som +VND,Vietnamese Dong +VUV,Vanuatu Vatu +WBTC,Wrapped Bitcoin +WST,Samoan Tala +XAF,CFA Franc BEAC +XCD,East Caribbean Dollar +XDR,Special Drawing Rights +XOF,CFA Franc BCEAO +XPF,CFP Franc +YER,Yemeni Rial +ZAR,South African Rand +ZMW,Zambian Kwacha +ZWL,Zimbabwean Dollar diff --git a/gamestonk_terminal/forex/av_model.py b/gamestonk_terminal/forex/av_model.py new file mode 100644 index 00000000000..cbc59bc1dab --- /dev/null +++ b/gamestonk_terminal/forex/av_model.py @@ -0,0 +1,114 @@ +"""AlphaVantage FX Model""" + +import argparse +from typing import Dict +import os +import pandas as pd +import requests +from gamestonk_terminal import config_terminal as cfg + + +def check_valid_forex_currency(fx_symbol: str) -> str: + """Check if given symbol is supported on alphavantage + + Parameters + ---------- + fx_symbol : str + Symbol to check + + Returns + ------- + str + Currency symbol + + Raises + ------ + 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"]): + return fx_symbol.upper() + + raise argparse.ArgumentTypeError( + f"{fx_symbol.upper()} not found in alphavantage supported currency codes. " + ) + + +def get_quote(to_symbol: str, from_symbol: str) -> Dict: + """Get current exchange rate quote from alpha vantage + + Parameters + ---------- + to_symbol : str + To forex symbol + from_symbol : str + From forex symbol + + Returns + ------- + 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}" + r = requests.get(url) + if r.status_code != 200: + return {} + return r.json() + + +def get_historical( + to_symbol: str, + from_symbol: str, + resolution: str = "d", + interval: int = 5, + start_date: str = "", +) -> pd.DataFrame: + """Get historical forex data + + Parameters + ---------- + to_symbol : str + To forex symbol + from_symbol : str + From forex symbol + resolution : str, optional + Resolution of data. Can be "i", "d", "w", "m" for intraday, daily, weekly or monthly + interval : int, optional + Interval for intraday data + start_date : str, optional + Start date for data. + + Returns + ------- + pd.DataFrame + Historical data for forex pair + """ + d_res = {"i": "FX_INTRADAY", "d": "FX_DAILY", "w": "FX_WEEKLY", "m": "FX_MONTHLY"} + + url = f"https://www.alphavantage.co/query?function={d_res[resolution]}&from_symbol={from_symbol}" + url += f"&to_symbol={to_symbol}&outputsize=full&apikey={cfg.API_KEY_ALPHAVANTAGE}" + if resolution == "i": + url += f"&interval={interval}min" + + r = requests.get(url) + if r.status_code != 200: + return pd.DataFrame() + + key = list(r.json().keys())[1] + + df = pd.DataFrame.from_dict(r.json()[key], orient="index") + if start_date and resolution != "i": + df = df[df.index > start_date] + + df = df.rename( + columns={ + "1. open": "Open", + "2. high": "High", + "3. low": "Low", + "4. close": "Close", + } + ) + df.index = pd.DatetimeIndex(df.index) + return df.astype(float) diff --git a/gamestonk_terminal/forex/av_view.py b/gamestonk_terminal/forex/av_view.py new file mode 100644 index 00000000000..9ccbb627cdc --- /dev/null +++ b/gamestonk_terminal/forex/av_view.py @@ -0,0 +1,80 @@ +"""AlphaVantage Forex View""" +__docformat__ = "numpy" + +import pandas as pd +from tabulate import tabulate +import mplfinance as mpf +import matplotlib.pyplot as plt +from gamestonk_terminal.forex import av_model +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import plot_autoscale + + +def display_quote(to_symbol: str, from_symbol: str): + """Display current forex pair exchange rate + + Parameters + ---------- + to_symbol : str + To symbol + from_symbol : str + From forex symbol + """ + quote = av_model.get_quote(to_symbol, from_symbol) + + if not quote: + print("Quote not pulled from AlphaVantage. Check API key.") + return + + df = pd.DataFrame.from_dict(quote) + df.index = df.index.to_series().apply(lambda x: x[3:]).values + df = df.iloc[[0, 2, 5, 4, 7, 8]] + if gtff.USE_TABULATE_DF: + print(tabulate(df, tablefmt="fancy_grid")) + else: + print(df.to_string()) + print("") + + +def display_candle(data: pd.DataFrame, to_symbol: str, from_symbol: str): + """Show candle plot for fx data + + Parameters + ---------- + data : pd.DataFrame + Loaded fx historical data + to_symbol : str + To forex symbol + from_symbol : str + From forex symbol + """ + mc = mpf.make_marketcolors( + up="green", + down="red", + edge="black", + wick="black", + volume="in", + ohlc="i", + ) + + s = mpf.make_mpf_style(marketcolors=mc, gridstyle=":", y_on_right=True) + mpf.plot( + data, + type="candle", + mav=(20, 50), + volume=False, + title=f"\n{to_symbol}/{from_symbol}", + xrotation=10, + style=s, + figratio=(10, 7), + figscale=1.10, + figsize=(plot_autoscale()), + update_width_config=dict( + candle_linewidth=1.0, + candle_width=0.8, + ), + ) + if gtff.USE_ION: + plt.ion() + plt.show() + print("") diff --git a/gamestonk_terminal/forex/forex_controller.py b/gamestonk_terminal/forex/forex_controller.py index 8fad8f77c44..707d8fa65fc 100644 --- a/gamestonk_terminal/forex/forex_controller.py +++ b/gamestonk_terminal/forex/forex_controller.py @@ -1,15 +1,15 @@ import argparse import os +from datetime import timedelta, datetime from typing import List -# import pandas as pd +import pandas as pd from prompt_toolkit.completion import NestedCompleter - -from gamestonk_terminal import config_terminal as cfg +from colorama import Style from gamestonk_terminal import feature_flags as gtff -from gamestonk_terminal.forex import fx_view +from gamestonk_terminal.forex.oanda import oanda_controller +from gamestonk_terminal.forex import av_view, av_model -# from gamestonk_terminal.forex.behavioural_analysis import ba_controller # from gamestonk_terminal.forex.exploratory_data_analysis import eda_controller from gamestonk_terminal.helper_funcs import ( @@ -17,14 +17,12 @@ from gamestonk_terminal.helper_funcs import ( MENU_GO_BACK, MENU_QUIT, MENU_RESET, + parse_known_args_and_warn, + try_except, + valid_date, ) from gamestonk_terminal.menu import session -# from gamestonk_terminal.stocks.due_diligence import news_view, reddit_view - - -account = cfg.OANDA_ACCOUNT - class ForexController: """Forex Controller class""" @@ -38,32 +36,11 @@ class ForexController: "reset", ] - CHOICES_COMMANDS = [ - "price", - "summary", - "list", - "orderbook", - "positionbook", - "order", - "load", - "cancel", - "positions", - "closetrade", - "trades", - "candles", - "pending", - "calendar", - # "news", - # "reddit", - ] - - # CHOICES_MENUS = [ - # "eda", - # "ba", - # ] + CHOICES_COMMANDS = ["select", "load", "quote", "candle"] + CHOICES_MENUS = ["oanda"] CHOICES += CHOICES_COMMANDS - # CHOICES += CHOICES_MENUS + CHOICES += CHOICES_MENUS def __init__(self): """Construct Data""" @@ -72,44 +49,37 @@ class ForexController: "cmd", choices=self.CHOICES, ) - self.instrument = None + self.from_symbol = "USD" + self.to_symbol = "" + self.data = pd.DataFrame() def print_help(self): """Print help""" - print("\nForex Mode:") - print(" cls clear screen") - 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(" news print news [News API]") - print( - " reddit search reddit for posts about the loaded instrument" - ) - # print("") - # print( - # "> ba behavioural analysis, from: reddit, stocktwits, twitter, google" - # ) - print("") + dim_bool = self.from_symbol and self.to_symbol + help_str = f""" +>>> FOREX <<< + +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 + select select fx pair + +To: {None or self.to_symbol} +From: {None or self.from_symbol} +{Style.DIM if not dim_bool else ""} +AlphaVantage (API Key required): + quote get last quote + load get historical data + candle show candle plot for loaded data +{Style.RESET_ALL} +Brokerages: +> oanda access oanda menu + + """ + print(help_str) def switch(self, an_input: str): """Process and dispatch input @@ -159,59 +129,147 @@ class ForexController: """Process Reset command - reset the program""" return MENU_RESET - def call_price(self, other_args): - """Process Price Command""" - fx_view.get_fx_price(account, self.instrument, other_args) + @try_except + def call_select(self, other_args: List[str]): + """Process select command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="select", + description="Select Forex pair in the form of TO -f FROM", + ) + parser.add_argument( + "-t", + "--to", + help="To currency", + type=av_model.check_valid_forex_currency, + dest="to_symbol", + ) + parser.add_argument( + "-f", + "--from", + help="From currency", + type=av_model.check_valid_forex_currency, + dest="from_symbol", + default=None, + ) - def call_load(self, other_args): - self.instrument = fx_view.load(other_args) + if ( + other_args + and "-f" not in other_args[0] + and "--from" not in other_args[0] + and "-t" not in other_args + and "-h" not in other_args + ): + other_args.insert(0, "-t") - def call_summary(self, other_args): - """Process account summary command""" - fx_view.get_account_summary(account, other_args) + 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: + self.from_symbol = ns_parser.from_symbol + print("") - def call_orderbook(self, other_args): - """Process Oanda Order Book""" - fx_view.get_order_book(self.instrument, other_args) + @try_except + def call_load(self, other_args: List[str]): + """Process select command""" + parser = argparse.ArgumentParser( + add_help=False, + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + prog="load", + description="Load historical exchange rate data.", + ) + parser.add_argument( + "-r", + "--resolution", + choices=["i", "d", "w", "m"], + default="d", + help="Resolution of data. Can be intraday, daily, weekly or monthly", + dest="resolution", + ) + parser.add_argument( + "-i", + "--interval", + choices=[1, 5, 15, 30, 60], + default="5", + help="Interval of intraday data. Can be 1, 5, 15, 30 or 60.", + dest="interval", + ) + parser.add_argument( + "-s", + "--start_date", + default=(datetime.now() - timedelta(days=366)), + type=valid_date, + help="Start date of data.", + dest="start_date", + ) - def call_positionbook(self, other_args): - """Process Oanda Position Book""" - fx_view.get_position_book(self.instrument, other_args) + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return - def call_list(self, other_args: List[str]): - """Process list orders command""" - fx_view.list_orders(account, other_args) + if not self.to_symbol or not self.from_symbol: + print( + "Make sure both a to symbol and a from symbol are supplied using \n" + ) + return - def call_trades(self, other_args): - """List open trades""" - fx_view.get_open_trades(account, other_args) + av_view.display_quote(self.to_symbol, self.from_symbol) - def call_calendar(self, other_args: List[str]): - """Call calendar""" - fx_view.calendar(self.instrument, other_args) + # pylint: disable=inconsistent-return-statements + def call_oanda(self, _): + ret = oanda_controller.menu() + if ret: + return True + self.print_help() # TODO: Add news and reddit commands back diff --git a/gamestonk_terminal/forex/oanda/oanda_controller.py b/gamestonk_terminal/forex/oanda/oanda_controller.py new file mode 100644 index 00000000000..ab24779e249 --- /dev/null +++ b/gamestonk_terminal/forex/oanda/oanda_controller.py @@ -0,0 +1,221 @@ +"""Oanda Controller""" +__docformat__ = "numpy" +import argparse +import os +from typing import List + +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.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, +) +from gamestonk_terminal.menu import session + + +account = cfg.OANDA_ACCOUNT + + +class OandaController: + + CHOICES = [ + "cls", + "?", + "help", + "q", + "quit", + "reset", + ] + + CHOICES_COMMANDS = [ + "price", + "summary", + "list", + "orderbook", + "positionbook", + "order", + "load", + "cancel", + "positions", + "closetrade", + "trades", + "candles", + "pending", + "calendar", + # "news", + # "reddit", + ] + CHOICES += CHOICES_COMMANDS + + def __init__(self): + """Construct Data""" + self.fx_parser = argparse.ArgumentParser(add_help=False, prog="oanda") + self.fx_parser.add_argument( + "cmd", + choices=self.CHOICES, + ) + self.instrument = None + + 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("") + + def switch(self, an_input: str): + """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 + """ + + # Empty command + if not an_input: + print("") + return None + + (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 + + 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 MENU_GO_BACK + + def call_quit(self, _): + """Process Quit command - exit the program""" + return MENU_QUIT + + def call_reset(self, _): + """Process Reset command - reset the program""" + return MENU_RESET + + 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) + + def call_summary(self, other_args): + """Process account summary command""" + oanda_view.get_account_summary(account, other_args) + + def call_orderbook(self, other_args): + """Process Oanda Order Book""" + oanda_view.get_order_book(self.instrument, other_args) + + def call_positionbook(self, other_args): + """Process Oanda Position Book""" + oanda_view.get_position_book(self.instrument, other_args) + + def call_list(self, other_args: List[str]): + """Process list orders command""" + oanda_view.list_orders(account, other_args) + + def call_order(self, other_args: List[str]): + """Place limit order""" + oanda_view.create_order(account, self.instrument, other_args) + + def call_cancel(self, other_args: List[str]): + """Cancel pending order by ID""" + oanda_view.cancel_pending_order(account, other_args) + + def call_positions(self, other_args): + """Get Open Positions""" + oanda_view.get_open_positions(account, other_args) + + 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) + + def call_trades(self, other_args): + """List open trades""" + oanda_view.get_open_trades(account, other_args) + + def call_calendar(self, other_args: List[str]): + """Call calendar""" + oanda_view.calendar(self.instrument, other_args) + + +def menu(): + """Forex Menu""" + oanda_controller = OandaController() + oanda_controller.call_help(None) + while True: + if session and gtff.USE_PROMPT_TOOLKIT: + completer = NestedCompleter.from_nested_dict( + {c: None for c in oanda_controller.CHOICES} + ) + + an_input = session.prompt( + f"{get_flair()} (forex)(oanda)> ", + completer=completer, + ) + 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 + + except SystemExit: + print("The command selected doesn't exit\n") + continue diff --git a/gamestonk_terminal/forex/oanda/oanda_model.py b/gamestonk_terminal/forex/oanda/oanda_model.py new file mode 100644 index 00000000000..50845540784 --- /dev/null +++ b/gamestonk_terminal/forex/oanda/oanda_model.py @@ -0,0 +1,2 @@ +"""Oanda Model""" +__docformat__ = "numpy" diff --git a/gamestonk_terminal/forex/oanda/oanda_view.py b/gamestonk_terminal/forex/oanda/oanda_view.py new file mode 100644 index 00000000000..8141127fef9 --- /dev/null +++ b/gamestonk_terminal/forex/oanda/oanda_view.py @@ -0,0 +1,917 @@ +"""Oanda View""" +__docformat__ = "numpy" + +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, +) + +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 +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. ", + ) + + try: + 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() + + except Exception as e: + print(e, "\n") + return None + + +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("") diff --git a/website/content/forex/candle/_index.md b/website/content/forex/candle/_index.md new file mode 100644 index 00000000000..1f62aff9033 --- /dev/null +++ b/website/content/forex/candle/_index.md @@ -0,0 +1,10 @@ +``` +usage: candle [-h] +``` + +Show candle for loaded fx data + +``` +optional arguments: + -h, --help show this help message (default: False) +``` diff --git a/website/content/forex/load/_index.md b/website/content/forex/load/_index.md new file mode 100644 index 00000000000..4d9b0115a9d --- /dev/null +++ b/website/content/forex/load/_index.md @@ -0,0 +1,16 @@ +``` +usage: load [-r {i,d,w,m}] [-i {1,5,15,30,60}] [-s START_DATE] [-h] +``` + +Load historical exchange rate data. + +``` +optional arguments: + -r {i,d,w,m}, --resolution {i,d,w,m} + Resolution of data. Can be intraday, daily, weekly or monthly (default: d) + -i {1,5,15,30,60}, --interval {1,5,15,30,60} + Interval of intraday data. Can be 1, 5, 15, 30 or 60. (default: 5) + -s START_DATE, --start_date START_DATE + Start date of data. (default: 2020-11-01 16:41:01.349343) + -h, --help show this help message (default: False) +``` diff --git a/website/content/forex/quote/_index.md b/website/content/forex/quote/_index.md new file mode 100644 index 00000000000..63c434054f3 --- /dev/null +++ b/website/content/forex/quote/_index.md @@ -0,0 +1,10 @@ +``` +usage: quote [-h] +``` + +Get current exchange rate quote + +``` +optional arguments: + -h, --help show this help message (default: False) +``` diff --git a/website/content/forex/select/_index.md b/website/content/forex/select/_index.md new file mode 100644 index 00000000000..2d6ef347144 --- /dev/null +++ b/website/content/forex/select/_index.md @@ -0,0 +1,14 @@ +``` +usage: select [-t TO_SYMBOL] [-f FROM_SYMBOL] [-h] +``` + +Select Forex pair in the form of TO -f FROM + +``` +optional arguments: + -t TO_SYMBOL, --to TO_SYMBOL + To currency (default: None) + -f FROM_SYMBOL, --from FROM_SYMBOL + From currency (default: None) + -h, --help show this help message (default: False) + ``` diff --git a/website/data/menu/main.yml b/website/data/menu/main.yml index 1e1fafd602f..d64f731dd29 100755 --- a/website/data/menu/main.yml +++ b/website/data/menu/main.yml @@ -878,5 +878,12 @@ main: ref: "/portfolio/portfolio_optimization/ef" - name: forex ref: "/forex" + sub: + - name: select + ref: "/forex/select" + - name: load + ref: "/forex/load" + - name: candle + ref: "/forex/candle" - name: resources ref: "/resources"