From de209bfa29d5baf55dab4ae0ab2a8ab7615b9c7e Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 9 Apr 2021 03:24:21 -0600 Subject: [PATCH 01/54] Added Oanda Controller --- gamestonk_terminal/oanda/oanda_controller.py | 117 +++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 gamestonk_terminal/oanda/oanda_controller.py diff --git a/gamestonk_terminal/oanda/oanda_controller.py b/gamestonk_terminal/oanda/oanda_controller.py new file mode 100644 index 00000000000..4befdbd1c10 --- /dev/null +++ b/gamestonk_terminal/oanda/oanda_controller.py @@ -0,0 +1,117 @@ +import argparse +# import matplotlib.pyplot as plt +from prompt_toolkit.completion import NestedCompleter +from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.menu import session +from gamestonk_terminal.oanda import oanda_functions +from gamestonk_terminal import config_terminal as cfg + +account = cfg.OANDA_ACCOUNT + + +class OandaController: + """Oanda Controller class""" + + CHOICES = [ + "help", + "q", + "quit", + "hist", + "price", + "summary", + "list"] + + def __init__( + self, +# instrument:str, + ): + """Construct Data""" +# self.instrument = instrument + self.oanda_parser = argparse.ArgumentParser(add_help=False, prog="fx") + self.oanda_parser.add_argument( + "cmd", + choices=self.CHOICES, + ) + + @staticmethod + def print_help(): + """Print help""" + + print("\nForex Mode:") + print("\t help \t show this menu again") + print("\t q \t quit this menu and goes back to main menu") + print("\t quit \t quit to abandon program") + print("") + print("\t price \t shows price for selected instrument") + print("\t summary shows account summary") + print("\t list \t list orders") + + def switch(self, an_input: str): + """Process and dispatch input + Returns + ______ + True, False, or None + False - quit the menu + True - quit the program + None - contiue in the menu + """ + (known_args, other_args) = self.oanda_parser.parse_known_args(an_input.split()) + + 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 - exit the program""" + return True + + def call_price(self, _): + """Process Price Command""" + try: + oanda_functions.get_fx_price(account, instrument) + except NameError as e: + print(e) + + def call_summary(self, _): + """Process account summary command""" + oanda_functions.get_account_summary(account) + + def call_list(self, _): + """Process list orders command""" + oanda_functions.list_orders(account) + + +def menu(): + """Oanda 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()} (fx)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (fx)> ") + + 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 + From 23c557ab8709c4d0dea4667e4710ec0d22cc2952 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 9 Apr 2021 03:24:52 -0600 Subject: [PATCH 02/54] Added Oanda Functions --- gamestonk_terminal/oanda/oanda_functions.py | 77 +++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 gamestonk_terminal/oanda/oanda_functions.py diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py new file mode 100644 index 00000000000..8843d337526 --- /dev/null +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -0,0 +1,77 @@ +import argparse +from oandapyV20 import API +import oandapyV20.endpoints.pricing as pricing +import oandapyV20.endpoints.accounts as accounts +import oandapyV20.endpoints.orders as orders +from gamestonk_terminal import config_terminal as cfg + +client = API(access_token=cfg.OANDA_TOKEN, environment="live") +account = cfg.OANDA_ACCOUNT + + +def get_fx_price(accountID, instrument): + 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) + + +def get_account_summary(accountID): + request = accounts.AccountSummary(accountID=accountID) + response = client.request(request) + balance = response["account"]["balance"] + margin_available = response["account"]["marginAvailable"] + margin_closeout = response["account"]["marginCloseoutNAV"] + margin_closeout_percent = response["account"]["marginCloseoutPercent"] + margin_closeout_position_value = response["account"]["marginCloseoutPositionValue"] + margin_used = response["account"]["marginUsed"] + net_asset_value = response["account"]["NAV"] + open_trade_count = response["account"]["openTradeCount"] + total_pl = response["account"]["pl"] + unrealized_pl = response["account"]["unrealizedPL"] + + print(f"Balance: {balance}") + print(f"NAV: {net_asset_value}") + print(f"Unrealized P/L: {unrealized_pl}") + print(f"Total P/L: {total_pl}") + print(f"Open Trade Count: {open_trade_count}") + print(f"Margin Available: {margin_available}") + print(f"Margin Used: {margin_used}") + print(f"Margin Closeout {margin_closeout}") + print(f"Margin Closeout Percent: {margin_closeout_percent}") + print(f"Margin Closeout Position Value: {margin_closeout_position_value}") + + +def list_orders(accountID, state="ALL", count="50"): + parameters = { + "state": state, + "count": count + } + request = orders.OrderList(accountID, parameters) response = client.request(request) + for i in range(0, int(count), 1): + try: + order_id = response["orders"][i]["id"] + instrument = response["orders"][i]["instrument"] + units = response["orders"][i]["units"] + order_state = response["orders"][i]["state"] + order_type = response["orders"][i]["type"] + + print(f"Order id: {order_id}") + print(f"Instrument: {instrument}") + print(f"Units: {units}") + print(f"Order State: {order_state}") + print(f"Order Type: {order_type}") + print("") + except KeyError as e: + print(f"Key error: {e} not found \n") + continue + except IndexError as e: + print(e) + break + + From 3d65a4b8f8a59d007fc434018cfd73ca0193d5bc Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 9 Apr 2021 03:25:19 -0600 Subject: [PATCH 03/54] Added Oanda Tokens to config --- gamestonk_terminal/config_terminal.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gamestonk_terminal/config_terminal.py b/gamestonk_terminal/config_terminal.py index 4189a27ce4e..83837f6fdff 100644 --- a/gamestonk_terminal/config_terminal.py +++ b/gamestonk_terminal/config_terminal.py @@ -35,3 +35,7 @@ API_NEWS_TOKEN = os.getenv("GT_API_NEWS_TOKEN") or "REPLACE_ME" # Robinhood RH_USERNAME = os.getenv("GT_RH_USERNAME") or "REPLACE_ME" RH_PASSWORD = os.getenv("GT_RH_PASSWORD") or "REPLACE_ME" + +# Oanda +OANDA_ACCOUNT = os.getenv("GT_OANDA_ACCOUNT") or "REPLACE ME" +OANDA_TOKEN = os.getenv("GT_OANDA_TOKEN") or "REPLACE ME" From ed799c63b57baa23b30cc367008d2ad1eaf4f2af Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 9 Apr 2021 03:29:03 -0600 Subject: [PATCH 04/54] Added Oanda --- terminal.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/terminal.py b/terminal.py index d27cd0f80d1..5c61ae8166b 100644 --- a/terminal.py +++ b/terminal.py @@ -30,6 +30,8 @@ from gamestonk_terminal.fred import fred_controller from gamestonk_terminal.residuals_analysis import ra_controller from gamestonk_terminal.portfolio import port_controller from gamestonk_terminal.cryptocurrency import crypto_controller +from gamestonk_terminal.oanda import oanda_controller + # import warnings # warnings.simplefilter("always") @@ -86,6 +88,7 @@ def main(): "pa", "crypto", "ra", + "fx", ] menu_parser.add_argument("opt", choices=choices) completer = NestedCompleter.from_nested_dict({c: None for c in choices}) @@ -201,6 +204,9 @@ def main(): elif ns_known_args.opt == "fa": b_quit = fam.fa_menu(s_ticker, s_start, s_interval) + elif ns_known_args.opt == "fx": + b_quit = oanda_controller.menu() + elif ns_known_args.opt == "ta": b_quit = ta_controller.menu(df_stock, s_ticker, s_start, s_interval) From eb266e8359067b06f92701d146b5d6ba4ef0163b Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 9 Apr 2021 03:29:50 -0600 Subject: [PATCH 05/54] Added init.py for oanda --- gamestonk_terminal/oanda/__init.py__ | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 gamestonk_terminal/oanda/__init.py__ diff --git a/gamestonk_terminal/oanda/__init.py__ b/gamestonk_terminal/oanda/__init.py__ new file mode 100644 index 00000000000..e69de29bb2d From c75cbec2a304aecd7bde1073a8e6376d5ac95468 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 9 Apr 2021 03:43:41 -0600 Subject: [PATCH 06/54] A line was joined on accident --- gamestonk_terminal/oanda/oanda_functions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py index 8843d337526..caa303b77c1 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -52,7 +52,8 @@ def list_orders(accountID, state="ALL", count="50"): "state": state, "count": count } - request = orders.OrderList(accountID, parameters) response = client.request(request) + request = orders.OrderList(accountID, parameters) + response = client.request(request) for i in range(0, int(count), 1): try: order_id = response["orders"][i]["id"] From 8857dcb604742b3979850c45f8ee5473f5350204 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Sat, 10 Apr 2021 02:58:22 -0600 Subject: [PATCH 07/54] Added functions for oanda --- gamestonk_terminal/oanda/oanda_controller.py | 77 ++++- gamestonk_terminal/oanda/oanda_functions.py | 334 +++++++++++++++++-- 2 files changed, 375 insertions(+), 36 deletions(-) diff --git a/gamestonk_terminal/oanda/oanda_controller.py b/gamestonk_terminal/oanda/oanda_controller.py index 4befdbd1c10..ebb446063be 100644 --- a/gamestonk_terminal/oanda/oanda_controller.py +++ b/gamestonk_terminal/oanda/oanda_controller.py @@ -1,5 +1,6 @@ import argparse # import matplotlib.pyplot as plt +from typing import List from prompt_toolkit.completion import NestedCompleter from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn from gamestonk_terminal import feature_flags as gtff @@ -17,17 +18,22 @@ class OandaController: "help", "q", "quit", - "hist", "price", "summary", - "list"] + "list", + "orderbook", + "positionbook", + "limitorder", + "cancel", + "positions", + "closetrade", + "trades", + "pending"] def __init__( self, -# instrument:str, ): """Construct Data""" -# self.instrument = instrument self.oanda_parser = argparse.ArgumentParser(add_help=False, prog="fx") self.oanda_parser.add_argument( "cmd", @@ -39,13 +45,21 @@ class OandaController: """Print help""" print("\nForex Mode:") - print("\t help \t show this menu again") - print("\t q \t quit this menu and goes back to main menu") - print("\t quit \t quit to abandon program") + print("help \t show this menu again") + print("q \t quit this menu and goes back to main menu") + print("quit \t quit to abandon program") print("") - print("\t price \t shows price for selected instrument") - print("\t summary shows account summary") - print("\t list \t list orders") + print("price \t shows price for selected instrument") + print("summary shows account summary") + print("list \t list order history") + print("trades \t list open trades") + print("orderbook \t print orderbook") + print("positionbook \t print positionbook") + print("limitorder \t place limit order -u # of units -p price") + print("cancel \t cancel a pending order by ID -i order ID") + print("pending \t get information on pending up to 25 pending orders") + print("positions \t Get open positions") + print("closetrade \t Close a trade by id") def switch(self, an_input: str): """Process and dispatch input @@ -72,20 +86,49 @@ class OandaController: """Process Quit command - exit the program""" return True - def call_price(self, _): + def call_price(self, other_args: List[str]): """Process Price Command""" - try: - oanda_functions.get_fx_price(account, instrument) - except NameError as e: - print(e) + oanda_functions.get_fx_price(account, other_args) def call_summary(self, _): """Process account summary command""" oanda_functions.get_account_summary(account) - def call_list(self, _): + def call_orderbook(self, other_args: List [str]): + """Process Oanda Order Book""" + oanda_functions.get_order_book(other_args) + + def call_positionbook(self, other_args: List [str]): + """Process Oanda Position Book""" + oanda_functions.get_position_book(other_args) + + def call_list(self, other_args: List [str]): """Process list orders command""" - oanda_functions.list_orders(account) + oanda_functions.list_orders(account, other_args) + + def call_limitorder(self, other_args: List [str]): + """Place market order""" + oanda_functions.create_limit_order(account, other_args) + + def call_cancel(self, other_args: List [str]): + """Cancel pending order by ID""" + oanda_functions.cancel_pending_order(account, other_args) + + def call_positions(self, _): + """Get Open Positions""" + oanda_functions.get_open_positions(account) + + def call_pending(self, _): + """See up to 25 pending orders""" + oanda_functions.get_pending_orders(account) + + def call_closetrade(self, other_args: List [str]): + """Close a trade by id""" + oanda_functions.close_trade(account, other_args) + + def call_trades(self, _): + """List open trades""" + oanda_functions.get_open_trades(account) def menu(): diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py index caa303b77c1..e93f6f1a3f3 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -1,24 +1,51 @@ import argparse from oandapyV20 import API +from typing import List import oandapyV20.endpoints.pricing as pricing import oandapyV20.endpoints.accounts as accounts import oandapyV20.endpoints.orders as orders +import oandapyV20.endpoints.instruments as instruments +import oandapyV20.endpoints.positions as positions +import oandapyV20.endpoints.trades as trades +from oandapyV20.exceptions import V20Error from gamestonk_terminal import config_terminal as cfg +from gamestonk_terminal.helper_funcs import parse_known_args_and_warn client = API(access_token=cfg.OANDA_TOKEN, environment="live") account = cfg.OANDA_ACCOUNT -def get_fx_price(accountID, instrument): - 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) +def get_fx_price(accountID, other_args: List[str]): + parser = argparse.ArgumentParser( + add_help=False, + prog="getfxprice", + description="Get price for forex", + ) + parser.add_argument( + "-i", + "--instrument", + dest="instrument", + action="store", + required=True + ) + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + try: + parameters = { + "instruments": ns_parser.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(ns_parser.instrument + " Bid: " + bid) + print(ns_parser.instrument + " Ask: " + ask) + except V20Error as e: + print(e) + except NameError as e: + print(e) def get_account_summary(accountID): @@ -47,14 +74,44 @@ def get_account_summary(accountID): print(f"Margin Closeout Position Value: {margin_closeout_position_value}") -def list_orders(accountID, state="ALL", count="50"): +def list_orders(accountID, other_args: List [str]): + parser = argparse.ArgumentParser( + add_help=False, + prog="listorders", + description="List order history", + ) + parser.add_argument( + "-s", + "--state", + dest="state", + action="store", + default="ALL", + required=False + ) + parser.add_argument( + "-c", + "--count", + dest="count", + action="store", + default=50, + required=False + ) + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + parameters = { - "state": state, - "count": count +# "state": ns_parser.state, +# "count": ns_parser.count, } + if ns_parser.state is not None: + parameters["state"] = ns_parser.state + if ns_parser.count is not None: + parameters["count"] = ns_parser.count + request = orders.OrderList(accountID, parameters) response = client.request(request) - for i in range(0, int(count), 1): + for i in range(0, int(ns_parser.count), 1): try: order_id = response["orders"][i]["id"] instrument = response["orders"][i]["instrument"] @@ -67,12 +124,251 @@ def list_orders(accountID, state="ALL", count="50"): print(f"Units: {units}") print(f"Order State: {order_state}") print(f"Order Type: {order_type}") - print("") - except KeyError as e: - print(f"Key error: {e} not found \n") + print("-"*30) + except KeyError: continue - except IndexError as e: - print(e) + except IndexError: break +def get_order_book(other_args: List [str]): + parser = argparse.ArgumentParser( + add_help=False, + prog="get_order_book", + description="Get Order book", + ) + parser.add_argument( + "-i", + "--instrument", + dest="instrument", + action="store", + required=True + ) + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + parameters = { + "bucketWidth": 0.1 + } + request = instruments.InstrumentsOrderBook(instrument=ns_parser.instrument, params=parameters) + response = client.request(request) + + for i in range(400, 700, 1): + order_instrument = response["orderBook"]["instrument"] + price = response["orderBook"]["buckets"][i]["price"] + short_count_percent = response["orderBook"]["buckets"][i]["shortCountPercent"] + long_count_percent = response["orderBook"]["buckets"][i]["longCountPercent"] + print(f"Instrument: {order_instrument}") + print(f"Price: {price}") + print(f"Short count percent: {short_count_percent}") + print(f"Long count percent: {long_count_percent}") + print("-"*30) + + +def get_position_book(other_args: List [str]): + parser = argparse.ArgumentParser( + add_help=False, + prog="get_position_book ", + description="Get Position Book", + ) + parser.add_argument( + "-i", + "--instrument", + dest="instrument", + action="store", + required=True + ) + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + parameters = { + "bucketWidth": 0.1 + } + request = instruments.InstrumentsPositionBook (instrument=ns_parser.instrument, params=parameters) + response = client.request(request) + + for i in range(200,400,1): + order_instrument = response["positionBook"]["instrument"] + price = response["positionBook"]["buckets"][i]["price"] + short_count_percent = response["positionBook"]["buckets"][i]["shortCountPercent"] + long_count_percent = response["positionBook"]["buckets"][i]["longCountPercent"] + print(f"Instrument: {order_instrument}") + print(f"Price: {price}") + print(f"Short count percent: {short_count_percent}") + print(f"Long count percent: {long_count_percent}") + print("-"*30) + + +def create_limit_order(accountID, instrument, other_args: List[str]): + parser = argparse.ArgumentParser( + add_help=False, + prog="create_limit_order", + description="Create limit order", + ) + parser.add_argument( + "-u", + "--unit", + dest="units", + action="store", + type=int, + default=0, + required=True, + ) + parser.add_argument( + "-p", + "--price", + dest="price", + action="store", + type=float, + required=True, + ) + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + data = { + "order":{ + "price": ns_parser.price, + "instrument": instrument, + "units": ns_parser.units, + "type": "LIMIT", + "timeInForce":"GTC", + "positionFill": "DEFAULT" + } + } + request = orders.OrderCreate(accountID, data) + response = client.request(request) + print(response) + + +def cancel_pending_order(accountID, other_args: List[str]): + parser = argparse.ArgumentParser( + add_help=False, + prog="cancelpendingorder", + description="Cancel Pending Order", + ) + parser.add_argument( + "-i", + "--id", + dest="orderID", + action="store", + type=str, + required=True, + ) + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + try: + request = orders.OrderCancel(accountID, ns_parser.orderID) + client.request(request) + except V20Error as e: + print(e) + + +def get_pending_orders(accountID): + request = orders.OrdersPending(accountID) + response = client.request(request) + for i in range(0,25,1): + try: + order_id = response["orders"][i]["id"] + instrument = response["orders"][i]["instrument"] + price = response["orders"][i]["price"] + units = response["orders"][i]["units"] + create_time = response["orders"][i]["createTime"] + time_in_force = response["orders"][i]["timeInForce"] + print(f"Order ID: {order_id}") + print(f"Instrument: {instrument}") + print(f"Price: {price}") + print(f"Units: {units}") + print(f"Time created: {create_time}") + print(f"Time in force: {time_in_force}") + print("-"*30) + except IndexError: + break + + +def get_open_positions(accountID): + request = positions.OpenPositions(accountID) + response = client.request(request) + for i in range(0,100,1): + try: + instrument = response["positions"][i]["instrument"] + long_units = response["positions"][i]["long"]["units"] + long_pl = response["positions"][i]["long"]["pl"] + long_upl = response["positions"][i]["long"]["unrealizedPL"] + short_units = response["positions"][i]["short"]["units"] + short_pl = response["positions"][i]["short"]["pl"] + short_upl = response["positions"][i]["short"]["unrealizedPL"] + print(f"Instrument: {instrument}\n") + print(f"Long Units: {long_units}") + print(f"Long P/L: {long_pl}") + print(f"Long Unrealized P/L: {long_upl}\n") + print(f"Short Units: {short_units}") + print(f"Short P/L: {short_pl}") + print(f"Short Unrealized P/L: {short_upl}") + print("-"*30+"\n") + except IndexError: + break + + +def get_open_trades(accountID): + request = trades.OpenTrades(accountID) + response = client.request(request) + for i in range (0,100,1): + try: + order_id = response["trades"][i]["id"] + instrument = response["trades"][i]["instrument"] + initial_units = response["trades"][i]["initialUnits"] + current_units = response["trades"][i]["currentUnits"] + price = response["trades"][i]["price"] + unrealized_pl = response["trades"][i]["unrealizedPL"] + print(f"Order ID: {order_id}\n") + print(f"Instrument: {instrument}") + print(f"Initial Units: {initial_units}") + print(f"Current Units: {current_units}") + print(f"Price: {price}") + print(f"Unrealized P/L: {unrealized_pl}") + print("-"*30+"\n") + except IndexError: + break + + + + + + +def close_trade(accountID, other_args: List[str]): + parser = ArgumentParser( + add_help = False, + prog="close_trade", + description="close a trade", + ) + parser.add_argument( + "-i", + "--id", + dest="orderID", + action="store", + type=str, + required=True, + ) + parser.add_argument( + "-u", + "--units", + dest="units", + action="store", + type=str, + required=False, + ) + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + data={ + "units": ns_parser.units, + } + try: + request = trades.TradeClose(accountID, ns_parser.orderID, data) + response = client.request(request) + except V20Error as e: + print(e) From 52ed3cd97401bfe8c3baa973a98ce258742942e4 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Thu, 15 Apr 2021 21:34:35 -0600 Subject: [PATCH 08/54] Updating oanda --- gamestonk_terminal/oanda/oanda_controller.py | 74 ++++--- gamestonk_terminal/oanda/oanda_functions.py | 222 +++++++++++-------- 2 files changed, 169 insertions(+), 127 deletions(-) diff --git a/gamestonk_terminal/oanda/oanda_controller.py b/gamestonk_terminal/oanda/oanda_controller.py index ebb446063be..8794f2f4656 100644 --- a/gamestonk_terminal/oanda/oanda_controller.py +++ b/gamestonk_terminal/oanda/oanda_controller.py @@ -1,5 +1,4 @@ import argparse -# import matplotlib.pyplot as plt from typing import List from prompt_toolkit.completion import NestedCompleter from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn @@ -28,16 +27,18 @@ class OandaController: "positions", "closetrade", "trades", - "pending"] + "candles", + "pending", + ] def __init__( self, - ): + ): """Construct Data""" self.oanda_parser = argparse.ArgumentParser(add_help=False, prog="fx") self.oanda_parser.add_argument( - "cmd", - choices=self.CHOICES, + "cmd", + choices=self.CHOICES, ) @staticmethod @@ -57,9 +58,10 @@ class OandaController: print("positionbook \t print positionbook") print("limitorder \t place limit order -u # of units -p price") print("cancel \t cancel a pending order by ID -i order ID") - print("pending \t get information on pending up to 25 pending orders") + print("pending \t get information on pending orders") print("positions \t Get open positions") print("closetrade \t Close a trade by id") + print("candles \t show candles") def switch(self, an_input: str): """Process and dispatch input @@ -72,7 +74,9 @@ class OandaController: """ (known_args, other_args) = self.oanda_parser.parse_known_args(an_input.split()) - return getattr(self, "call_" + known_args.cmd, lambda: "command not recognized!")(other_args) + return getattr( + self, "call_" + known_args.cmd, lambda: "command not recognized!" + )(other_args) def call_help(self, _): """Process Help Command""" @@ -94,23 +98,23 @@ class OandaController: """Process account summary command""" oanda_functions.get_account_summary(account) - def call_orderbook(self, other_args: List [str]): + def call_orderbook(self, other_args: List[str]): """Process Oanda Order Book""" oanda_functions.get_order_book(other_args) - def call_positionbook(self, other_args: List [str]): + def call_positionbook(self, other_args: List[str]): """Process Oanda Position Book""" oanda_functions.get_position_book(other_args) - def call_list(self, other_args: List [str]): + def call_list(self, other_args: List[str]): """Process list orders command""" oanda_functions.list_orders(account, other_args) - def call_limitorder(self, other_args: List [str]): - """Place market order""" + def call_limitorder(self, other_args: List[str]): + """Place limit order""" oanda_functions.create_limit_order(account, other_args) - def call_cancel(self, other_args: List [str]): + def call_cancel(self, other_args: List[str]): """Cancel pending order by ID""" oanda_functions.cancel_pending_order(account, other_args) @@ -122,39 +126,41 @@ class OandaController: """See up to 25 pending orders""" oanda_functions.get_pending_orders(account) - def call_closetrade(self, other_args: List [str]): + def call_closetrade(self, other_args: List[str]): """Close a trade by id""" oanda_functions.close_trade(account, other_args) + def call_candles(self, _): + oanda_functions.show_candles(account) + def call_trades(self, _): """List open trades""" oanda_functions.get_open_trades(account) def menu(): - """Oanda 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} - ) + """Oanda 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( + an_input = session.prompt( f"{get_flair()} (fx)> ", completer=completer, - ) - else: - an_input = input(f"{get_flair()} (fx)> ") + ) + else: + an_input = input(f"{get_flair()} (fx)> ") - try: - process_input = oanda_controller.switch(an_input) + 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 + 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/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py index e93f6f1a3f3..fa3db84bca5 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -10,6 +10,10 @@ import oandapyV20.endpoints.trades as trades from oandapyV20.exceptions import V20Error from gamestonk_terminal import config_terminal as cfg from gamestonk_terminal.helper_funcs import parse_known_args_and_warn +import pandas as pd +import matplotlib.pyplot as plt +import mplfinance as mpf +import matplotlib.dates as mdates client = API(access_token=cfg.OANDA_TOKEN, environment="live") account = cfg.OANDA_ACCOUNT @@ -22,20 +26,14 @@ def get_fx_price(accountID, other_args: List[str]): description="Get price for forex", ) parser.add_argument( - "-i", - "--instrument", - dest="instrument", - action="store", - required=True + "-i", "--instrument", dest="instrument", action="store", required=True ) ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return try: - parameters = { - "instruments": ns_parser.instrument - } + parameters = {"instruments": ns_parser.instrument} request = pricing.PricingInfo(accountID=accountID, params=parameters) response = client.request(request) bid = response["prices"][0]["bids"][0]["price"] @@ -74,42 +72,32 @@ def get_account_summary(accountID): print(f"Margin Closeout Position Value: {margin_closeout_position_value}") -def list_orders(accountID, other_args: List [str]): +def list_orders(accountID, other_args: List[str]): parser = argparse.ArgumentParser( add_help=False, prog="listorders", description="List order history", ) parser.add_argument( - "-s", - "--state", - dest="state", - action="store", - default="ALL", - required=False + "-s", "--state", dest="state", action="store", default="ALL", required=False ) parser.add_argument( - "-c", - "--count", - dest="count", - action="store", - default=50, - required=False + "-c", "--count", dest="count", action="store", default=50, required=False ) ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return parameters = { -# "state": ns_parser.state, -# "count": ns_parser.count, + # "state": ns_parser.state, + # "count": ns_parser.count, } if ns_parser.state is not None: parameters["state"] = ns_parser.state if ns_parser.count is not None: parameters["count"] = ns_parser.count - request = orders.OrderList(accountID, parameters) + request = orders.OrderList(accountID, parameters) response = client.request(request) for i in range(0, int(ns_parser.count), 1): try: @@ -124,34 +112,30 @@ def list_orders(accountID, other_args: List [str]): print(f"Units: {units}") print(f"Order State: {order_state}") print(f"Order Type: {order_type}") - print("-"*30) + print("-" * 30) except KeyError: continue except IndexError: break -def get_order_book(other_args: List [str]): +def get_order_book(other_args: List[str]): parser = argparse.ArgumentParser( add_help=False, prog="get_order_book", description="Get Order book", ) parser.add_argument( - "-i", - "--instrument", - dest="instrument", - action="store", - required=True + "-i", "--instrument", dest="instrument", action="store", required=True ) ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - parameters = { - "bucketWidth": 0.1 - } - request = instruments.InstrumentsOrderBook(instrument=ns_parser.instrument, params=parameters) + parameters = {"bucketWidth": 0.1} + request = instruments.InstrumentsOrderBook( + instrument=ns_parser.instrument, params=parameters + ) response = client.request(request) for i in range(400, 700, 1): @@ -163,79 +147,83 @@ def get_order_book(other_args: List [str]): print(f"Price: {price}") print(f"Short count percent: {short_count_percent}") print(f"Long count percent: {long_count_percent}") - print("-"*30) + print("-" * 30) -def get_position_book(other_args: List [str]): +def get_position_book(other_args: List[str]): parser = argparse.ArgumentParser( add_help=False, prog="get_position_book ", description="Get Position Book", ) parser.add_argument( - "-i", - "--instrument", - dest="instrument", - action="store", - required=True - ) + "-i", "--instrument", dest="instrument", action="store", required=True + ) ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - parameters = { - "bucketWidth": 0.1 - } - request = instruments.InstrumentsPositionBook (instrument=ns_parser.instrument, params=parameters) + parameters = {"bucketWidth": 0.1} + request = instruments.InstrumentsPositionBook( + instrument=ns_parser.instrument, params=parameters + ) response = client.request(request) - for i in range(200,400,1): + for i in range(200, 400, 1): order_instrument = response["positionBook"]["instrument"] price = response["positionBook"]["buckets"][i]["price"] - short_count_percent = response["positionBook"]["buckets"][i]["shortCountPercent"] + short_count_percent = response["positionBook"]["buckets"][i][ + "shortCountPercent" + ] long_count_percent = response["positionBook"]["buckets"][i]["longCountPercent"] print(f"Instrument: {order_instrument}") print(f"Price: {price}") print(f"Short count percent: {short_count_percent}") print(f"Long count percent: {long_count_percent}") - print("-"*30) + print("-" * 30) -def create_limit_order(accountID, instrument, other_args: List[str]): +def create_limit_order(accountID, other_args: List[str]): parser = argparse.ArgumentParser( - add_help=False, - prog="create_limit_order", - description="Create limit order", + add_help=False, + prog="create_limit_order", + description="Create limit order", ) parser.add_argument( - "-u", - "--unit", - dest="units", - action="store", - type=int, - default=0, - required=True, + "-u", + "--unit", + dest="units", + action="store", + type=int, + default=0, + required=True, ) parser.add_argument( - "-p", - "--price", - dest="price", - action="store", - type=float, - required=True, + "-p", + "--price", + dest="price", + action="store", + type=float, + required=True, ) + parser.add_argument( + "-i", + "--instrument", + dest="instrument", + action="store", + required=True) ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return data = { - "order":{ + "order": { "price": ns_parser.price, - "instrument": instrument, + "instrument": ns_parser.instrument, "units": ns_parser.units, "type": "LIMIT", - "timeInForce":"GTC", - "positionFill": "DEFAULT" + "timeInForce": "GTC", + "positionFill": "DEFAULT", } } request = orders.OrderCreate(accountID, data) @@ -250,12 +238,12 @@ def cancel_pending_order(accountID, other_args: List[str]): description="Cancel Pending Order", ) parser.add_argument( - "-i", - "--id", - dest="orderID", - action="store", - type=str, - required=True, + "-i", + "--id", + dest="orderID", + action="store", + type=str, + required=True, ) ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: @@ -270,7 +258,7 @@ def cancel_pending_order(accountID, other_args: List[str]): def get_pending_orders(accountID): request = orders.OrdersPending(accountID) response = client.request(request) - for i in range(0,25,1): + for i in range(len(response["orders"])): try: order_id = response["orders"][i]["id"] instrument = response["orders"][i]["instrument"] @@ -284,7 +272,7 @@ def get_pending_orders(accountID): print(f"Units: {units}") print(f"Time created: {create_time}") print(f"Time in force: {time_in_force}") - print("-"*30) + print("-" * 30) except IndexError: break @@ -292,7 +280,7 @@ def get_pending_orders(accountID): def get_open_positions(accountID): request = positions.OpenPositions(accountID) response = client.request(request) - for i in range(0,100,1): + for i in range(0, 100, 1): try: instrument = response["positions"][i]["instrument"] long_units = response["positions"][i]["long"]["units"] @@ -303,12 +291,12 @@ def get_open_positions(accountID): short_upl = response["positions"][i]["short"]["unrealizedPL"] print(f"Instrument: {instrument}\n") print(f"Long Units: {long_units}") - print(f"Long P/L: {long_pl}") + print(f"Total Long P/L: {long_pl}") print(f"Long Unrealized P/L: {long_upl}\n") print(f"Short Units: {short_units}") - print(f"Short P/L: {short_pl}") + print(f"Total Short P/L: {short_pl}") print(f"Short Unrealized P/L: {short_upl}") - print("-"*30+"\n") + print("-" * 30 + "\n") except IndexError: break @@ -316,7 +304,7 @@ def get_open_positions(accountID): def get_open_trades(accountID): request = trades.OpenTrades(accountID) response = client.request(request) - for i in range (0,100,1): + for i in range(len(response["trades"])): try: order_id = response["trades"][i]["id"] instrument = response["trades"][i]["instrument"] @@ -328,20 +316,16 @@ def get_open_trades(accountID): print(f"Instrument: {instrument}") print(f"Initial Units: {initial_units}") print(f"Current Units: {current_units}") - print(f"Price: {price}") + print(f"Entry Price: {price}") print(f"Unrealized P/L: {unrealized_pl}") - print("-"*30+"\n") + print("-" * 30 + "\n") except IndexError: break - - - - def close_trade(accountID, other_args: List[str]): parser = ArgumentParser( - add_help = False, + add_help=False, prog="close_trade", description="close a trade", ) @@ -364,11 +348,63 @@ def close_trade(accountID, other_args: List[str]): ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - data={ + data = { "units": ns_parser.units, - } + } try: request = trades.TradeClose(accountID, ns_parser.orderID, data) response = client.request(request) except V20Error as e: print(e) + + +def show_candles(accountID): + parameters = { + "granularity": "D" + } + try: + request = instruments.InstrumentsCandles("EUR_USD", params=parameters) + response = client.request(request) + process_response(response) + oanda_fix_date(".temp_candles.csv") + df = pd.read_csv(".candles.csv") + df["Datetime"] = pd.to_datetime(df["Datetime"]) + df.set_index(pd.DatetimeIndex(df["Datetime"]), inplace=True) + print(df) +# dvalues = df[["Open", "High", "Low", "Close"]].values.tolist() +# pdates = mdates.date2num(data.index) +# ohlc = [ ppdates[i] + dvalues[i] for i in range(len(pdates))] +# plt.style.use("fivethirtyeight") +# fix, ax = plt.subplots(figsize = (12, 6)) +# mpf.candlestick_ohlc(ax, ohlc[-50:], width=0.4) +# ax.set_xlabel("Date") +# ax.set_ylabel("Price") +# ax.set_title("Chart for EUR_USD") +# ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %d')) +# fig.autofmt_xdate() + mpf.plot(df, type="candlestick") +# plt.show() + + except Exception as e: + print(e) + + +def process_response(response): + with open(".temp_candles.csv", 'w') as out: + for i in range(len(response["candles"])): + time = response["candles"][i]["time"] + o = response["candles"][i]["mid"]["o"] + h = response["candles"][i]["mid"]["h"] + l = response["candles"][i]["mid"]["l"] + c = response["candles"][i]["mid"]["c"] + out.write(str(time) + "," + str(o) + "," + str(h) + "," + str(l) + "," + str(c) + "\n") + + +def oanda_fix_date(file): + with open(file, 'r') as candle_file: + lines = candle_file.readlines() + with open(".candles.csv", 'w') as out: + out.write("Datetime, Open, High, Low, Close\n") + for line in lines: + with open(".candles.csv", "a") as output: + output.write(line[:10] + " " + line[11:19] + line[30:]) From d58f8f4cafbed90ac9e5f684eff6d5ca2edf8af2 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Thu, 15 Apr 2021 21:43:14 -0600 Subject: [PATCH 09/54] resolve conflict from personal changes --- README.md | 53 +- ROADMAP.md | 7 +- gamestonk_terminal/README.md | 20 +- .../behavioural_analysis/google_view.py | 2 +- .../comparison_analysis/ca_controller.py | 15 +- .../discovery/disc_controller.py | 3 + ...nsider_api.py => business_insider_view.py} | 128 +++-- .../due_diligence/dd_controller.py | 219 +++++++++ gamestonk_terminal/due_diligence/dd_menu.py | 148 ------ ...api.py => financial_modeling_prep_view.py} | 22 +- .../{finviz_api.py => finviz_view.py} | 81 ++- ...rket_watch_api.py => market_watch_view.py} | 42 +- .../{news_api.py => news_view.py} | 22 +- .../{quandl_api.py => quandl_view.py} | 40 +- .../{reddit_api.py => reddit_view.py} | 22 +- gamestonk_terminal/feature_flags.py | 3 + gamestonk_terminal/main_helper.py | 87 +++- gamestonk_terminal/options/op_controller.py | 73 ++- gamestonk_terminal/options/volume.py | 176 ------- gamestonk_terminal/options/volume_view.py | 463 ++++++++++++++++++ .../portfolio_optimization/README.md | 169 +++++++ .../portfolio_optimization/__init__.py | 0 .../optimizer_helper.py | 226 +++++++++ .../portfolio_optimization/optimizer_view.py | 337 +++++++++++++ .../portfolio_optimization/po_controller.py | 206 ++++++++ .../prediction_techniques/pred_controller.py | 207 ++++++++ .../prediction_techniques/pred_menu.py | 145 ------ gamestonk_terminal/screener/README.md | 13 +- .../screener/screener_controller.py | 8 + notebooks/templates/due_diligence.ipynb | 2 +- poetry.lock | 173 ++++++- pyproject.toml | 1 + requirements-full.txt | 1 + requirements-old.txt | 1 + requirements-win.txt | 1 + requirements.txt | 1 + terminal.py | 24 +- tests/test_dd_financial_modeling_prep_api.py | 9 - tests/test_dd_financial_modeling_prep_view.py | 9 + ...d_finviz_api.py => test_dd_finviz_view.py} | 2 +- 40 files changed, 2520 insertions(+), 641 deletions(-) rename gamestonk_terminal/due_diligence/{business_insider_api.py => business_insider_view.py} (80%) create mode 100644 gamestonk_terminal/due_diligence/dd_controller.py delete mode 100644 gamestonk_terminal/due_diligence/dd_menu.py rename gamestonk_terminal/due_diligence/{financial_modeling_prep_api.py => financial_modeling_prep_view.py} (65%) rename gamestonk_terminal/due_diligence/{finviz_api.py => finviz_view.py} (70%) rename gamestonk_terminal/due_diligence/{market_watch_api.py => market_watch_view.py} (92%) rename gamestonk_terminal/due_diligence/{news_api.py => news_view.py} (78%) rename gamestonk_terminal/due_diligence/{quandl_api.py => quandl_view.py} (77%) rename gamestonk_terminal/due_diligence/{reddit_api.py => reddit_view.py} (88%) delete mode 100644 gamestonk_terminal/options/volume.py create mode 100644 gamestonk_terminal/options/volume_view.py create mode 100644 gamestonk_terminal/portfolio_optimization/README.md create mode 100644 gamestonk_terminal/portfolio_optimization/__init__.py create mode 100644 gamestonk_terminal/portfolio_optimization/optimizer_helper.py create mode 100644 gamestonk_terminal/portfolio_optimization/optimizer_view.py create mode 100644 gamestonk_terminal/portfolio_optimization/po_controller.py create mode 100644 gamestonk_terminal/prediction_techniques/pred_controller.py delete mode 100644 gamestonk_terminal/prediction_techniques/pred_menu.py delete mode 100644 tests/test_dd_financial_modeling_prep_api.py create mode 100644 tests/test_dd_financial_modeling_prep_view.py rename tests/{test_dd_finviz_api.py => test_dd_finviz_view.py} (70%) diff --git a/README.md b/README.md index eb6c28d634a..54c5089fe7f 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,9 @@ @@ -120,6 +123,14 @@ Navigate into the folder with: `cd GamestonkTerminal/` conda install poetry ``` +5.5. If installing python 3.8 +``` +conda deactivate +conda activate gst +``` +*The `conda deactivate` -> `conda activate` in the middle is on purpose, this is sometimes required to avoid issues with poetry* + + 6. Install poetry dependencies ``` poetry install @@ -132,21 +143,6 @@ This is a library for package management, and ensures a smoother experience than python terminal.py ``` -### Advanced User Install - Python 3.8 - -*Note that the `conda deactivate` -> `conda activate` in the middle is on purpose, this is sometimes required to avoid issues with poetry* - -``` -conda create -n gst python=3.8.8 -conda activate gst -conda install poetry -conda deactivate -conda activate gst -poetry install -conda install -c conda-forge fbprophet numpy=1.19.5 hdf5=1.10.5 -poetry install -E prediction -``` - ### Advanced User Install - Machine Learning If you are an advanced user and use other Python distributions, we have several requirements.txt documents that you can pick from to download project dependencies. @@ -185,7 +181,7 @@ Note: The problem with docker is that it won't output matplotlib figures. * `pip install pystan --upgrade` * `poetry update --lock` -### Other issues +### Other Issues If you run into trouble with poetry and the advice above did not help, your best bet is to try @@ -215,6 +211,7 @@ If you run into trouble with poetry and the advice above did not help, your best 7. Submit a ticket on github ### ModuleNotFoundError + In the case when you run into an error of the form `ModuleNotFoundError: No module named '_______'`. The solution is to install the missing package via pip. @@ -223,6 +220,30 @@ If you get the error that `statsmodels` is not found, you would run Then please submit an issue so that we can address why that was not imported. +Please note that the package `pmdarima` needs to installed through `pip install` and not through `conda install`. + +### Update Terminal + +The terminal is constantly being updated with new features and bug fixes, hence, for your terminal to be update, you can run: +``` +git pull +``` +to get the latest changes. + +If this fails due to the fact that you had modified some python files, and there's a conflict with the updates, you can use: +``` +git stash +``` + +Then, re-run `poetry install` or `pip install -r requirements.txt` to get any new dependencies. + +Once installation is finished, you're ready to gamestonk. + +If you `stashed` your changes previously, you can un-stash them with: +``` +git stash pop +``` + ### API Keys The project is build around several different API calls, whether it is to access historical data or financials. diff --git a/ROADMAP.md b/ROADMAP.md index 1642af3fe10..f5c466f3a9a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -142,8 +142,13 @@ * [ ] Add Brokers (td, webull, etc) (@jmaslek) * [ ] Refactoring * [ ] Summaries / tear sheets (@jmaslek) -* [ ] Portfolio Optimization (@jmaslek) +___ +##Portfolio Optimization +* [x] Basic Optimization through PyPortFolioOpt(@jmaslek) - [PR #329](https://github.com/DidierRLopes/GamestonkTerminal/pull/329) +**NEXT** +* [ ] Decide what features should be present +* [ ] Allow for more custom optimization constrains --- ## FRED diff --git a/gamestonk_terminal/README.md b/gamestonk_terminal/README.md index 46e946a0161..2b6d79c7b2e 100644 --- a/gamestonk_terminal/README.md +++ b/gamestonk_terminal/README.md @@ -23,9 +23,11 @@ The main menu allows the following commands: ``` load -t S_TICKER [-s S_START_DATE] [-i {1,5,15,30,60}] ``` - * Load stock ticker to perform analysis on + * 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. * -s : The starting date (format YYYY-MM-DD) of the stock * -i : Intraday stock minutes + * --source : Source of historical data. 'yf' and 'av' available. Default 'yf' + * -p : Pre/After market hours. Only works for 'yf' source, and intraday data **Note:** Until a ticker is loaded, the menu will only show *disc* and *sen* menu, as the others require a ticker being provided. @@ -33,6 +35,7 @@ load -t S_TICKER [-s S_START_DATE] [-i {1,5,15,30,60}] clear ``` * Clear previously loaded stock ticker. + ``` view -t S_TICKER [-s S_START_DATE] [-i {1,5,15,30,60}] [--type N_TYPE] ``` @@ -238,6 +241,21 @@ Command|Description|Brokers `hold` | view net holdings across all logins   +## Portfolio Optimization [»](portfolio_optimization/README.md) +command|desciription +------|------ +`equal_weight`|Returns equal weights for all selected stocks +`mkt_cap`| Returns weights that are weighted by market cap +`div_yield`|Returns weights that are weighted by dividend yield +`max_sharpe`| Returns portfolio of Efficient Frontier with maximum sharpe ratio +`min_vol`| Returns portfolio on Efficient Frontier with minimum volatility +`eff_risk`| Returns portfolio on Efficient Frontier that maximizes returns at a given risk +`eff_ret`| Returns portfolio on Efficient Frontier that minimizes risk at a given return level +`show_eff`|Plots random portfolios and shows the Efficient Frontier + +  + + ## Cryptocurrency [»](cryptocurrency/README.md) Command|Description diff --git a/gamestonk_terminal/behavioural_analysis/google_view.py b/gamestonk_terminal/behavioural_analysis/google_view.py index 9b85d7a2dc1..9047f75e59c 100644 --- a/gamestonk_terminal/behavioural_analysis/google_view.py +++ b/gamestonk_terminal/behavioural_analysis/google_view.py @@ -99,7 +99,7 @@ def regions(l_args, s_ticker): plt.bar(df_interest_region.index, df_interest_region[s_ticker], width=0.8) plt.grid(b=True, which="major", color="#666666", linestyle="-") plt.ylabel("Interest [%]") - plt.xlabel("Time") + plt.xlabel("Region") plt.show() print("") diff --git a/gamestonk_terminal/comparison_analysis/ca_controller.py b/gamestonk_terminal/comparison_analysis/ca_controller.py index 67b91f8b4da..f999ed092af 100644 --- a/gamestonk_terminal/comparison_analysis/ca_controller.py +++ b/gamestonk_terminal/comparison_analysis/ca_controller.py @@ -3,6 +3,7 @@ __docformat__ = "numpy" import argparse import requests +import random from typing import List from datetime import datetime import pandas as pd @@ -15,6 +16,7 @@ from gamestonk_terminal.comparison_analysis import yahoo_finance_api as yf_api from gamestonk_terminal.comparison_analysis import market_watch_api as mw_api from gamestonk_terminal.comparison_analysis import finbrain_api as f_api from gamestonk_terminal.comparison_analysis import finviz_compare_view +from gamestonk_terminal.portfolio_optimization import po_controller from gamestonk_terminal.menu import session from prompt_toolkit.completion import NestedCompleter @@ -42,6 +44,7 @@ class ComparisonAnalysisController: "ownership", "performance", "technical", + "po", ] def __init__( @@ -107,6 +110,8 @@ class ComparisonAnalysisController: print(" performance brief performance comparison") print(" technical brief technical comparison") print("") + print(" > po portfolio optimization for selected tickers") + print("") return @staticmethod @@ -172,9 +177,11 @@ class ComparisonAnalysisController: if len(self.similar) > 10: print( - "\nThe limit of stocks to compare with are 10. Hence, the similar stocks list will be:" + "\nThe limit of stocks to compare with are 10. Hence, 10 random similar stocks will be displayed.", + "\nThe selected list will be:", ) - self.similar = self.similar[:10] + random.shuffle(self.similar) + self.similar = sorted(self.similar[:10]) print(", ".join(self.similar)) except Exception as e: @@ -312,6 +319,10 @@ class ComparisonAnalysisController: """Process technical command""" finviz_compare_view.screener(other_args, "technical", self.ticker, self.similar) + def call_po(self, _): + """Open Portfolio Optimization menu with ticker and similar""" + return po_controller.menu([self.ticker] + self.similar) + def menu(stock: pd.DataFrame, ticker: str, start: datetime, interval: str): """Comparison Analysis Menu""" diff --git a/gamestonk_terminal/discovery/disc_controller.py b/gamestonk_terminal/discovery/disc_controller.py index c7a4ac40e33..07bb33a3302 100644 --- a/gamestonk_terminal/discovery/disc_controller.py +++ b/gamestonk_terminal/discovery/disc_controller.py @@ -4,6 +4,7 @@ __docformat__ = "numpy" import argparse import os from typing import List +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 @@ -209,6 +210,8 @@ def menu(): an_input = input(f"{get_flair()} (disc)> ") try: + plt.close("all") + process_input = disc_controller.switch(an_input) if process_input is not None: diff --git a/gamestonk_terminal/due_diligence/business_insider_api.py b/gamestonk_terminal/due_diligence/business_insider_view.py similarity index 80% rename from gamestonk_terminal/due_diligence/business_insider_api.py rename to gamestonk_terminal/due_diligence/business_insider_view.py index 63ca11b3dc9..32a1995198c 100644 --- a/gamestonk_terminal/due_diligence/business_insider_api.py +++ b/gamestonk_terminal/due_diligence/business_insider_view.py @@ -1,6 +1,11 @@ +""" Business Insider View """ +__docformat__ = "numpy" + import argparse +from typing import List import json import re +from pandas.core.frame import DataFrame import requests import matplotlib.pyplot as plt import pandas as pd @@ -12,11 +17,30 @@ from gamestonk_terminal.helper_funcs import ( get_user_agent, parse_known_args_and_warn, ) +from gamestonk_terminal import feature_flags as gtff register_matplotlib_converters() -def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval): +def price_target_from_analysts( + other_args: List[str], stock: DataFrame, ticker: str, start: str, interval: str +): + """Display analysts' price targets for a given stock + + Parameters + ---------- + other_args : List[str] + argparse other args - ["-n", "10"] + stock : DataFrame + Due diligence stock dataframe + ticker : str + Due diligence ticker symbol + start : str + Start date of the stock data + interval : str + Stock data interval + """ + parser = argparse.ArgumentParser( add_help=False, prog="pt", @@ -34,12 +58,12 @@ def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return url_market_business_insider = ( - f"https://markets.businessinsider.com/stocks/{s_ticker.lower()}-stock" + f"https://markets.businessinsider.com/stocks/{ticker.lower()}-stock" ) text_soup_market_business_insider = BeautifulSoup( requests.get( @@ -59,8 +83,7 @@ def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval): d_analyst_data = json.loads(s_analyst_data) break - # pprint.pprint(d_analyst_data) - df_analyst_data = pd.DataFrame.from_dict(d_analyst_data["Markers"]) + df_analyst_data = pd.DataFrame.from_dict(d_analyst_data["Markers"]) # type: ignore df_analyst_data = df_analyst_data[ ["DateLabel", "Company", "InternalRating", "PriceTarget"] ] @@ -73,17 +96,17 @@ def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval): df_analyst_data = df_analyst_data.set_index("Date") # Slice start of ratings - if s_start: - df_analyst_data = df_analyst_data[s_start:] + if start: + df_analyst_data = df_analyst_data[start:] # type: ignore - if s_interval == "1440min": - plt.plot(df_stock.index, df_stock["5. adjusted close"].values, lw=3) + if interval == "1440min": + plt.plot(stock.index, stock["5. adjusted close"].values, lw=3) # Intraday else: - plt.plot(df_stock.index, df_stock["4. close"].values, lw=3) + plt.plot(stock.index, stock["4. close"].values, lw=3) - if s_start: - plt.plot(df_analyst_data.groupby(by=["Date"]).mean()[s_start:]) + if start: + plt.plot(df_analyst_data.groupby(by=["Date"]).mean()[start:]) # type: ignore else: plt.plot(df_analyst_data.groupby(by=["Date"]).mean()) @@ -91,13 +114,17 @@ def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval): plt.legend(["Closing Price", "Average Price Target", "Price Target"]) - plt.title(f"{s_ticker} (Time Series) and Price Target") - plt.xlim(df_stock.index[0], df_stock.index[-1]) + plt.title(f"{ticker} (Time Series) and Price Target") + plt.xlim(stock.index[0], stock.index[-1]) plt.xlabel("Time") plt.ylabel("Share Price ($)") plt.grid(b=True, which="major", color="#666666", linestyle="-") plt.minorticks_on() plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + + if gtff.USE_ION: + plt.ion() + plt.show() print("") @@ -115,7 +142,17 @@ def price_target_from_analysts(l_args, df_stock, s_ticker, s_start, s_interval): return -def estimates(l_args, s_ticker): +def estimates(other_args: List[str], ticker: str): + """Display analysts' estimates for a given ticker + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Due diligence ticker symbol + """ + parser = argparse.ArgumentParser( add_help=False, prog="est", @@ -123,12 +160,12 @@ def estimates(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return url_market_business_insider = ( - f"https://markets.businessinsider.com/stocks/{s_ticker.lower()}-stock" + f"https://markets.businessinsider.com/stocks/{ticker.lower()}-stock" ) text_soup_market_business_insider = BeautifulSoup( requests.get( @@ -274,7 +311,24 @@ def estimates(l_args, s_ticker): return -def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval): +def insider_activity( + other_args: List[str], stock: DataFrame, ticker: str, start: str, interval: str +): + """Display insider activity + + Parameters + ---------- + other_args : List[str] + argparse other args - ["-n", "10"] + stock : DataFrame + Due diligence stock dataframe + ticker : str + Due diligence ticker symbol + start : str + Start date of the stock data + interval : str + Stock data interval + """ parser = argparse.ArgumentParser( add_help=False, prog="ins", @@ -291,12 +345,12 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return url_market_business_insider = ( - f"https://markets.businessinsider.com/stocks/{s_ticker.lower()}-stock" + f"https://markets.businessinsider.com/stocks/{ticker.lower()}-stock" ) text_soup_market_business_insider = BeautifulSoup( requests.get( @@ -334,17 +388,17 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval): df_insider = df_insider.set_index("Date") df_insider = df_insider.sort_index(ascending=True) - if s_start: - df_insider = df_insider[s_start:] + if start: + df_insider = df_insider[start:] # type: ignore _, ax = plt.subplots() - if s_interval == "1440min": - plt.plot(df_stock.index, df_stock["5. adjusted close"].values, lw=3) + if interval == "1440min": + plt.plot(stock.index, stock["5. adjusted close"].values, lw=3) else: # Intraday - plt.plot(df_stock.index, df_stock["4. close"].values, lw=3) + plt.plot(stock.index, stock["4. close"].values, lw=3) - plt.title(f"{s_ticker.upper()} (Time Series) and Price Target") + plt.title(f"{ticker.upper()} (Time Series) and Price Target") plt.xlabel("Time") plt.ylabel("Share Price ($)") @@ -354,7 +408,7 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval): * float(row["Shares Traded"].replace(",", "")), axis=1, ) - plt.xlim(df_insider.index[0], df_stock.index[-1]) + plt.xlim(df_insider.index[0], stock.index[-1]) min_price, max_price = ax.get_ylim() price_range = max_price - min_price @@ -373,16 +427,16 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval): for ind in ( df_insider[df_insider["Type"] == "Sell"].groupby(by=["Date"]).sum().index ): - if ind in df_stock.index: + if ind in stock.index: ind_dt = ind else: ind_dt = get_next_stock_market_days(ind, 1)[0] n_stock_price = 0 - if s_interval == "1440min": - n_stock_price = df_stock["5. adjusted close"][ind_dt] + if interval == "1440min": + n_stock_price = stock["5. adjusted close"][ind_dt] else: - n_stock_price = df_stock["4. close"][ind_dt] + n_stock_price = stock["4. close"][ind_dt] plt.vlines( x=ind_dt, @@ -402,16 +456,16 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval): for ind in ( df_insider[df_insider["Type"] == "Buy"].groupby(by=["Date"]).sum().index ): - if ind in df_stock.index: + if ind in stock.index: ind_dt = ind else: ind_dt = get_next_stock_market_days(ind, 1)[0] n_stock_price = 0 - if s_interval == "1440min": - n_stock_price = df_stock["5. adjusted close"][ind_dt] + if interval == "1440min": + n_stock_price = stock["5. adjusted close"][ind_dt] else: - n_stock_price = df_stock["4. close"][ind_dt] + n_stock_price = stock["4. close"][ind_dt] plt.vlines( x=ind_dt, @@ -431,6 +485,10 @@ def insider_activity(l_args, df_stock, s_ticker, s_start, s_interval): plt.grid(b=True, which="major", color="#666666", linestyle="-") plt.minorticks_on() plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + + if gtff.USE_ION: + plt.ion() + plt.show() l_names = list() diff --git a/gamestonk_terminal/due_diligence/dd_controller.py b/gamestonk_terminal/due_diligence/dd_controller.py new file mode 100644 index 00000000000..14ecab8fc40 --- /dev/null +++ b/gamestonk_terminal/due_diligence/dd_controller.py @@ -0,0 +1,219 @@ +""" Due Diligence Controller """ +__docformat__ = "numpy" + +import argparse +from typing import List +from pandas.core.frame import DataFrame +from prompt_toolkit.completion import NestedCompleter + +from gamestonk_terminal.due_diligence import business_insider_view as bi_view +from gamestonk_terminal.due_diligence import financial_modeling_prep_view as fmp_view +from gamestonk_terminal.due_diligence import finviz_view as fvz_view +from gamestonk_terminal.due_diligence import market_watch_view as mw_view +from gamestonk_terminal.due_diligence import quandl_view as q_view +from gamestonk_terminal.due_diligence import reddit_view as r_view +from gamestonk_terminal.due_diligence import news_view +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import get_flair +from gamestonk_terminal.menu import session + + +class DueDiligenceController: + """ Due Diligence Controller """ + + # Command choices + CHOICES = [ + "help", + "q", + "quit", + "red", + "short", + "rating", + "pt", + "est", + "ins", + "insider", + "news", + "analyst", + "warnings", + "sec", + ] + + def __init__(self, stock: DataFrame, ticker: str, start: str, interval: str): + """Constructor + + Parameters + ---------- + stock : DataFrame + Due diligence stock dataframe + ticker : str + Due diligence ticker symbol + start : str + Start date of the stock data + interval : str + Stock data interval + """ + self.stock = stock + self.ticker = ticker + self.start = start + self.interval = interval + self.dd_parser = argparse.ArgumentParser(add_help=False, prog="dd") + self.dd_parser.add_argument( + "cmd", + choices=self.CHOICES, + ) + + def print_help(self): + """ Print help """ + + intraday = (f"Intraday {self.interval}", "Daily")[self.interval == "1440min"] + + if self.start: + print( + f"\n{intraday} Stock: {self.ticker} (from {self.start.strftime('%Y-%m-%d')})" + ) + else: + print(f"\n{intraday} Stock: {self.ticker}") + + print("\nDue Diligence:") + print(" help show this fundamental analysis menu again") + print(" q quit this menu, and shows back to main menu") + print(" quit quit to abandon program") + print("") + print(" news latest news of the company [News API]") + print(" red gets due diligence from another user's post [Reddit]") + print(" analyst analyst prices and ratings of the company [Finviz]") + print( + " rating rating of the company from strong sell to strong buy [FMP]" + ) + print(" pt price targets over time [Business Insider]") + print( + " est quarter and year analysts earnings estimates [Business Insider]" + ) + print(" ins insider activity over time [Business Insider]") + print(" insider insider trading of the company [Finviz]") + print(" sec SEC filings [Market Watch]") + print(" short short interest [Quandl]") + print( + " warnings company warnings according to Sean Seah book [Market Watch]" + ) + print("") + + 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 + """ + (known_args, other_args) = self.dd_parser.parse_known_args(an_input.split()) + + 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_red(self, other_args: List[str]): + """ Process red command """ + r_view.due_diligence(other_args, self.ticker) + + def call_insider(self, other_args: List[str]): + """ Process insider command """ + fvz_view.insider(other_args, self.ticker) + + def call_news(self, other_args: List[str]): + """ Process news command """ + news_view.news(other_args, self.ticker) + + def call_analyst(self, other_args: List[str]): + """ Process analyst command """ + fvz_view.analyst(other_args, self.ticker) + + def call_pt(self, other_args: List[str]): + """ Process pt command """ + bi_view.price_target_from_analysts( + other_args, self.stock, self.ticker, self.start, self.interval + ) + + def call_est(self, other_args: List[str]): + """ Process est command """ + bi_view.estimates(other_args, self.ticker) + + def call_ins(self, other_args: List[str]): + """ Process ins command """ + bi_view.insider_activity( + other_args, self.stock, self.ticker, self.start, self.interval + ) + + def call_rating(self, other_args: List[str]): + """ Process rating command """ + fmp_view.rating(other_args, self.ticker) + + def call_warnings(self, other_args: List[str]): + """ Process rating command """ + mw_view.sean_seah_warnings(other_args, self.ticker) + + def call_sec(self, other_args: List[str]): + """ Process sec command """ + mw_view.sec_fillings(other_args, self.ticker) + + def call_short(self, other_args: List[str]): + """ Process short command """ + q_view.short_interest(other_args, self.ticker, self.start) + + +def menu(stock: DataFrame, ticker: str, start: str, interval: str): + """Due Diligence Menu + + Parameters + ---------- + stock : DataFrame + Due diligence stock dataframe + ticker : str + Due diligence ticker symbol + start : str + Start date of the stock data + interval : str + Stock data interval + """ + + dd_controller = DueDiligenceController(stock, ticker, start, interval) + dd_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 dd_controller.CHOICES} + ) + + an_input = session.prompt( + f"{get_flair()} (dd)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (dd)> ") + + try: + process_input = dd_controller.switch(an_input) + + if process_input is not None: + return process_input + + except SystemExit: + print("The command selected doesn't exist\n") + continue diff --git a/gamestonk_terminal/due_diligence/dd_menu.py b/gamestonk_terminal/due_diligence/dd_menu.py deleted file mode 100644 index 94635e6d8c8..00000000000 --- a/gamestonk_terminal/due_diligence/dd_menu.py +++ /dev/null @@ -1,148 +0,0 @@ -import argparse - -from gamestonk_terminal.due_diligence import business_insider_api as bi_api -from gamestonk_terminal.due_diligence import financial_modeling_prep_api as fmp_api -from gamestonk_terminal.due_diligence import finviz_api as fvz_api -from gamestonk_terminal.due_diligence import market_watch_api as mw_api -from gamestonk_terminal.due_diligence import quandl_api as q_api -from gamestonk_terminal.due_diligence import reddit_api as r_api -from gamestonk_terminal.due_diligence import news_api -from gamestonk_terminal import feature_flags as gtff -from gamestonk_terminal.helper_funcs import get_flair -from gamestonk_terminal.menu import session -from prompt_toolkit.completion import NestedCompleter - - -def print_due_diligence(s_ticker, s_start, s_interval): - """ Print help """ - - s_intraday = (f"Intraday {s_interval}", "Daily")[s_interval == "1440min"] - - if s_start: - print(f"\n{s_intraday} Stock: {s_ticker} (from {s_start.strftime('%Y-%m-%d')})") - else: - print(f"\n{s_intraday} Stock: {s_ticker}") - - print("\nDue Diligence:") - print(" help show this fundamental analysis menu again") - print(" q quit this menu, and shows back to main menu") - print(" quit quit to abandon program") - print("") - print(" news latest news of the company [News API]") - print(" red gets due diligence from another user's post [Reddit]") - print(" analyst analyst prices and ratings of the company [Finviz]") - print(" rating rating of the company from strong sell to strong buy [FMP]") - print(" pt price targets over time [Business Insider]") - print( - " est quarter and year analysts earnings estimates [Business Insider]" - ) - print(" ins insider activity over time [Business Insider]") - print(" insider insider trading of the company [Finviz]") - print(" sec SEC filings [Market Watch]") - print(" short short interest [Quandl]") - print( - " warnings company warnings according to Sean Seah book [Market Watch]" - ) - print("") - return - - -def dd_menu(df_stock, s_ticker, s_start, s_interval): - - # Add list of arguments that the due diligence parser accepts - dd_parser = argparse.ArgumentParser(prog="dd", add_help=False) - choices = [ - "info", - "help", - "q", - "quit", - "red", - "short", - "rating", - "pt", - "est", - "ins", - "insider", - "news", - "analyst", - "warnings", - "sec", - ] - dd_parser.add_argument("cmd", choices=choices) - completer = NestedCompleter.from_nested_dict({c: None for c in choices}) - - print_due_diligence(s_ticker, s_start, s_interval) - - # Loop forever and ever - while True: - # Get input command from user - if session and gtff.USE_PROMPT_TOOLKIT: - as_input = session.prompt( - f"{get_flair()} (dd)> ", - completer=completer, - ) - else: - as_input = input(f"{get_flair()} (dd)> ") - - # Parse due diligence command of the list of possible commands - try: - (ns_known_args, l_args) = dd_parser.parse_known_args(as_input.split()) - - except SystemExit: - print("The command selected doesn't exist\n") - continue - - if ns_known_args.cmd == "help": - print_due_diligence(s_ticker, s_start, s_interval) - - elif ns_known_args.cmd == "q": - # Just leave the DD menu - return False - - elif ns_known_args.cmd == "quit": - # Abandon the program - return True - - # REDDIT API - elif ns_known_args.cmd == "red": - r_api.due_diligence(l_args, s_ticker) - - # FINVIZ API - elif ns_known_args.cmd == "insider": - fvz_api.insider(l_args, s_ticker) - - elif ns_known_args.cmd == "news": - news_api.news(l_args, s_ticker) - - elif ns_known_args.cmd == "analyst": - fvz_api.analyst(l_args, s_ticker) - - # BUSINESS INSIDER API - elif ns_known_args.cmd == "pt": - bi_api.price_target_from_analysts( - l_args, df_stock, s_ticker, s_start, s_interval - ) - - elif ns_known_args.cmd == "est": - bi_api.estimates(l_args, s_ticker) - - elif ns_known_args.cmd == "ins": - bi_api.insider_activity(l_args, df_stock, s_ticker, s_start, s_interval) - - # FINANCIAL MODELING PREP API - elif ns_known_args.cmd == "rating": - fmp_api.rating(l_args, s_ticker) - - # MARKET WATCH API - elif ns_known_args.cmd == "sec": - mw_api.sec_fillings(l_args, s_ticker) - - elif ns_known_args.cmd == "warnings": - mw_api.sean_seah_warnings(l_args, s_ticker) - - # QUANDL API - elif ns_known_args.cmd == "short": - q_api.short_interest(l_args, s_ticker, s_start) - - else: - print("Command not recognized!") diff --git a/gamestonk_terminal/due_diligence/financial_modeling_prep_api.py b/gamestonk_terminal/due_diligence/financial_modeling_prep_view.py similarity index 65% rename from gamestonk_terminal/due_diligence/financial_modeling_prep_api.py rename to gamestonk_terminal/due_diligence/financial_modeling_prep_view.py index 9163bf02149..4026649e2fe 100644 --- a/gamestonk_terminal/due_diligence/financial_modeling_prep_api.py +++ b/gamestonk_terminal/due_diligence/financial_modeling_prep_view.py @@ -1,10 +1,24 @@ +""" Financial Modeling Prep View """ +__docformat__ = "numpy" + import argparse +from typing import List import FundamentalAnalysis as fa # Financial Modeling Prep from gamestonk_terminal import config_terminal as cfg from gamestonk_terminal.helper_funcs import parse_known_args_and_warn -def rating(l_args, s_ticker): +def rating(other_args: List[str], ticker: str): + """Display ratings for a given ticker + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Stock ticker + """ + parser = argparse.ArgumentParser( add_help=False, prog="rating", @@ -16,18 +30,18 @@ def rating(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - df_fa = fa.rating(s_ticker, cfg.API_KEY_FINANCIALMODELINGPREP) + df_fa = fa.rating(ticker, cfg.API_KEY_FINANCIALMODELINGPREP) print(df_fa) print("") except KeyError: print( - f"Financialmodelingprep.com is returning empty response the ticker {s_ticker}." + f"Financialmodelingprep.com is returning empty response the ticker {ticker}." ) print("") return diff --git a/gamestonk_terminal/due_diligence/finviz_api.py b/gamestonk_terminal/due_diligence/finviz_view.py similarity index 70% rename from gamestonk_terminal/due_diligence/finviz_api.py rename to gamestonk_terminal/due_diligence/finviz_view.py index 50b47b433ee..56a3eaa8a21 100644 --- a/gamestonk_terminal/due_diligence/finviz_api.py +++ b/gamestonk_terminal/due_diligence/finviz_view.py @@ -1,4 +1,8 @@ +""" FinViz View """ +__docformat__ = "numpy" + import argparse +from typing import List from colorama import Fore, Style import finviz import pandas as pd @@ -11,6 +15,19 @@ from gamestonk_terminal.helper_funcs import ( def category_color_red_green(val: str) -> str: + """Add color to analyst rating + + Parameters + ---------- + val : str + Analyst rating - Upgrade/Downgrade + + Returns + ------- + str + Analyst rating with color + """ + if val == "Upgrade": return Fore.GREEN + val + Style.RESET_ALL if val == "Downgrade": @@ -18,7 +35,16 @@ def category_color_red_green(val: str) -> str: return val -def insider(l_args, s_ticker): +def insider(other_args: List[str], ticker: str): + """Display insider activity for a given stock ticker + + Parameters + ---------- + other_args : List[str] + argparse other args - ["-n", "10"] + ticker : str + Stock ticker + """ parser = argparse.ArgumentParser( add_help=False, prog="insider", @@ -39,11 +65,11 @@ def insider(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - d_finviz_insider = finviz.get_insider(s_ticker) + d_finviz_insider = finviz.get_insider(ticker) df_fa = pd.DataFrame.from_dict(d_finviz_insider) df_fa.set_index("Date", inplace=True) df_fa = df_fa[ @@ -68,7 +94,17 @@ def insider(l_args, s_ticker): return -def news(l_args, s_ticker): +def news(other_args: List[str], ticker: str): + """Display news for a given stock ticker + + Parameters + ---------- + other_args : List[str] + argparse other args - ["-n", "10"] + ticker : str + Stock ticker + """ + parser = argparse.ArgumentParser( add_help=False, prog="news", @@ -88,11 +124,11 @@ def news(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - d_finviz_news = finviz.get_news(s_ticker) + d_finviz_news = finviz.get_news(ticker) i = 0 for s_news_title, s_news_link in {*d_finviz_news}: print(f"-> {s_news_title}") @@ -110,9 +146,22 @@ def news(l_args, s_ticker): return -def analyst_df(s_ticker: str) -> DataFrame: +def analyst_df(ticker: str) -> DataFrame: + """[summary] + + Parameters + ---------- + ticker : str + Stock ticker + + Returns + ------- + DataFrame + [description] + """ + try: - d_finviz_analyst_price = finviz.get_analyst_price_targets(s_ticker) + d_finviz_analyst_price = finviz.get_analyst_price_targets(ticker) df_fa = pd.DataFrame.from_dict(d_finviz_analyst_price) df_fa.set_index("date", inplace=True) except Exception as e: @@ -122,7 +171,17 @@ def analyst_df(s_ticker: str) -> DataFrame: return df_fa -def analyst(l_args, s_ticker): +def analyst(other_args, ticker): + """Display analyst ratings + + Parameters + ---------- + other_args : [type] + argparse other args + ticker : [type] + Stock ticker + """ + parser = argparse.ArgumentParser( add_help=False, prog="analyst", @@ -144,11 +203,11 @@ def analyst(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - df_fa = analyst_df(s_ticker) + df_fa = analyst_df(ticker) if ns_parser.n_color == 1: df_fa["category"] = df_fa["category"].apply(category_color_red_green) diff --git a/gamestonk_terminal/due_diligence/market_watch_api.py b/gamestonk_terminal/due_diligence/market_watch_view.py similarity index 92% rename from gamestonk_terminal/due_diligence/market_watch_api.py rename to gamestonk_terminal/due_diligence/market_watch_view.py index f9cfd054983..7a080347cb1 100644 --- a/gamestonk_terminal/due_diligence/market_watch_api.py +++ b/gamestonk_terminal/due_diligence/market_watch_view.py @@ -1,4 +1,8 @@ +""" Market Watch View """ +__docformat__ = "numpy" + import argparse +from typing import List import re import requests import pandas as pd @@ -13,7 +17,17 @@ from gamestonk_terminal.helper_funcs import ( ) -def sec_fillings(l_args, s_ticker): +def sec_fillings(other_args: List[str], ticker: str): + """Display SEC filings for a given stock ticker + + Parameters + ---------- + other_args : List[str] + argparse other args - ["-n", "10"] + ticker : str + Stock ticker + """ + parser = argparse.ArgumentParser( add_help=False, prog="sec", @@ -34,13 +48,13 @@ def sec_fillings(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return pd.set_option("display.max_colwidth", None) - url_financials = f"https://www.marketwatch.com/investing/stock/{s_ticker}/financials/secfilings" + url_financials = f"https://www.marketwatch.com/investing/stock/{ticker}/financials/secfilings" text_soup_financials = BeautifulSoup( requests.get(url_financials, headers={"User-Agent": get_user_agent()}).text, @@ -60,7 +74,7 @@ def sec_fillings(l_args, s_ticker): l_financials_info.extend(a_financials[5:-1]) l_financials_info.append(financials_info.a["href"]) # Append data values to financials - df_financials.loc[len(df_financials.index)] = l_financials_info + df_financials.loc[len(df_financials.index)] = l_financials_info # type: ignore if "Filing Date" in a_financials: l_financials_header = [a_financials[2]] @@ -72,7 +86,7 @@ def sec_fillings(l_args, s_ticker): b_ready_to_process_info = True # Set Filing Date as index - df_financials = df_financials.set_index("Filing Date") + df_financials = df_financials.set_index("Filing Date") # type: ignore print(df_financials.head(n=ns_parser.n_num).to_string()) print("") @@ -83,7 +97,17 @@ def sec_fillings(l_args, s_ticker): return -def sean_seah_warnings(l_args, s_ticker): +def sean_seah_warnings(other_args: List[str], ticker: str): + """Display Sean Seah warnings + + Parameters + ---------- + other_args : List[str] + argparse other args + ticker : str + Stock ticker + """ + parser = argparse.ArgumentParser( add_help=False, prog="warnings", @@ -114,7 +138,7 @@ def sean_seah_warnings(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -129,7 +153,7 @@ def sean_seah_warnings(l_args, s_ticker): # From INCOME STATEMENT, get: 'EPS (Basic)', 'Net Income', 'Interest Expense', 'EBITDA' url_financials = ( - f"https://www.marketwatch.com/investing/stock/{s_ticker}/financials/income" + f"https://www.marketwatch.com/investing/stock/{ticker}/financials/income" ) text_soup_financials = BeautifulSoup( requests.get(url_financials, headers={"User-Agent": get_user_agent()}).text, @@ -173,7 +197,7 @@ def sean_seah_warnings(l_args, s_ticker): ] # From BALANCE SHEET, get: 'Liabilities & Shareholders\' Equity', 'Long-Term Debt' - url_financials = f"https://www.marketwatch.com/investing/stock/{s_ticker}/financials/balance-sheet" + url_financials = f"https://www.marketwatch.com/investing/stock/{ticker}/financials/balance-sheet" text_soup_financials = BeautifulSoup( requests.get(url_financials, headers={"User-Agent": get_user_agent()}).text, "lxml", diff --git a/gamestonk_terminal/due_diligence/news_api.py b/gamestonk_terminal/due_diligence/news_view.py similarity index 78% rename from gamestonk_terminal/due_diligence/news_api.py rename to gamestonk_terminal/due_diligence/news_view.py index 7d61da02925..9b82d67caae 100644 --- a/gamestonk_terminal/due_diligence/news_api.py +++ b/gamestonk_terminal/due_diligence/news_view.py @@ -1,4 +1,8 @@ +""" News View """ +__docformat__ = "numpy" + import argparse +from typing import List from datetime import datetime, timedelta import requests from gamestonk_terminal import config_terminal as cfg @@ -8,7 +12,17 @@ from gamestonk_terminal.helper_funcs import ( ) -def news(l_args, s_ticker): +def news(other_args: List[str], ticker: str): + """Display news for a given ticker + + Parameters + ---------- + other_args : List[str] + argparse other args - ["-n", "10"] + ticker : str + Stock ticker + """ + parser = argparse.ArgumentParser( add_help=False, prog="news", @@ -28,14 +42,14 @@ def news(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return s_from = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d") response = requests.get( - f"https://newsapi.org/v2/everything?q={s_ticker}&from={s_from}" + f"https://newsapi.org/v2/everything?q={ticker}&from={s_from}" f"&sortBy=publishedAt&language=en&apiKey={cfg.API_NEWS_TOKEN}", ) @@ -45,7 +59,7 @@ def news(l_args, s_ticker): else: print( - f"{response.json()['totalResults']} news articles from {s_ticker} were found since {s_from}\n" + f"{response.json()['totalResults']} news articles from {ticker} were found since {s_from}\n" ) for idx, article in enumerate(response.json()["articles"]): diff --git a/gamestonk_terminal/due_diligence/quandl_api.py b/gamestonk_terminal/due_diligence/quandl_view.py similarity index 77% rename from gamestonk_terminal/due_diligence/quandl_api.py rename to gamestonk_terminal/due_diligence/quandl_view.py index 7d4254a9f6d..b0ca0f09f67 100644 --- a/gamestonk_terminal/due_diligence/quandl_api.py +++ b/gamestonk_terminal/due_diligence/quandl_view.py @@ -1,7 +1,11 @@ +""" Quandl View """ +__docformat__ = "numpy" + import argparse +from typing import List import quandl from matplotlib import pyplot as plt -import matplotlib.ticker as ticker +import matplotlib.ticker import pandas as pd from gamestonk_terminal.helper_funcs import ( check_positive, @@ -11,7 +15,19 @@ from gamestonk_terminal.helper_funcs import ( from gamestonk_terminal import config_terminal as cfg -def short_interest(l_args, s_ticker, s_start): +def short_interest(other_args: List[str], ticker: str, start: str): + """Display short interest for a given ticker and a given start date + + Parameters + ---------- + other_args : List[str] + argparse other args - ["-d", "10"] + ticker : str + Stock ticker + start : str + Start date of the stock data + """ + parser = argparse.ArgumentParser( add_help=False, prog="short", @@ -40,18 +56,18 @@ def short_interest(l_args, s_ticker, s_start): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return quandl.ApiConfig.api_key = cfg.API_KEY_QUANDL if ns_parser.b_nyse: - df_short_interest = quandl.get(f"FINRA/FNYX_{s_ticker}") + df_short_interest = quandl.get(f"FINRA/FNYX_{ticker}") else: - df_short_interest = quandl.get(f"FINRA/FNSQ_{s_ticker}") + df_short_interest = quandl.get(f"FINRA/FNSQ_{ticker}") - df_short_interest = df_short_interest[s_start:] + df_short_interest = df_short_interest[start:] # type: ignore df_short_interest.columns = [ "".join( " " + char if char.isupper() else char.strip() for char in idx @@ -77,18 +93,18 @@ def short_interest(l_args, s_ticker, s_start): ax.set_ylabel("Shares") ax.set_xlabel("Date") - if s_start: + if start: ax.set_title( - f"{('NASDAQ', 'NYSE')[ns_parser.b_nyse]} Short Interest on {s_ticker} from {s_start.date()}" + f"{('NASDAQ', 'NYSE')[ns_parser.b_nyse]} Short Interest on {ticker} from {start.date()}" # type: ignore ) else: ax.set_title( - f"{('NASDAQ', 'NYSE')[ns_parser.b_nyse]} Short Interest on {s_ticker}" + f"{('NASDAQ', 'NYSE')[ns_parser.b_nyse]} Short Interest on {ticker}" ) ax.legend(labels=["Short Volume", "Total Volume"]) ax.tick_params(axis="both", which="major") - ax.yaxis.set_major_formatter(ticker.EngFormatter()) + ax.yaxis.set_major_formatter(matplotlib.ticker.EngFormatter()) ax_twin = ax.twinx() ax_twin.tick_params(axis="y", colors="green") ax_twin.set_ylabel("Percentage of Volume Shorted", color="green") @@ -98,7 +114,9 @@ def short_interest(l_args, s_ticker, s_start): color="green", ) ax_twin.tick_params(axis="y", which="major", color="green") - ax_twin.yaxis.set_major_formatter(ticker.FormatStrFormatter("%.0f%%")) + ax_twin.yaxis.set_major_formatter( + matplotlib.ticker.FormatStrFormatter("%.0f%%") + ) plt.xlim([df_short_interest.index[0], df_short_interest.index[-1]]) df_short_interest["% of Volume Shorted"] = df_short_interest[ diff --git a/gamestonk_terminal/due_diligence/reddit_api.py b/gamestonk_terminal/due_diligence/reddit_view.py similarity index 88% rename from gamestonk_terminal/due_diligence/reddit_api.py rename to gamestonk_terminal/due_diligence/reddit_view.py index f305abacef7..5c536add200 100644 --- a/gamestonk_terminal/due_diligence/reddit_api.py +++ b/gamestonk_terminal/due_diligence/reddit_view.py @@ -1,4 +1,8 @@ +""" Reddit View """ +__docformat__ = "numpy" + import argparse +from typing import List from datetime import datetime, timedelta from psaw import PushshiftAPI import praw @@ -7,7 +11,17 @@ from gamestonk_terminal import config_terminal as cfg from gamestonk_terminal.reddit_helpers import print_and_record_reddit_post -def due_diligence(l_args, s_ticker): +def due_diligence(other_args: List[str], ticker: str): + """Display Reddit due diligence data for a given ticker + + Parameters + ---------- + other_args : List[str] + argparse other args - ["-l", "5"] + ticker : str + Stock ticker + """ + parser = argparse.ArgumentParser( add_help=False, prog="red", @@ -47,7 +61,7 @@ def due_diligence(l_args, s_ticker): ) try: - ns_parser = parse_known_args_and_warn(parser, l_args) + ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return @@ -83,9 +97,9 @@ def due_diligence(l_args, s_ticker): ] submissions = psaw_api.search_submissions( - after=int(n_ts_after), subreddit=l_sub_reddits, q=s_ticker, filter=["id"] + after=int(n_ts_after), subreddit=l_sub_reddits, q=ticker, filter=["id"] ) - d_submission = {} + d_submission = {} # type: ignore n_flair_posts_found = 0 while True: submission = next(submissions, None) diff --git a/gamestonk_terminal/feature_flags.py b/gamestonk_terminal/feature_flags.py index 3eaebcd2bb2..5c4910a1ab2 100644 --- a/gamestonk_terminal/feature_flags.py +++ b/gamestonk_terminal/feature_flags.py @@ -16,6 +16,9 @@ USE_PROMPT_TOOLKIT = strtobool(os.getenv("GTFF_USE_PROMPT_TOOLKIT", "False")) # Enable Prediction features ENABLE_PREDICT = strtobool(os.getenv("GTFF_ENABLE_PREDICT", "False")) +# Enable FB Prophet +ENABLE_FBPROPHET = strtobool(os.getenv("GTFF_ENABLE_FBPROPHET", "False")) + # Enable plot autoscaling USE_PLOT_AUTOSCALING = strtobool(os.getenv("GTFF_USE_PLOT_AUTOSCALING", "False")) diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py index 27c0cacdb09..40c8f6714ba 100644 --- a/gamestonk_terminal/main_helper.py +++ b/gamestonk_terminal/main_helper.py @@ -1,10 +1,12 @@ import argparse from sys import stdout +from datetime import datetime, timedelta import matplotlib.pyplot as plt import pandas as pd from alpha_vantage.timeseries import TimeSeries import mplfinance as mpf import yfinance as yf +import pytz from gamestonk_terminal.helper_funcs import ( valid_date, @@ -58,6 +60,9 @@ def print_help(s_ticker, s_start, s_interval, b_is_market_open): ) print(" pa portfolio analysis, \t\t supports: robinhood, alpaca, ally ") print(" crypto cryptocurrencies, \t\t uses coingecko api") + print( + " po portfolio optimization, \t\t optimal portfolio weights from pyportfolioopt" + ) if s_ticker: print( @@ -117,7 +122,9 @@ def load(l_args, s_ticker, s_start, s_interval, df_stock): parser = argparse.ArgumentParser( add_help=False, prog="load", - description=""" Load a stock in order to perform analysis.""", + 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", @@ -151,8 +158,15 @@ def load(l_args, s_ticker, s_start, s_interval, df_stock): dest="source", type=check_sources, default="yf", - help="Source of historical data. Intraday: Only 'av' available." - "Daily: 'yf' and 'av' available.", + help="Source of historical data. 'yf' and 'av' available.", + ) + parser.add_argument( + "-p", + "--prepost", + action="store_true", + default=False, + dest="b_prepost", + help="Pre/After market hours. Only works for 'yf' source, and intraday data", ) try: @@ -229,9 +243,54 @@ def load(l_args, s_ticker, s_start, s_interval, df_stock): df_stock = df_stock[ns_parser.s_start_date :] elif ns_parser.source == "yf": - print( - "Unfortunately, yahoo finance historical data doesn't contain intraday prices" + s_int = s_interval[:-2] + + d_granularity = {"1m": 6, "5m": 59, "15m": 59, "30m": 59, "60m": 729} + + s_start_dt = datetime.utcnow() - timedelta(days=d_granularity[s_int]) + s_date_start = s_start_dt.strftime("%Y-%m-%d") + + s_start = pytz.utc.localize(s_start_dt) + + if s_start_dt > ns_parser.s_start_date: + print( + f"Using Yahoo Finance with granularity {s_int} the starting date is set to: {s_date_start}\n" + ) + + df_stock = yf.download( + ns_parser.s_ticker, + start=s_date_start, + progress=False, + interval=s_int, + prepost=ns_parser.b_prepost, + ) + + else: + df_stock = yf.download( + ns_parser.s_ticker, + start=ns_parser.s_start_date.strftime("%Y-%m-%d"), + progress=False, + interval=s_int, + prepost=ns_parser.b_prepost, + ) + + # Check that a stock was successfully retrieved + if df_stock.empty: + print("") + return [s_ticker, s_start, s_interval, df_stock] + + df_stock = df_stock.rename( + columns={ + "Open": "1. open", + "High": "2. high", + "Low": "3. low", + "Close": "4. close", + "Adj Close": "5. adjusted close", + "Volume": "6. volume", + } ) + df_stock.index.name = "date" + return [s_ticker, s_start, s_interval, df_stock] except Exception as e: @@ -369,24 +428,6 @@ def view(l_args, s_ticker, s_start, s_interval, df_stock): type_candles = lett_to_num(ns_parser.type) - try: - ts = TimeSeries(key=cfg.API_KEY_ALPHAVANTAGE, output_format="pandas") - # Daily - if s_interval == "1440min": - # pylint: disable=unbalanced-tuple-unpacking - df_stock, _ = ts.get_daily_adjusted(symbol=s_ticker, outputsize="full") - # Intraday - else: - # pylint: disable=unbalanced-tuple-unpacking - df_stock, _ = ts.get_intraday( - symbol=s_ticker, outputsize="full", interval=s_interval - ) - - except Exception as e: - print(e) - print("Either the ticker or the API_KEY are invalids. Try again!") - return - df_stock.sort_index(ascending=True, inplace=True) # Slice dataframe from the starting date YYYY-MM-DD selected diff --git a/gamestonk_terminal/options/op_controller.py b/gamestonk_terminal/options/op_controller.py index 9459833b180..e373ba1f677 100644 --- a/gamestonk_terminal/options/op_controller.py +++ b/gamestonk_terminal/options/op_controller.py @@ -9,7 +9,7 @@ import yfinance as yf from prompt_toolkit.completion import NestedCompleter from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn from gamestonk_terminal import feature_flags as gtff -from gamestonk_terminal.options import volume +from gamestonk_terminal.options import volume_view from gamestonk_terminal.menu import session @@ -17,16 +17,15 @@ class OptionsController: """Options Controller class.""" # Command choices - CHOICES = ["help", "q", "quit", "exp", "volume"] + CHOICES = ["help", "q", "quit", "exp", "voi", "vcalls", "vputs"] - def __init__( - self, - ticker: str, - ): + def __init__(self, ticker: str, last_adj_close_price: float): """Construct data.""" self.ticker = ticker - self.raw_data_options = yf.Ticker(self.ticker) - self.expiry_date = self.raw_data_options.options[0] + self.yf_ticker_data = yf.Ticker(self.ticker) + self.expiry_date = self.yf_ticker_data.options[0] + self.options = self.yf_ticker_data.option_chain(self.expiry_date) + self.last_adj_close_price = last_adj_close_price self.op_parser = argparse.ArgumentParser(add_help=False, prog="op") self.op_parser.add_argument( "cmd", @@ -48,7 +47,7 @@ class OptionsController: action="store", type=int, default=-1, - choices=range(len(self.raw_data_options.options)), + choices=range(len(self.yf_ticker_data.options)), help=f"Expiry date index for {self.ticker}.", ) @@ -64,12 +63,13 @@ class OptionsController: # Print possible expiry dates if ns_parser.n_date == -1: print("\nAvailable expiry dates:") - for i, d in enumerate(self.raw_data_options.options): + for i, d in enumerate(self.yf_ticker_data.options): print(f" {(2-len(str(i)))*' '}{i}. {d}") # It means an expiry date was correctly selected else: - self.expiry_date = self.raw_data_options.options[ns_parser.n_date] + self.expiry_date = self.yf_ticker_data.options[ns_parser.n_date] + self.options = self.yf_ticker_data.option_chain(self.expiry_date) print(f"\nSelected expiry date : {self.expiry_date}") except Exception as e: @@ -89,7 +89,9 @@ class OptionsController: print(f"Selected expiry date: {expiry_date}") print("") print(" exp see/set expiry date") - print(" volume plot options trading volume / open interest") + print(" voi volume + open interest options trading plot") + print(" vcalls calls volume + open interest plot") + print(" vputs puts volume + open interest plot") print("") return @@ -121,26 +123,47 @@ class OptionsController: """Process Quit command - quit the program.""" return True - def call_volume(self, _): - """Process volume command.""" - volume.volume_graph( - self.raw_data_options, - self.ticker, - self.expiry_date, - volume_percentile_threshold=60, - ) - print("") - def call_exp(self, other_args: List[str]): """Process exp command.""" self.expiry_dates(self, other_args) + def call_voi(self, other_args: List[str]): + """Process voi command.""" + volume_view.plot_volume_open_interest( + other_args, + self.ticker, + self.expiry_date, + self.last_adj_close_price, + self.options.calls, + self.options.puts, + ) -def menu(ticker: str): - """Options info Menu.""" + def call_vcalls(self, other_args: List[str]): + """Process vcalls command.""" + volume_view.plot_calls_volume_open_interest( + other_args, + self.ticker, + self.expiry_date, + self.last_adj_close_price, + self.options.calls, + ) + + def call_vputs(self, other_args: List[str]): + """Process vcalls command.""" + volume_view.plot_puts_volume_open_interest( + other_args, + self.ticker, + self.expiry_date, + self.last_adj_close_price, + self.options.puts, + ) + + +def menu(ticker: str, last_adj_close_price: float): + """ Options Menu. """ try: - op_controller = OptionsController(ticker) + op_controller = OptionsController(ticker, last_adj_close_price) op_controller.call_help(None) except IndexError: print("No options found for " + ticker) diff --git a/gamestonk_terminal/options/volume.py b/gamestonk_terminal/options/volume.py deleted file mode 100644 index 3b87ac8efe7..00000000000 --- a/gamestonk_terminal/options/volume.py +++ /dev/null @@ -1,176 +0,0 @@ -"""options info. [Source: Yahoo Finance].""" -from bisect import bisect_left -import seaborn as sns -import pandas as pd -import matplotlib.pyplot as plt -import numpy as np - -from gamestonk_terminal.helper_funcs import plot_autoscale -from gamestonk_terminal import config_plot as cfgPlot - - -def volume_graph(raw_data, ticker_name, exp_date, volume_percentile_threshold=50): - """Docstring make linter hap.""" - # SET VOLUME TO BE FILTERED, default = 50 - PERCENTILE_THRESHOLD = volume_percentile_threshold - - TICKER_NAME = ticker_name - raw_data_options = raw_data - EXP_DATE = exp_date - - # current stock price - spot = __get_current_spot(raw_data_options) - - calls = __parse_opt_data(raw_data_options, EXP_DATE) - puts = __parse_opt_data(raw_data_options, EXP_DATE, is_calls=False) - - calls = __add_max_pain_data(calls, spot) - - puts = __add_max_pain_data(puts, spot, is_calls=False) - - max_pain = __calc_max_pain(calls, puts) - - # Initialize the matplotlib figure - _, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI) - # _, ax = plt.subplots(figsize=(12, 10)) - - # make x axis symmetric - axis_origin = max(abs(max(puts["oi+v"])), abs(max(calls["oi+v"]))) - ax.set_xlim(-axis_origin, +axis_origin) - - VOLUME_THRESHOLD = np.percentile(calls["oi+v"], PERCENTILE_THRESHOLD) - - sns.set_style(style="darkgrid") - - g = sns.barplot( - x="oi+v", - y="strike", - data=calls[calls["oi+v"] > VOLUME_THRESHOLD], - label="Calls: Open Interest", - color="lightgreen", - orient="h", - ) - - g = sns.barplot( - x="volume", - y="strike", - data=calls[calls["oi+v"] > VOLUME_THRESHOLD], - label="Calls: Volume", - color="green", - orient="h", - ) - - g = sns.barplot( - x="oi+v", - y="strike", - data=puts[puts["oi+v"] < -VOLUME_THRESHOLD], - label="Puts: Open Interest", - color="pink", - orient="h", - ) - - g = sns.barplot( - x="volume", - y="strike", - data=puts[puts["oi+v"] < -VOLUME_THRESHOLD], - label="Puts: Volume", - color="red", - orient="h", - ) - - # draw spot line - s = [float(strike.get_text()) for strike in ax.get_yticklabels()] - spot_index = bisect_left(s, spot) # find where the spot is on the graph - spot_line = ax.axhline(spot_index, ls="--", color="dodgerblue", alpha=0.3) - - # draw max pain line - max_pain_index = bisect_left(s, max_pain) - max_pain_line = ax.axhline(max_pain_index, ls="-", color="black", alpha=0.3) - max_pain_line.set_linewidth(5) - - # ax.axhline(max_pain_index, ls='--') - # format ticklabels without - for puts - g.set_xticks(g.get_xticks()) - xlabels = [f"{x:,.0f}".replace("-", "") for x in g.get_xticks()] - g.set_xticklabels(xlabels) - - plt.title( - f"{TICKER_NAME.upper()} volumes for {EXP_DATE} (open interest displayed only during market hours)" - ) - ax.invert_yaxis() - - # ax.spines['left'].set_position('center') - - _ = ax.legend() - handles, _ = ax.get_legend_handles_labels() - handles.append(spot_line) - handles.append(max_pain_line) - - # create legend labels + add to graph - labels = [ - "Calls open interest", - "Calls volume ", - "Puts open interest", - "Puts volume", - "Current stock price", - f"Max pain = {max_pain}", - ] - - plt.legend(handles=handles[:], labels=labels) - sns.despine(left=True, bottom=True) - plt.show() - - -def __add_max_pain_data(df, spot, is_calls=True): - # max pain parsing + calculation - df["spot"] = round(spot, 2) - if is_calls: - df["dv"] = spot - df.index - else: - df["dv"] = df.index - spot - df["dv"] = df["dv"].apply(lambda x: max(0, x)) - df["dv"] = abs(df["dv"] * df["volume"]) - return df - - -def __calc_max_pain(calls, puts): - - df = pd.merge(calls, puts, left_index=True, right_index=True) - df["dv"] = round(df["dv_x"] + df["dv_y"], 2) - - max_pain = df["dv"].idxmax() - return max_pain - - -def __parse_opt_data(raw_data_options, exp_date, is_calls=True): - # get option chain for specific expiration - opt = raw_data_options.option_chain(exp_date) - - # PARSE DATA - if is_calls: - option_data = opt.calls - flag = "calls" - else: - option_data = opt.puts - flag = "puts" - - data = option_data.pivot_table( - index="strike", values=["volume", "openInterest"], aggfunc="sum" - ).reindex() - - data["strike"] = data.index - data["type"] = flag - - if is_calls: - data["openInterest"] = data["openInterest"] - data["volume"] = data["volume"] - else: - data["openInterest"] = -data["openInterest"] - data["volume"] = -data["volume"] - - data["oi+v"] = data["openInterest"] + data["volume"] - return data - - -def __get_current_spot(raw_data_options): - return raw_data_options.history().tail(1)["Close"].iloc[0] diff --git a/gamestonk_terminal/options/volume_view.py b/gamestonk_terminal/options/volume_view.py new file mode 100644 index 00000000000..a20e4cafc54 --- /dev/null +++ b/gamestonk_terminal/options/volume_view.py @@ -0,0 +1,463 @@ +""" Volume view """ +__docformat__ = "numpy" + +from bisect import bisect_left +import seaborn as sns +from typing import List +import argparse +import pandas as pd +import matplotlib.pyplot as plt +import numpy as np + +from gamestonk_terminal.helper_funcs import ( + plot_autoscale, + check_non_negative, + parse_known_args_and_warn, +) +from gamestonk_terminal import config_plot as cfgPlot +from gamestonk_terminal import feature_flags as gtff + + +def plot_volume_open_interest( + other_args: List[str], + ticker: str, + exp_date: str, + last_adj_close_price: float, + op_calls: pd.DataFrame, + op_puts: pd.DataFrame, +): + """Options volume and open interest + + Parameters + ---------- + other_args : List[str] + Command line arguments to be processed with argparse + ticker : str + Main ticker to compare income + exp_date : str + Expiry date of the option + last_adj_close_price: float + Last adjusted closing price + op_calls: pd.DataFrame + Option data calls + op_puts: pd.DataFrame + Option data puts + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="voi", + description=""" + Plots Volume + Open Interest of calls vs puts. [Source: Yahoo Finance] + """, + ) + parser.add_argument( + "-v", + "--minv", + dest="min_vol", + type=check_non_negative, + default=-1, + help="minimum volume (considering open interest) threshold of the plot.", + ) + parser.add_argument( + "-m", + "--min", + dest="min_sp", + type=check_non_negative, + default=-1, + help="minimum strike price to consider in the plot.", + ) + parser.add_argument( + "-M", + "--max", + dest="max_sp", + type=check_non_negative, + default=-1, + help="maximum strike price to consider in the plot.", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + df_calls, df_puts, max_pain = get_calls_puts_maxpain( + op_calls, op_puts, last_adj_close_price + ) + + if ( + ns_parser.min_vol == -1 + and ns_parser.min_sp == -1 + and ns_parser.max_sp == -1 + ): + # If no argument provided, we use the percentile 50 to get 50% of upper volume data + volume_percentile_threshold = 50 + min_vol_calls = np.percentile(df_calls["oi+v"], volume_percentile_threshold) + min_vol_puts = np.percentile(df_puts["oi+v"], volume_percentile_threshold) + + df_calls = df_calls[df_calls["oi+v"] > min_vol_calls] + df_puts = df_puts[df_puts["oi+v"] < min_vol_puts] + + else: + if ns_parser.min_vol > -1: + df_calls = df_calls[df_calls["oi+v"] > ns_parser.min_vol] + df_puts = df_puts[df_puts["oi+v"] < -ns_parser.min_vol] + + if ns_parser.min_sp > -1: + df_calls = df_calls[df_calls["strike"] > ns_parser.min_sp] + df_puts = df_puts[df_puts["strike"] > ns_parser.min_sp] + + if ns_parser.max_sp > -1: + df_calls = df_calls[df_calls["strike"] < ns_parser.max_sp] + df_puts = df_puts[df_puts["strike"] < ns_parser.max_sp] + + if df_calls.empty and df_puts.empty: + print( + "The filtering applied is too strong, there is no data available for such conditions.\n" + ) + return + + # Initialize the matplotlib figure + _, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI) + + # make x axis symmetric + axis_origin = max(abs(max(df_puts["oi+v"])), abs(max(df_calls["oi+v"]))) + ax.set_xlim(-axis_origin, +axis_origin) + + sns.set_style(style="darkgrid") + + g = sns.barplot( + x="oi+v", + y="strike", + data=df_calls, + label="Calls: Open Interest", + color="lightgreen", + orient="h", + ) + + g = sns.barplot( + x="volume", + y="strike", + data=df_calls, + label="Calls: Volume", + color="green", + orient="h", + ) + + g = sns.barplot( + x="oi+v", + y="strike", + data=df_puts, + label="Puts: Open Interest", + color="pink", + orient="h", + ) + + g = sns.barplot( + x="volume", + y="strike", + data=df_puts, + label="Puts: Volume", + color="red", + orient="h", + ) + + # draw spot line + s = [float(strike.get_text()) for strike in ax.get_yticklabels()] + spot_index = bisect_left( + s, last_adj_close_price + ) # find where the spot is on the graph + spot_line = ax.axhline(spot_index, ls="--", color="dodgerblue", alpha=0.3) + + # draw max pain line + max_pain_index = bisect_left(s, max_pain) + max_pain_line = ax.axhline(max_pain_index, ls="-", color="black", alpha=0.3) + max_pain_line.set_linewidth(5) + + # format ticklabels without - for puts + g.set_xticks(g.get_xticks()) + xlabels = [f"{x:,.0f}".replace("-", "") for x in g.get_xticks()] + g.set_xticklabels(xlabels) + + plt.title( + f"{ticker} volumes for {exp_date} (open interest displayed only during market hours)" + ) + ax.invert_yaxis() + + _ = ax.legend() + handles, _ = ax.get_legend_handles_labels() + handles.append(spot_line) + handles.append(max_pain_line) + + # create legend labels + add to graph + labels = [ + "Calls open interest", + "Calls volume ", + "Puts open interest", + "Puts volume", + "Current stock price", + f"Max pain = {max_pain}", + ] + + plt.legend(handles=handles[:], labels=labels) + sns.despine(left=True, bottom=True) + + if gtff.USE_ION: + plt.ion() + plt.show() + + print("") + + except Exception as e: + print(e, "\n") + return + + +def get_calls_puts_maxpain( + op_calls: pd.DataFrame, op_puts: pd.DataFrame, last_adj_close_price: float +): + """Get calls and puts dataframes, and max pain + + Parameters + ---------- + op_calls: pd.DataFrame + Option data calls + op_puts: pd.DataFrame + Option data puts + last_adj_close_price: float + Last adjusted closing price + + Returns + ---------- + pd.DataFrame + Processed calls dataframe + pd.DataFrame + Processed puts dataframe + float + Max pain + """ + df_calls = pd.DataFrame() + df_puts = pd.DataFrame() + max_pain = 0 + + if not op_calls.empty: + # Process Calls Data + df_calls = op_calls.pivot_table( + index="strike", values=["volume", "openInterest"], aggfunc="sum" + ).reindex() + df_calls["strike"] = df_calls.index + df_calls["type"] = "calls" + df_calls["openInterest"] = df_calls["openInterest"] + df_calls["volume"] = df_calls["volume"] + df_calls["oi+v"] = df_calls["openInterest"] + df_calls["volume"] + df_calls["spot"] = round(last_adj_close_price, 2) + df_calls["dv"] = last_adj_close_price - df_calls.index + df_calls["dv"] = df_calls["dv"].apply(lambda x: max(0, x)) + df_calls["dv"] = abs(df_calls["dv"] * df_calls["volume"]) + + if not op_puts.empty: + # Process Puts Data + df_puts = op_puts.pivot_table( + index="strike", values=["volume", "openInterest"], aggfunc="sum" + ).reindex() + df_puts["strike"] = df_puts.index + df_puts["type"] = "puts" + df_puts["openInterest"] = df_puts["openInterest"] + df_puts["volume"] = -df_puts["volume"] + df_puts["openInterest"] = -df_puts["openInterest"] + df_puts["oi+v"] = df_puts["openInterest"] + df_puts["volume"] + df_puts["spot"] = round(last_adj_close_price, 2) + df_puts["dv"] = df_puts.index - last_adj_close_price + df_puts["dv"] = df_puts["dv"].apply(lambda x: max(0, x)) + df_puts["dv"] = abs(df_puts["dv"] * df_puts["volume"]) + + if not op_calls.empty and not op_puts.empty: + # Get max pain + df_opt = pd.merge(df_calls, df_puts, left_index=True, right_index=True) + df_opt["dv"] = round(df_opt["dv_x"] + df_opt["dv_y"], 2) + max_pain = df_opt["dv"].idxmax() + + return df_calls, df_puts, max_pain + + +def plot_calls_volume_open_interest( + other_args: List[str], + ticker: str, + exp_date: str, + last_adj_close_price: float, + op_calls: pd.DataFrame, +): + """Options volume and open interest + + Parameters + ---------- + other_args : List[str] + Command line arguments to be processed with argparse + ticker : str + Main ticker to compare income + exp_date : str + Expiry date of the option + last_adj_close_price: float + Last adjusted closing price + op_calls: pd.DataFrame + Option data calls + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="vcalls", + description=""" + Plots Calls Volume + Open Interest. [Source: Yahoo Finance] + """, + ) + parser.add_argument( + "-m", + "--min", + dest="min_sp", + type=check_non_negative, + default=-1, + help="minimum strike price to consider in the plot.", + ) + parser.add_argument( + "-M", + "--max", + dest="max_sp", + type=check_non_negative, + default=-1, + help="maximum strike price to consider in the plot.", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + df_calls, _, _ = get_calls_puts_maxpain( + op_calls, pd.DataFrame(), last_adj_close_price + ) + + if ns_parser.min_sp == -1: + min_strike = 0.75 * last_adj_close_price + else: + min_strike = ns_parser.min_sp + + if ns_parser.max_sp == -1: + max_strike = 1.25 * last_adj_close_price + else: + max_strike = ns_parser.max_sp + + plt.figure(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI) + + plt.axvline(last_adj_close_price, lw=2) + plt.bar( + df_calls["strike"], + df_calls["volume"] + df_calls["openInterest"], + color="lightgreen", + ) + plt.bar(df_calls["strike"], df_calls["volume"], color="green") + + plt.title(f"{ticker} calls volumes for {exp_date} ") + plt.legend(["Stock Price", "Open Interest", "Volume"]) + plt.xlabel("Strike Price") + plt.ylabel("Volume") + plt.xlim([min_strike, max_strike]) + + if gtff.USE_ION: + plt.ion() + plt.show() + + print("") + + except Exception as e: + print(e, "\n") + return + + +def plot_puts_volume_open_interest( + other_args: List[str], + ticker: str, + exp_date: str, + last_adj_close_price: float, + op_puts: pd.DataFrame, +): + """Options volume and open interest + + Parameters + ---------- + other_args : List[str] + Command line arguments to be processed with argparse + ticker : str + Main ticker to compare income + exp_date : str + Expiry date of the option + last_adj_close_price: float + Last adjusted closing price + op_puts: pd.DataFrame + Option data puts + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="vputs", + description=""" + Plots Puts Volume + Open Interest. [Source: Yahoo Finance] + """, + ) + parser.add_argument( + "-m", + "--min", + dest="min_sp", + type=check_non_negative, + default=-1, + help="minimum strike price to consider in the plot.", + ) + parser.add_argument( + "-M", + "--max", + dest="max_sp", + type=check_non_negative, + default=-1, + help="maximum strike price to consider in the plot.", + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + _, df_puts, _ = get_calls_puts_maxpain( + pd.DataFrame(), op_puts, last_adj_close_price + ) + + if ns_parser.min_sp == -1: + min_strike = 0.75 * last_adj_close_price + else: + min_strike = ns_parser.min_sp + + if ns_parser.max_sp == -1: + max_strike = 1.25 * last_adj_close_price + else: + max_strike = ns_parser.max_sp + + plt.figure(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI) + + plt.axvline(last_adj_close_price, lw=2) + plt.bar( + df_puts["strike"], + -df_puts["volume"] - df_puts["openInterest"], + color="pink", + ) + plt.bar(df_puts["strike"], -df_puts["volume"], color="red") + + plt.title(f"{ticker} puts volumes for {exp_date} ") + plt.legend(["Stock Price", "Open Interest", "Volume"]) + plt.xlabel("Strike Price") + plt.ylabel("Volume") + plt.xlim([min_strike, max_strike]) + + if gtff.USE_ION: + plt.ion() + plt.show() + + print("") + + except Exception as e: + print(e, "\n") + return diff --git a/gamestonk_terminal/portfolio_optimization/README.md b/gamestonk_terminal/portfolio_optimization/README.md new file mode 100644 index 00000000000..bf96cf77629 --- /dev/null +++ b/gamestonk_terminal/portfolio_optimization/README.md @@ -0,0 +1,169 @@ +# Portfolio Optimization + +This menu aims to discover optimal portfolios for selected stocks. + +Currently we support two methods: + +* [Property Weighting](#weighting) + * Equal Weights + * Market Cap Weighting + * Divident Yield Weighting +* [Mean-Variance-Optimization](#eff_front) + * Max sharpe ratio + * Minimum volatility + * Maximum returns at given risk level + * Minimum risk level at a given return + +## Procedure +There are three ways to load stocks to be analyzed. +* add +* select +* from ca menu +###add +Adds selected tickers to the menu to be considered + +```` +usage: add ticker1,ticker2,ticker3,... +```` +###select +Clears current list and loads selected ticker +```` +usage: select ticker1,ticker2,ticker3,... +```` + +### ca menu +From the ca menu, the loaded ticker and the selected similar tickers can be loaded by entering the `> po` menu. + +From this `po` menu, you can return to `ca` using `ca`, which will keep your loaded stock, but reset the selected stocks from that menu. + +Once your stocks are listed, you can select one of the options. +## Property weighted +* equal weights +* market cap weighted +* dividend yield weighted +### equal weights +Returns an equally weighted portfolio where the weights are 1/number of stocks. +```` +equal_weight [-v --value VALUE] [--pie] +```` +* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie Flag that displays a pie chart of the allocations. + +### market cap weighted +Returns portfolio values that are weighted by their relative Market Cap. +```` +mkt_cap [-v --value VALUE] [--pie] +```` +* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie Flag that displays a pie chart of the allocations. + +###dividend yield weighted +Returns portfolio values that are weighted by relative Dividend Yield. +```` +div_yield [-v --value VALUE] [--pie] +```` +* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie Flag that displays a pie chart of the allocations. + +## Mean Variance Optimization + +These approaches are based off of the efficient frontier approach, which is meant to solve the following optimization problem. + + + +With constraints: + + + +\ + + +Where S is the covariance matrix between stocks and R is the expected returns. The condition that all weights add up to 1 +just implies that you want to have a net long portfolio (with no margin). +A long-short portfolio can have negative weights and usually wants to have everything add up to 0 for a market-neutral strategy. + +Currently, we do not allow for changing risk models or adding constraints. If there is something specific, please submit a feature request, or if you can +write it, feel free to add a PR! + +All of our current implementations use the [PyPortFolioOpt](#https://pyportfolioopt.readthedocs.io/en/latest/index.html) package. + +### max_sharpe +The sharpe ratio is defined as + +(Mean Returns - Risk Free Rate)/(Standard Deviation of Returns) + +The usage is: +```` +max_sharpe [-p PERIOD] [-v --value VALUE] [--pie] +```` +* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo. +* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie Flag that displays a pie chart of the allocations. + +### min_vol +This portfolio minimizes the total volatility, which also means it has the smallest returns among the efficient frontier. +The usage is: +```` +min_vol [-p PERIOD] [-v --value VALUE] [--pie] +```` +* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo. +* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie Flag that displays a pie chart of the allocations. + +### eff_risk +This portfolio maximizes the returns at a given risk tolerance +The usage is: +```` +eff_risk [-p PERIOD] [-r --risk RISK_LEVEL] [-v --value VALUE] [--pie] +```` +* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo. +* -r/--risk Risk tolerance. Default is 0.1 (10%) +* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie Flag that displays a pie chart of the allocations. + +### eff_ret +This portfolio minimizes the risk at a given return level +The usage is: +```` +eff_ret [-p PERIOD] [-r --return] [-v --value VALUE] [--pie] +```` +* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo. +* -r/--return. Desired return. Default is 0.1 (10%) +* -v/--value If provided, this represents an actual allocation amount for the portfolio. Defaults to 1, which just returns the weights. +* --pie Flag that displays a pie chart of the allocations. + +### show_eff +This function plots random portfolios based on their risk and returns and shows the efficient frontier. +The usage is: +```` +show_eff [-p PERIOD] [-n N_PORTFOLIOS] +```` +* -p/--period Amount of time to retrieve data from yfinance. Options are: 1d,5d,1mo,3mo,6mo,1y,2y,5y,10y,ytd,max and it defaults to 3mo. +* -n Number of portfolios to simulate. + +##Sample Usage +In this example, we generate weights for a list of 6 stocks using the eff_ret command. This optimization looks to maximize returns +at a given risk level. We start by adding the stocks we want to analyze: +```` +select aapl,amzn,msft,f,gm,ge +```` +Which shows: +```` +Current Tickers: GE, GM, AMZN, AAPL, F, MSFT +```` +To perform the optimization, we will set a target return of 25% (.25). Given how stocks performed during COVID, +there is a lot of volatility, so many optimizations may not get low volatility. The module also returns annualized volatility, +so the number is your portfolio volatility * `sqrt(252`. This optimization will be (including a pie chart!). Note we could also supply a different +time period, which changes the expected returns and historical volatility, which changes the optimization. We could also specify a dollar +amount that you wish to allocate using the `-v` flag. +```` +eff_ret -r .25 --pie +```` +The console will show (numbers will vary based on when this is done). +![console](https://user-images.githubusercontent.com/18151143/114740311-bd429c80-9d17-11eb-90e2-97430781431a.png) + +And the pie chart: + +![yummypie](https://user-images.githubusercontent.com/18151143/114740289-b9167f00-9d17-11eb-9c29-470785b21d09.png) + +Note that since `AAPL` had zero allocation, it was omitted from the chart. \ No newline at end of file diff --git a/gamestonk_terminal/portfolio_optimization/__init__.py b/gamestonk_terminal/portfolio_optimization/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_helper.py b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py new file mode 100644 index 00000000000..3ca81f396a2 --- /dev/null +++ b/gamestonk_terminal/portfolio_optimization/optimizer_helper.py @@ -0,0 +1,226 @@ +""" Portfolio Optimization Helper Functions""" +__docformat__ = "numpy" + +import argparse +from typing import List +import math +import pandas as pd +import numpy as np +import matplotlib.pyplot as plt +import yfinance as yf +from pypfopt.efficient_frontier import EfficientFrontier +from pypfopt import risk_models +from pypfopt import expected_returns +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import plot_autoscale, parse_known_args_and_warn + +title_maps = { + "max_sharpe": "Maximum Sharpe Portfolio", + "min_volatility": "Minimum Volatility Portfolio", + "eff_risk": "Maximum Return Portfolio at Risk = {:1f} %", + "eff_ret": "Minimum Volatility Portfolio at Target Return = {:.1f} %", + "equal": "Equally Weighted Portfolio", + "marketCap": "MarketCap Weighted Portfolio", + "dividendYield": "Dividend Yield Weighted Portfolio", +} + + +def process_stocks(list_of_stocks: List[str], period: str = "3mo") -> pd.DataFrame: + """ + + Parameters + ---------- + list_of_stocks: List[str] + List of tickers to get historical data for + period: str + Period to get data from yfinance + + Returns + ------- + stock_closes: DataFrame + DataFrame containing daily (adjusted) close prices for each stock in list + """ + stock_prices = yf.download( + list_of_stocks, period=period, progress=False, group_by="ticker" + ) + stock_closes = pd.DataFrame(index=stock_prices.index) + for stock in list_of_stocks: + stock_closes[stock] = stock_prices[stock]["Adj Close"] + return stock_closes + + +def prepare_efficient_frontier(stock_prices: pd.DataFrame): + """ + Take in a dataframe of prices and return an efficient frontier object + Parameters + ---------- + stock_prices : DataFrame + DataFrame where indices are DateTime and columns are stocks + + Returns + ------- + ef: EfficientFrontier + EfficientFrontier object + """ + mu = expected_returns.mean_historical_return(stock_prices) + S = risk_models.sample_cov(stock_prices) + ef = EfficientFrontier(mu, S) + return ef + + +def display_weights(weights: dict): + """ + Print weights in a nice format + Parameters + ---------- + weights: dict + weights to display. Keys are stocks. Values are either weights or values if -v specified + """ + if not weights: + return + weight_df = pd.DataFrame.from_dict(data=weights, orient="index", columns=["value"]) + if math.isclose(weight_df.sum()["value"], 1, rel_tol=0.1): + weight_df["weight"] = (weight_df["value"] * 100).astype(str).apply( + lambda s: s[:6] + ) + " %" + print(pd.DataFrame(weight_df["weight"])) + else: + print(weight_df) + + +def my_autopct(x): + """Function for autopct of plt.pie. This results in values not being printed in the pie if they are 'too small'""" + if x > 4: + return f"{x:.2f} %" + else: + return "" + + +def pie_chart_weights(weights: dict, optimizer: str, value: float): + """ + Show a pie chart of holdings + Parameters + ---------- + weights: dict + weights to display. Keys are stocks. Values are either weights or values if -v specified + optimzer: str + Optimization technique used for title + """ + plt.close("all") + if not weights: + return + + init_stocks = list(weights.keys()) + init_sizes = list(weights.values()) + stocks = [] + sizes = [] + for stock, size in zip(init_stocks, init_sizes): + if size > 0: + stocks.append(stock) + sizes.append(size) + + total_size = np.sum(sizes) + + leg_labels = [ + f"{str(a)}: {str(round(100*b/total_size,3))[:4]}%" + for a, b in zip(stocks, sizes) + ] + plt.figure(figsize=plot_autoscale(), dpi=PLOT_DPI) + if math.isclose(sum(sizes), 1, rel_tol=0.1): + wedges, _, autotexts = plt.pie( + sizes, + labels=stocks, + autopct=my_autopct, + textprops=dict(color="k"), + explode=[s / (5 * total_size) for s in sizes], + normalize=True, + shadow=True, + ) + else: + wedges, _, autotexts = plt.pie( + sizes, + labels=stocks, + autopct="", + textprops=dict(color="k"), + explode=[s / (5 * total_size) for s in sizes], + normalize=True, + shadow=True, + ) + for i, a in enumerate(autotexts): + if sizes[i] / total_size > 0.05: + a.set_text(f"{sizes[i]:.2f}") + else: + a.set_text("") + + plt.axis("equal") + + plt.legend( + wedges, + leg_labels, + title="Stocks", + loc="center left", + bbox_to_anchor=(0.85, 0, 0.5, 1), + ) + + plt.setp(autotexts, size=8, weight="bold") + + if optimizer in ["eff_ret", "eff_risk"]: + plt.title(title_maps[optimizer].format(100 * value)) + else: + plt.title(title_maps[optimizer]) + + if gtff.USE_ION: + plt.ion() + + plt.show() + print("") + + +def parse_from_port_type( + parser_in: argparse.ArgumentParser, port_type: str, other_args: List[str] +): + """ + + Parameters + ---------- + parser_in: ArgumentParser + parser to get data from + port_type: str + Type of optimization that will be done. One of ["max_sharpe","min_vol", "eff_risk", "eff_ret"] + other_args: List[str] + Arguments passed to function + Returns + ------- + + ns_parser: + Parsed arguments + """ + + if port_type in ["max_sharpe", "min_volatility"]: + ns_parser = parse_known_args_and_warn(parser_in, other_args) + if not ns_parser: + return None + return ns_parser + + elif port_type == "eff_risk": + parser_in.add_argument( + "-r", "--risk", type=float, dest="risk_level", default=0.1 + ) + ns_parser = parse_known_args_and_warn(parser_in, other_args) + if not ns_parser: + return None + return ns_parser + + elif port_type == "eff_ret": + parser_in.add_argument( + "-r", "--return", type=float, dest="target_return", default=0.1 + ) + + ns_parser = parse_known_args_and_warn(parser_in, other_args) + if not ns_parser: + return None + return ns_parser + else: + print("Incorrect portfolio optimizer type\n") + return None diff --git a/gamestonk_terminal/portfolio_optimization/optimizer_view.py b/gamestonk_terminal/portfolio_optimization/optimizer_view.py new file mode 100644 index 00000000000..e9d74a76138 --- /dev/null +++ b/gamestonk_terminal/portfolio_optimization/optimizer_view.py @@ -0,0 +1,337 @@ +""" Portfolio Optimization Functions""" +__docformat__ = "numpy" + +import argparse +from typing import List +import matplotlib.pyplot as plt +import numpy as np +import yfinance as yf +from pypfopt import plotting +from pypfopt import risk_models +from pypfopt import expected_returns +from pypfopt import EfficientFrontier +from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, plot_autoscale +from gamestonk_terminal.portfolio_optimization.optimizer_helper import ( + process_stocks, + prepare_efficient_frontier, + pie_chart_weights, + display_weights, + parse_from_port_type, +) +from gamestonk_terminal.config_plot import PLOT_DPI +from gamestonk_terminal import feature_flags as gtff + +period_choices = [ + "1d", + "5d", + "1mo", + "3mo", + "6mo", + "1y", + "2y", + "5y", + "10y", + "ytd", + "max", +] + + +def equal_weight(stocks: List[str], other_args: List[str]): + """ + Equally weighted portfolio, where weight = 1/# of stocks + + Parameters + ---------- + stocks: List[str] + List of tickers to be included in optimization + + Returns + ------- + weights : dict + Dictionary of weights where keys are the tickers + + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="equal_weight", + description="Return equally weighted portfolio holdings", + ) + + parser.add_argument( + "-v", + "--value", + default=1, + type=float, + dest="value", + help="Amount to allocate to portfolio", + ) + parser.add_argument( + "--pie", + action="store_true", + dest="pie", + default=False, + help="Display a pie chart for weights", + ) + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + values = {} + n_stocks = len(stocks) + for stock in stocks: + values[stock] = ns_parser.value * round(1 / n_stocks, 5) + if ns_parser.pie: + pie_chart_weights(values, "equal", 0) + if n_stocks >= 1: + print("Equal Weight Portfolio: ") + display_weights(values) + print("") + + except Exception as e: + print(e) + print("") + + +def property_weighting(stocks: List[str], property_type: str, other_args: List[str]): + """ + Property weighted portfolio where each weight is the relative fraction. + Parameters + ---------- + stocks: List[str] + List of tickers to be included in optimization + property_type: str + Property to weight by. Can be anything in yfinance.Ticker().info. Examples: + "marketCap", "dividendYield", etc + + Returns + ------- + weights: dict + Dictionary of weights where keys are the tickers + """ + parser = argparse.ArgumentParser( + add_help=False, + prog="market_cap_weighted", + description="Return portfolio weights/values that are weighted by marketcap", + ) + + parser.add_argument( + "-v", + "--value", + default=1, + type=float, + dest="value", + help="Amount to allocate to portfolio", + ) + parser.add_argument( + "--pie", + action="store_true", + dest="pie", + default=False, + help="Display a pie chart for weights", + ) + weights = {} + prop = {} + prop_sum = 0 + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + for stock in stocks: + stock_prop = yf.Ticker(stock).info[property_type] + if stock_prop is None: + stock_prop = 0 + prop[stock] = stock_prop + prop_sum += stock_prop + for k, v in prop.items(): + weights[k] = round(v / prop_sum, 5) * ns_parser.value + + if ns_parser.pie: + pie_chart_weights(weights, property_type, 0) + + if property_type == "marketCap": + print("Market Cap Weighted Portfolio: ") + + elif property_type == "dividendYield": + print("Dividend Yield Weighted Portfolio: ") + + if len(stocks) >= 1: + display_weights(weights) + print("") + + except Exception as e: + print(e) + print("") + return + + +def ef_portfolio(stocks: List[str], port_type: str, other_args: List[str]): + """ + Return a portfolio based on condition in port_type Defaults to 3m of historical data + Parameters + ---------- + stocks: List[str] + List of the stocks to be included in the weights + port_type: str + Method to be used on ef object (example: max_sharpe, min_volatility) + Returns + ------- + weights: dict + Dictionary of weights where keys are the tickers. + """ + + parser = argparse.ArgumentParser(add_help=False, prog=port_type) + + parser.add_argument( + "-p", + "--period", + default="3mo", + dest="period", + help="period to get yfinance data from", + choices=period_choices, + ) + + parser.add_argument( + "-v", + "--value", + dest="value", + help="Amount to allocate to portfolio", + type=float, + default=1.0, + ) + parser.add_argument( + "--pie", + action="store_true", + dest="pie", + default=False, + help="Display a pie chart for weights", + ) + + try: + ns_parser = parse_from_port_type(parser, port_type, other_args) + if not ns_parser: + return + if len(stocks) < 2: + print("Please have at least 2 loaded tickers to calculate weights.\n") + return + period = ns_parser.period + stock_prices = process_stocks(stocks, period) + ef = prepare_efficient_frontier(stock_prices) + + if port_type == "max_sharpe": + + ef_sharpe = dict(ef.max_sharpe()) + weights = { + key: ns_parser.value * round(value, 5) + for key, value in ef_sharpe.items() + } + val = 0 + print("Weights that maximize Sharpe Ratio:") + + elif port_type == "min_volatility": + + ef_min_vol = dict(ef.min_volatility()) + weights = { + key: ns_parser.value * round(value, 5) + for key, value in ef_min_vol.items() + } + val = 0 + print("Weights that minimize volatility") + + elif port_type == "eff_risk": + + ef_eff_risk = dict(ef.efficient_risk(ns_parser.risk_level)) + weights = { + key: ns_parser.value * round(value, 5) + for key, value in ef_eff_risk.items() + } + val = ns_parser.risk_level + print(f"Weights for maximizing returns at risk = {100*val:.1f} %") + + elif port_type == "eff_ret": + + ef_eff_risk = dict(ef.efficient_return(ns_parser.target_return)) + weights = { + key: ns_parser.value * round(value, 5) + for key, value in ef_eff_risk.items() + } + val = ns_parser.target_return + print(f"Weights for minimizing risk at target return = {100*val:.1f} %") + + else: + raise ValueError("EF Method not found") + + if ns_parser.pie: + pie_chart_weights(weights, port_type, val) + + print("") + ef.portfolio_performance(verbose=True) + print("") + display_weights(weights) + print("") + + except Exception as e: + print(e) + print("") + return + + +def show_ef(stocks: List[str], other_args: List[str]): + parser = argparse.ArgumentParser(add_help=False, prog="ef") + + parser.add_argument( + "-p", + "--period", + default="3mo", + dest="period", + help="period to get yfinance data from", + choices=period_choices, + ) + parser.add_argument( + "-n", default=300, dest="n_port", help="number of portfolios to simulate" + ) + + try: + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + if len(stocks) < 2: + print("Please have at least 2 loaded tickers to calculate weights.\n") + return + + stock_prices = process_stocks(stocks, ns_parser.period) + mu = expected_returns.mean_historical_return(stock_prices) + S = risk_models.sample_cov(stock_prices) + ef = EfficientFrontier(mu, S) + fig, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI) + + # Generate random portfolios + n_samples = ns_parser.n_port + w = np.random.dirichlet(np.ones(len(mu)), n_samples) + rets = w.dot(mu) + stds = np.sqrt(np.diag(w @ S @ w.T)) + sharpes = rets / stds + ax.scatter(stds, rets, marker=".", c=sharpes, cmap="viridis_r") + + plotting.plot_efficient_frontier(ef, ax=ax, show_assets=True) + # Find the tangency portfolio + ef.max_sharpe() + ret_sharpe, std_sharpe, _ = ef.portfolio_performance() + ax.scatter(std_sharpe, ret_sharpe, marker="*", s=100, c="r", label="Max Sharpe") + + ax.set_title("Efficient Frontier") + ax.legend() + plt.tight_layout() + plt.grid(b=True, which="major", color="#666666", linestyle="-") + plt.minorticks_on() + plt.grid(b=True, which="minor", color="#999999", linestyle="-", alpha=0.2) + + if gtff.USE_ION: + plt.ion() + + plt.show() + print("") + + except Exception as e: + print(e) + print("") diff --git a/gamestonk_terminal/portfolio_optimization/po_controller.py b/gamestonk_terminal/portfolio_optimization/po_controller.py new file mode 100644 index 00000000000..df2f8a55fa6 --- /dev/null +++ b/gamestonk_terminal/portfolio_optimization/po_controller.py @@ -0,0 +1,206 @@ +"""Portfolio Optimization Controller Module""" +__docformat__ = "numpy" + + +import argparse +from typing import List +import matplotlib.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, parse_known_args_and_warn +from gamestonk_terminal.menu import session +from gamestonk_terminal.portfolio_optimization import optimizer_view as po_api + + +class PortfolioOptimization: + + CHOICES = [ + "help", + "q", + "quit", + "select", + "add", + "equal_weight", + "mkt_cap", + "div_yield", + "max_sharpe", + "min_vol", + "eff_risk", + "eff_ret", + "show_ef", + "yolo", + ] + + def __init__(self, tickers: List[str] = []): + """ + 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)) + # These will allow the ca menu to be re-access + self.ca_ticker = None + self.ca_similar = None + + @staticmethod + def print_help(tickers: List[str]): + """Print help""" + print("\nPortfolio Optimization:") + print(" help show this menu again") + print(" q quit this menu, and shows back to main menu") + print(" quit quit to abandon program") + print(f"\nCurrent Tickers: {('None', ', '.join(tickers))[bool(tickers)]}") + print("") + print(" add add ticker to optimize") + print(" select overwrite current tickers with new tickers") + print("") + print("Optimization:") + print("") + print(" equal_weight equally weighted portfolio") + print(" mkt_cap marketcap weighted portfolio") + print(" div_yield dividend weighted portfolio") + print(" max_sharpe portfolio with maximum sharpe ratio") + print(" min_vol portfolio with minimum volatility") + print(" eff_risk portfolio that maximizes returns at given risk") + print(" eff_ret portfolio that minimizes risk at given return") + print(" show_ef show the efficient frontier") + print("") + plt.close("all") + + 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 + """ + (known_args, other_args) = self.po_parser.parse_known_args(an_input.split()) + + return getattr( + self, "call_" + known_args.cmd, lambda: "Command not recognized!" + )(other_args) + + def call_help(self, _): + """Process Help command""" + self.print_help(self.tickers) + + 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_add(self, other_args: List[str]): + self.add_stocks(self, other_args) + + def call_select(self, other_args: List[str]): + self.tickers = [] + self.add_stocks(self, other_args) + + def call_equal_weight(self, other_args: List[str]): + po_api.equal_weight(self.tickers, other_args) + + def call_mkt_cap(self, other_args: List[str]): + po_api.property_weighting(self.tickers, "marketCap", other_args) + + def call_div_yield(self, other_args: List[str]): + po_api.property_weighting(self.tickers, "dividendYield", other_args) + + def call_max_sharpe(self, other_args: List[str]): + po_api.ef_portfolio(self.tickers, "max_sharpe", other_args) + + def call_min_vol(self, other_args: List[str]): + po_api.ef_portfolio(self.tickers, "min_volatility", other_args) + + def call_eff_risk(self, other_args: List[str]): + po_api.ef_portfolio(self.tickers, "eff_risk", other_args) + + def call_eff_ret(self, other_args: List[str]): + po_api.ef_portfolio(self.tickers, "eff_ret", other_args) + + def call_show_ef(self, other_args): + po_api.show_ef(self.tickers, other_args) + print("") + + def call_yolo(self, _): + # Easter egg :) + print("DFV YOLO") + print({"GME": 200}) + print("") + + @staticmethod + def add_stocks(self, other_args: List[str]): + """ Add ticker to current list for optimization""" + parser = argparse.ArgumentParser( + add_help=False, + prog="add", + description="""Add tickers for optimizing.""", + ) + parser.add_argument( + "-t", + "--tickers", + dest="add_tickers", + type=lambda s: [str(item).upper() for item in s.split(",")], + default=[], + help="add tickers to optimzation.", + ) + try: + + 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 + tickers = set(self.tickers) + for ticker in ns_parser.add_tickers: + tickers.add(ticker) + self.tickers = list(tickers) + + print( + f"\nCurrent Tickers: {('None', ', '.join(self.tickers))[bool(self.tickers)]}" + ) + print("") + except Exception as e: + print(e) + + print("") + + +def menu(tickers: List[str]): + """Portfolio Optimization Menu""" + if tickers == [""]: + tickers = [] + plt.close("all") + po_controller = PortfolioOptimization(tickers) + po_controller.call_help(tickers) + + while True: + # 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()} (po)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (po)> ") + + try: + process_input = po_controller.switch(an_input) + + if process_input is not None: + return process_input + + except SystemExit: + print("The command selected doesn't exist\n") + continue diff --git a/gamestonk_terminal/prediction_techniques/pred_controller.py b/gamestonk_terminal/prediction_techniques/pred_controller.py new file mode 100644 index 00000000000..8d2bdb4b2be --- /dev/null +++ b/gamestonk_terminal/prediction_techniques/pred_controller.py @@ -0,0 +1,207 @@ +import argparse +from typing import List +import pandas as pd +from datetime import datetime +import matplotlib.pyplot as plt +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import get_flair +from gamestonk_terminal.menu import session +from gamestonk_terminal.prediction_techniques import ( + arima, + ets, + knn, + neural_networks, + regression, + sma, +) +from prompt_toolkit.completion import NestedCompleter + + +class PredictionTechniquesController: + """Prediction Techniques Controller class""" + + # Command choices + CHOICES = [ + "help", + "q", + "quit", + "sma", + "ets", + "knn", + "linear", + "quadratic", + "cubic", + "regression", + "arima", + "mlp", + "rnn", + "lstm", + ] + + if gtff.ENABLE_FBPROPHET: + CHOICES.append("prophet") + + def __init__( + self, + stock: pd.DataFrame, + ticker: str, + start: datetime, + interval: str, + ): + """Constructor""" + self.stock = stock + self.ticker = ticker + self.start = start + self.interval = interval + self.pred_parser = argparse.ArgumentParser(add_help=False, prog="pred") + self.pred_parser.add_argument( + "cmd", + choices=self.CHOICES, + ) + + @staticmethod + def print_help(self): + """ Print help """ + + 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("\nPrediction Techniques:") + print(" help show this prediction techniques menu again") + print(" q quit this menu, and shows back to main menu") + print(" quit quit to abandon program") + print("") + print(" sma simple moving average") + print(" ets exponential smoothing (e.g. Holt-Winters)") + print(" knn k-Nearest Neighbors") + print(" linear linear regression (polynomial 1)") + print(" quadratic quadratic regression (polynomial 2)") + print(" cubic cubic regression (polynomial 3)") + print(" regression regression (other polynomial)") + print(" arima autoregressive integrated moving average") + print(" mlp MultiLayer Perceptron") + print(" rnn Recurrent Neural Network") + print(" lstm Long-Short Term Memory") + if gtff.ENABLE_FBPROPHET: + print(" prophet Facebook's prophet prediction") + print("") + + 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 + """ + (known_args, other_args) = self.pred_parser.parse_known_args(an_input.split()) + + return getattr( + self, "call_" + known_args.cmd, lambda: "Command not recognized!" + )(other_args) + + def call_help(self, _): + """Process Help command""" + self.print_help(self) + + 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_sma(self, other_args: List[str]): + """Process sma command""" + sma.simple_moving_average(other_args, self.ticker, self.stock) + + def call_ets(self, other_args: List[str]): + """Process ets command""" + ets.exponential_smoothing(other_args, self.ticker, self.stock) + + def call_knn(self, other_args: List[str]): + """Process knn command""" + knn.k_nearest_neighbors(other_args, self.ticker, self.stock) + + def call_linear(self, other_args: List[str]): + """Process linear command""" + regression.regression(other_args, self.ticker, self.stock, regression.LINEAR) + + def call_quadratic(self, other_args: List[str]): + """Process quadratic command""" + regression.regression(other_args, self.ticker, self.stock, regression.QUADRATIC) + + def call_cubic(self, other_args: List[str]): + """Process cubic command""" + regression.regression(other_args, self.ticker, self.stock, regression.CUBIC) + + def call_regression(self, other_args: List[str]): + """Process regression command""" + regression.regression( + other_args, self.ticker, self.stock, regression.USER_INPUT + ) + + def call_arima(self, other_args: List[str]): + """Process arima command""" + arima.arima(other_args, self.ticker, self.stock) + + def call_mlp(self, other_args: List[str]): + """Process mlp command""" + neural_networks.mlp(other_args, self.ticker, self.stock) + + def call_rnn(self, other_args: List[str]): + """Process rnn command""" + neural_networks.rnn(other_args, self.ticker, self.stock) + + def call_lstm(self, other_args: List[str]): + """Process lstm command""" + neural_networks.lstm(other_args, self.ticker, self.stock) + + if gtff.ENABLE_FBPROPHET: + + def call_prophet(self, other_args: List[str]): + """Process prophet command""" + from gamestonk_terminal.prediction_techniques import fbprophet + + fbprophet.fbprophet(other_args, self.ticker, self.stock) + + +def menu(stock: pd.DataFrame, ticker: str, start: datetime, interval: str): + """Comparison Analysis Menu""" + + pred_controller = PredictionTechniquesController(stock, ticker, start, interval) + pred_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 pred_controller.CHOICES} + ) + an_input = session.prompt( + f"{get_flair()} (pred)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (pred)> ") + + try: + plt.close("all") + + process_input = pred_controller.switch(an_input) + + if process_input is not None: + return process_input + + except SystemExit: + print("The command selected doesn't exist\n") + continue diff --git a/gamestonk_terminal/prediction_techniques/pred_menu.py b/gamestonk_terminal/prediction_techniques/pred_menu.py deleted file mode 100644 index 31460d87ae7..00000000000 --- a/gamestonk_terminal/prediction_techniques/pred_menu.py +++ /dev/null @@ -1,145 +0,0 @@ -import argparse - -import matplotlib.pyplot as plt -from gamestonk_terminal import feature_flags as gtff -from gamestonk_terminal.helper_funcs import get_flair -from gamestonk_terminal.menu import session -from gamestonk_terminal.prediction_techniques import ( - arima, - ets, - fbprophet, - knn, - neural_networks, - regression, - sma, -) -from prompt_toolkit.completion import NestedCompleter - - -def print_prediction(s_ticker, s_start, s_interval): - """ Print help """ - - s_intraday = (f"Intraday {s_interval}", "Daily")[s_interval == "1440min"] - - if s_start: - print(f"\n{s_intraday} Stock: {s_ticker} (from {s_start.strftime('%Y-%m-%d')})") - else: - print(f"\n{s_intraday} Stock: {s_ticker}") - - print("\nPrediction Techniques:") - print(" help show this prediction techniques menu again") - print(" q quit this menu, and shows back to main menu") - print(" quit quit to abandon program") - print("") - print(" sma simple moving average") - print(" ets exponential smoothing (e.g. Holt-Winters)") - print(" knn k-Nearest Neighbors") - print(" linear linear regression (polynomial 1)") - print(" quadratic quadratic regression (polynomial 2)") - print(" cubic cubic regression (polynomial 3)") - print(" regression regression (other polynomial)") - print(" arima autoregressive integrated moving average") - print(" prophet Facebook's prophet prediction") - print(" mlp MultiLayer Perceptron") - print(" rnn Recurrent Neural Network") - print(" lstm Long-Short Term Memory") - print("") - - -def pred_menu(df_stock, s_ticker, s_start, s_interval): - - # Add list of arguments that the prediction techniques parser accepts - pred_parser = argparse.ArgumentParser(prog="pred", add_help=False) - choices = [ - "help", - "q", - "quit", - "sma", - "ets", - "knn", - "linear", - "quadratic", - "cubic", - "regression", - "arima", - "prophet", - "mlp", - "rnn", - "lstm", - ] - pred_parser.add_argument("cmd", choices=choices) - completer = NestedCompleter.from_nested_dict({c: None for c in choices}) - - print_prediction(s_ticker, s_start, s_interval) - - # Loop forever and ever - while True: - # Get input command from user - if session and gtff.USE_PROMPT_TOOLKIT: - as_input = session.prompt( - f"{get_flair()} (pred)> ", - completer=completer, - ) - else: - as_input = input(f"{get_flair()} (pred)> ") - - # Images are non blocking - allows to close them if we type other command - plt.close("all") - - # Parse prediction techniques command of the list of possible commands - try: - (ns_known_args, l_args) = pred_parser.parse_known_args(as_input.split()) - - except SystemExit: - print("The command selected doesn't exist\n") - continue - - if ns_known_args.cmd == "help": - print_prediction(s_ticker, s_start, s_interval) - - elif ns_known_args.cmd == "q": - # Just leave the FA menu - return False - - elif ns_known_args.cmd == "quit": - # Abandon the program - return True - - elif ns_known_args.cmd == "sma": - sma.simple_moving_average(l_args, s_ticker, df_stock) - - elif ns_known_args.cmd == "ets": - ets.exponential_smoothing(l_args, s_ticker, df_stock) - - elif ns_known_args.cmd == "knn": - knn.k_nearest_neighbors(l_args, s_ticker, df_stock) - - elif ns_known_args.cmd == "linear": - regression.regression(l_args, s_ticker, df_stock, regression.LINEAR) - - elif ns_known_args.cmd == "quadratic": - regression.regression(l_args, s_ticker, df_stock, regression.QUADRATIC) - - elif ns_known_args.cmd == "cubic": - regression.regression(l_args, s_ticker, df_stock, regression.CUBIC) - - elif ns_known_args.cmd == "regression": - regression.regression(l_args, s_ticker, df_stock, regression.USER_INPUT) - - elif ns_known_args.cmd == "arima": - arima.arima(l_args, s_ticker, df_stock) - - elif ns_known_args.cmd == "prophet": - fbprophet.fbprophet(l_args, s_ticker, df_stock) - - elif ns_known_args.cmd == "mlp": - neural_networks.mlp(l_args, s_ticker, df_stock) - - elif ns_known_args.cmd == "rnn": - neural_networks.rnn(l_args, s_ticker, df_stock) - - elif ns_known_args.cmd == "lstm": - neural_networks.lstm(l_args, s_ticker, df_stock) - - else: - print("Command not recognized!") diff --git a/gamestonk_terminal/screener/README.md b/gamestonk_terminal/screener/README.md index b4f46eb644d..6271a7684db 100644 --- a/gamestonk_terminal/screener/README.md +++ b/gamestonk_terminal/screener/README.md @@ -23,7 +23,8 @@ This menu aims to filter stocks based on pre-specified preset filters, and the u * contains [Oversold (-s) signal example](#signal-oversold) * [signals](#signals) * view filter signals (e.g. -s top_gainers) [Finviz] - +* [po](#port_opt) + * go to the portfolio optimization menu ## view @@ -180,3 +181,13 @@ Prints list of available signals. [Source: Finviz] Captura de ecrã 2021-04-05, às 20 25 13 +## po +Goes to the portfolio menu with list of passed stocks. In order to pass the stocks, just type the tickers of interest, no commas. + +``` +usage: po ticker1 ticker2 ... +``` + +```` +example: po aapl msft tsla gme +```` \ No newline at end of file diff --git a/gamestonk_terminal/screener/screener_controller.py b/gamestonk_terminal/screener/screener_controller.py index cc34b3e80ab..0fe364750b0 100644 --- a/gamestonk_terminal/screener/screener_controller.py +++ b/gamestonk_terminal/screener/screener_controller.py @@ -14,6 +14,7 @@ from gamestonk_terminal.helper_funcs import ( ) from gamestonk_terminal.screener import finviz_view from gamestonk_terminal.screener import yahoo_finance_view +from gamestonk_terminal.portfolio_optimization import po_controller class ScreenerController: @@ -34,6 +35,7 @@ class ScreenerController: "performance", "technical", "signals", + "po", ] def __init__(self): @@ -69,6 +71,8 @@ class ScreenerController: print("") print(" signals view filter signals (e.g. -s top_gainers)") print("") + print(" > po go to the portfolio optimization menu") + print("") @staticmethod def view_available_presets(other_args: List[str]): @@ -217,6 +221,10 @@ class ScreenerController: """Process signals command""" finviz_view.view_signals(other_args) + def call_po(self, other_args: List[str]): + """Call the portfolio optimization menu""" + return po_controller.menu(other_args) + def menu(): """Screener Menu""" diff --git a/notebooks/templates/due_diligence.ipynb b/notebooks/templates/due_diligence.ipynb index cbbdc235398..09f4d95510f 100644 --- a/notebooks/templates/due_diligence.ipynb +++ b/notebooks/templates/due_diligence.ipynb @@ -68,7 +68,7 @@ "outputs": [], "source": [ "from gamestonk_terminal.technical_analysis import trendline_api as trend\n", - "from gamestonk_terminal.due_diligence import finviz_api as finviz" + "from gamestonk_terminal.due_diligence import finviz_view as finviz" ] }, { diff --git a/poetry.lock b/poetry.lock index b20746d369b..5cbefaf1f5d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -379,6 +379,21 @@ category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +[[package]] +name = "cvxpy" +version = "1.1.12" +description = "A domain-specific language for modeling convex optimization problems in Python." +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +ecos = ">=2" +numpy = ">=1.15" +osqp = ">=0.4.1" +scipy = ">=1.1.0" +scs = ">=1.1.6" + [[package]] name = "cycler" version = "0.10.0" @@ -456,6 +471,18 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "ecos" +version = "2.0.7.post1" +description = "This is the Python package for ECOS: Embedded Cone Solver. See Github page for more information." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +numpy = ">=1.6" +scipy = ">=0.9" + [[package]] name = "entrypoints" version = "0.3" @@ -1491,6 +1518,19 @@ numpy = ">=1.7" docs = ["sphinx (==1.2.3)", "sphinxcontrib-napoleon", "sphinx-rtd-theme", "numpydoc"] tests = ["pytest", "pytest-cov", "pytest-pep8"] +[[package]] +name = "osqp" +version = "0.6.2.post0" +description = "OSQP: The Operator Splitting QP Solver" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +numpy = ">=1.7" +qdldl = "*" +scipy = ">=0.13.2" + [[package]] name = "overrides" version = "3.1.0" @@ -1930,6 +1970,23 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "pyportfolioopt" +version = "1.4.1" +description = "Financial portfolio optimization in python" +category = "main" +optional = false +python-versions = ">=3.6.1,<4.0.0" + +[package.dependencies] +cvxpy = ">=1.1.10,<2.0.0" +numpy = ">=1.12,<2.0" +pandas = ">=0.19" +scipy = ">=1.3,<2.0" + +[package.extras] +optionals = ["cvxopt (>=1.2,!=1.2.5.post1,<2.0)", "scikit-learn (>=0.24.1,<0.25.0)", "matplotlib (>=3.2.0,<4.0.0)"] + [[package]] name = "pyrsistent" version = "0.14.11" @@ -2073,6 +2130,18 @@ python-versions = ">=3.6" cffi = {version = "*", markers = "implementation_name == \"pypy\""} py = {version = "*", markers = "implementation_name == \"pypy\""} +[[package]] +name = "qdldl" +version = "0.1.5.post0" +description = "QDLDL, a free LDL factorization routine." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +numpy = ">=1.7" +scipy = ">=0.13.2" + [[package]] name = "qtconsole" version = "5.0.3" @@ -2265,6 +2334,18 @@ Cython = {version = "*", markers = "sys_platform == \"darwin\""} dataclasses = {version = "*", markers = "python_version < \"3.7\""} pyobjc-framework-Cocoa = {version = "*", markers = "sys_platform == \"darwin\""} +[[package]] +name = "scs" +version = "2.1.2" +description = "scs: splitting conic solver" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +numpy = ">=1.7" +scipy = ">=0.13.2" + [[package]] name = "seaborn" version = "0.11.1" @@ -2837,7 +2918,7 @@ prediction = ["fbprophet", "tensorflow", "pmdarima", "transformers", "flair", "t [metadata] lock-version = "1.1" python-versions = "^3.6.8" -content-hash = "144771641ef753b1e937c0db16496c409905f6f692ef81a03d9ba716d9207bb0" +content-hash = "5b3abe79834fc75092d1d9e8e3a809252f56eddc170fb61a93c36ffef9328b73" [metadata.files] absl-py = [ @@ -3063,6 +3144,20 @@ cssselect = [ {file = "cssselect-1.1.0-py2.py3-none-any.whl", hash = "sha256:f612ee47b749c877ebae5bb77035d8f4202c6ad0f0fc1271b3c18ad6c4468ecf"}, {file = "cssselect-1.1.0.tar.gz", hash = "sha256:f95f8dedd925fd8f54edb3d2dfb44c190d9d18512377d3c1e2388d16126879bc"}, ] +cvxpy = [ + {file = "cvxpy-1.1.12-cp36-cp36m-win_amd64.whl", hash = "sha256:86f8e4aa1a79482431b89aa526249d9fda04c74e04a3ec224d1175d281643d71"}, + {file = "cvxpy-1.1.12-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:90b09a7361b9c614166fa673e1fe9321317df6939135330e1621b9a0bf10e3a8"}, + {file = "cvxpy-1.1.12-cp37-cp37m-win_amd64.whl", hash = "sha256:084e5292f2a13133a1cd28d68f5af1df33a5119b0f1aec7cecf834eea3e2d11d"}, + {file = "cvxpy-1.1.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:575f6e1c70274008961edff97a7494701faed20b40fbd49327010c0e7d186256"}, + {file = "cvxpy-1.1.12-cp38-cp38-win_amd64.whl", hash = "sha256:76d04444cb50ad2840bbc72a625ac246b5eeeeb580ee20c71bec057822667738"}, + {file = "cvxpy-1.1.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2de5e0e75316839bc71b5df2c2db0131cee7ef06dac566c6c44dc852c091502b"}, + {file = "cvxpy-1.1.12-cp39-cp39-win_amd64.whl", hash = "sha256:9fa4d033ab31855aea090e4b358017b36880390a7d0e6e97ecc5226c77eab489"}, + {file = "cvxpy-1.1.12-py3.6-win-amd64.egg", hash = "sha256:d2c28e0eea1b6dc70262c9ed8b63c5c1747d85c28b3ec24cbaab07583b3ad729"}, + {file = "cvxpy-1.1.12-py3.7-win-amd64.egg", hash = "sha256:44d0e3d60693831ac70bdc9b2e23faf84401347bfcdef4eb5da5ae22ac57f5ff"}, + {file = "cvxpy-1.1.12-py3.8-win-amd64.egg", hash = "sha256:c86f8de9a63ccbd45844a4482df8099824688b5329060f089aad1c3beb939190"}, + {file = "cvxpy-1.1.12-py3.9-win-amd64.egg", hash = "sha256:08c720706abd94c25903a4db5dd2171232e72d0969b874a5de9a8181f96357d5"}, + {file = "cvxpy-1.1.12.tar.gz", hash = "sha256:b499ebfae4fc645e9523621573ffcb1c5b6854a1b5c0ce1d66a8a9a458605807"}, +] cycler = [ {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"}, {file = "cycler-0.10.0.tar.gz", hash = "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"}, @@ -3126,6 +3221,30 @@ detecta = [ {file = "detecta-0.0.5-py3-none-any.whl", hash = "sha256:dbaf1938e5d785386c904034e2553824e2aa31793e967984ca65983aca06d23e"}, {file = "detecta-0.0.5.tar.gz", hash = "sha256:d2ea7d13dfbbc994d6ce385a7f8dc0a85fe675a8a8e712a64ec56e54c40603ed"}, ] +ecos = [ + {file = "ecos-2.0.7.post1-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:dd9f01e28fe58894fb394931804884122606fb4e2a59d4514b803e9cd11b7d2b"}, + {file = "ecos-2.0.7.post1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:db7433051f6072d4821ebc582e9ff853d7d631ed98770550d248eae70b29dd26"}, + {file = "ecos-2.0.7.post1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:96ddc1c4e440820bb343c44785da480a64a9c468ec467b997e40f0c7d3236226"}, + {file = "ecos-2.0.7.post1-cp27-cp27m-win32.whl", hash = "sha256:574fa26661d192e48551a30e217296875020fdb7eebf9a072a14d68a0b9de03a"}, + {file = "ecos-2.0.7.post1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7c871b7a49a5855e7df0d60a06d0e2148a4b183d612f7db5e155988629c3ec21"}, + {file = "ecos-2.0.7.post1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:845455f99cd579ee0cdfbcea675b4e4f7674b563e6a54a225977fc0819cda7e0"}, + {file = "ecos-2.0.7.post1-cp34-cp34m-manylinux1_i686.whl", hash = "sha256:e002df0f4b6777be68c73756e60f3cf76cb5f2f7d36c4f1a482c5538aac1a287"}, + {file = "ecos-2.0.7.post1-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:feda86ddd191b1ae34d5ea615743894d1baa800f2dbb552cb7c7095a87037831"}, + {file = "ecos-2.0.7.post1-cp34-cp34m-win32.whl", hash = "sha256:6b0829b76ba49f6ebf8b9512673a0b702b756704927cbb106043e1b2273dadea"}, + {file = "ecos-2.0.7.post1-cp34-cp34m-win_amd64.whl", hash = "sha256:54f7c480029fbfa738ddb8e538388868b2a17c9189a426d924ca0c36e4cbbbd5"}, + {file = "ecos-2.0.7.post1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:1f811e7244a58a7037474b3dead85c7ffd524cf631c90584cc544e438f114cf9"}, + {file = "ecos-2.0.7.post1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:72657189f71dbde01d1df841d2139e04da17071c8d3270919c8501f239d8c8e4"}, + {file = "ecos-2.0.7.post1-cp35-cp35m-win32.whl", hash = "sha256:30e7e9c5ad8a012ba1f69aa6827beb99f208323f678a82fcccc30e4ab8090aad"}, + {file = "ecos-2.0.7.post1-cp35-cp35m-win_amd64.whl", hash = "sha256:94dd0f82a18550232e4924e6c42730c46d7cdc03c4e2dc889e98ec97c0f24061"}, + {file = "ecos-2.0.7.post1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:361758a3568eb5a3a9b37c00a22b36f9548bbd0cd21f1da904a3627285bb4274"}, + {file = "ecos-2.0.7.post1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:831acb6bac205025ffe87002d8f425d2764a70db3d9d053a1f7e0e50bc2a18b9"}, + {file = "ecos-2.0.7.post1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:912b17f72476aff33b32e92decd7c02ddb929db28227563b3750948783cff6f4"}, + {file = "ecos-2.0.7.post1-cp36-cp36m-win32.whl", hash = "sha256:4fdfee011853cd07d494ef58a9299b1f0ccf0268c375109cb2b60727f746ea74"}, + {file = "ecos-2.0.7.post1-cp36-cp36m-win_amd64.whl", hash = "sha256:fb64fb29aef26474f807df4f0c198a6d192291edc9faa3bb05e3bc9b1e2b960a"}, + {file = "ecos-2.0.7.post1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4b3068ef023c4f39f6c0d6fd73d27d8a8008f8b14e1f4ddd8d0f4876e841b986"}, + {file = "ecos-2.0.7.post1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:4f775caf828597d094cddae54f7ed12b4a5aa760f535a92ccabec47741e4d61c"}, + {file = "ecos-2.0.7.post1.tar.gz", hash = "sha256:83e90f42b3f32e2a93f255c3cfad2da78dbd859119e93844c45d2fca20bdc758"}, +] entrypoints = [ {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, @@ -3810,6 +3929,29 @@ opt-einsum = [ {file = "opt_einsum-3.3.0-py3-none-any.whl", hash = "sha256:2455e59e3947d3c275477df7f5205b30635e266fe6dc300e3d9f9646bfcea147"}, {file = "opt_einsum-3.3.0.tar.gz", hash = "sha256:59f6475f77bbc37dcf7cd748519c0ec60722e91e63ca114e68821c0c54a46549"}, ] +osqp = [ + {file = "osqp-0.6.2.post0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:21047e9ceb4c2224ba15dd95f5125e2dcabd178aa93d8efa407e67216ea4fe62"}, + {file = "osqp-0.6.2.post0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:598a7bb1534a0ab69bcaf22d6e6c4ec042a41184f007c01b7fa863b528c6b61d"}, + {file = "osqp-0.6.2.post0-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:6b745ba55add8be63eaee059858b7c0cf59925074fcc8355c17c3863fad01a95"}, + {file = "osqp-0.6.2.post0-cp35-cp35m-win_amd64.whl", hash = "sha256:1104dd56d6bfe5ac5e5e076e1cf9e2be76932cadae4e0cfbdf495f3f43e4f8fa"}, + {file = "osqp-0.6.2.post0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6f89feaf7c30dfb5031b370e8c45aed23a60d6471606bad9b7d23506ddf807e6"}, + {file = "osqp-0.6.2.post0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:198b66cbe9326ef181f7598856aae20dcc5b7824b119cc119b412206e858bc28"}, + {file = "osqp-0.6.2.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:9aaecd3ed00a99d063e5bd45ebce965d1ebc8f4754d6c05ab5d31c3577a68513"}, + {file = "osqp-0.6.2.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:2d150d880dc5bee015aadac6cff32ab0c2721cd4bb8a3f5b1f0cec819fe70b39"}, + {file = "osqp-0.6.2.post0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:20cd6a352694cc182bb79a8e19e4e03b7970ecb4b95d6abf86e3c578a45783a9"}, + {file = "osqp-0.6.2.post0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:96e42524dd156f62ac8e35d71831588acb2919b5ba27e0e695c757539708dfa5"}, + {file = "osqp-0.6.2.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:d6dd2b70ffcee6089758518620087a5909a10621a59493be8ce0ef2a74502a27"}, + {file = "osqp-0.6.2.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:7c9f7e661fe2da984eab6533a56b5366f10a7360a28b8dd18c222fcf6655e247"}, + {file = "osqp-0.6.2.post0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b102c26b2017b039032797a388548dcfe11dbd3d06b3294721fa65976fe1065e"}, + {file = "osqp-0.6.2.post0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a835746179c81944e51a6a2e0efb0aca66402be351af912bfd682527f15a35a7"}, + {file = "osqp-0.6.2.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:57f5026b91bb8f39059576c5ee50c0a1c2705d775a776e2e24be2aa47e63d8b0"}, + {file = "osqp-0.6.2.post0-cp38-cp38-win_amd64.whl", hash = "sha256:fb0d77e634362fe59ab2bedba3dde40d5357922e9719bcb4d16a99766cefd5a2"}, + {file = "osqp-0.6.2.post0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e21463d97bcab58350a41a975e9e5e4a685087b6357899a58813d2238795315"}, + {file = "osqp-0.6.2.post0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:02fdbabfffd72a8118328d117a83998800ca015182053fced9d5e8be6ffd702a"}, + {file = "osqp-0.6.2.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:b238b9049a29f6955f08567b285b0734dd578b529a3780d617854ffd91e5e4ed"}, + {file = "osqp-0.6.2.post0-cp39-cp39-win_amd64.whl", hash = "sha256:2c6cb14fd1365004d0e7a01f6101e16414a0348db736ee45aac8865fac165e57"}, + {file = "osqp-0.6.2.post0.tar.gz", hash = "sha256:5f0695f26a3bef0fae91254bc283fab790dcca0064bfe0f425167f9c9e8b4cbc"}, +] overrides = [ {file = "overrides-3.1.0.tar.gz", hash = "sha256:30f761124579e59884b018758c4d7794914ef02a6c038621123fec49ea7599c6"}, ] @@ -4069,6 +4211,10 @@ pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] +pyportfolioopt = [ + {file = "PyPortfolioOpt-1.4.1-py3-none-any.whl", hash = "sha256:73f0b4ac2a5d879879a7a91af038b4d7ff82e9998fb2cad7c249302294e8c09e"}, + {file = "PyPortfolioOpt-1.4.1.tar.gz", hash = "sha256:e6c8e983db92cd13caf41d87fb4dcd945d37053f28b75dd19b5be699b6b1d19e"}, +] pyrsistent = [ {file = "pyrsistent-0.14.11.tar.gz", hash = "sha256:3ca82748918eb65e2d89f222b702277099aca77e34843c5eb9d52451173970e2"}, ] @@ -4212,6 +4358,24 @@ pyzmq = [ {file = "pyzmq-22.0.3-pp37-pypy37_pp73-win32.whl", hash = "sha256:279cc9b51db48bec2db146f38e336049ac5a59e5f12fb3a8ad864e238c1c62e3"}, {file = "pyzmq-22.0.3.tar.gz", hash = "sha256:f7f63ce127980d40f3e6a5fdb87abf17ce1a7c2bd8bf2c7560e1bbce8ab1f92d"}, ] +qdldl = [ + {file = "qdldl-0.1.5.post0-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:787d59b4301608e96bdf32ab3a572d9f41b3ea08581774826720986e18da261e"}, + {file = "qdldl-0.1.5.post0-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:223dd49f4aa625c727c3053e9f62814fab2f929e0575b34dd1f38e66b536a849"}, + {file = "qdldl-0.1.5.post0-cp35-cp35m-win_amd64.whl", hash = "sha256:cd0090d5512a8e386534a755f9ebd8be82087d401746cebbfb9f9faa21073c39"}, + {file = "qdldl-0.1.5.post0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:3e5e2ee6b45dd655120ca8d6645331c800c494ce765bc0674714ba86e1ebec7d"}, + {file = "qdldl-0.1.5.post0-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:a2ab0d4618d5516101fc0ce99c873df61a085c3d7cb4d8347d5eca818795e48f"}, + {file = "qdldl-0.1.5.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:ac7a2891628554de6260132fa090c5b00cf32ae083ece4991a43b887ec6f2114"}, + {file = "qdldl-0.1.5.post0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:817a226b2450e56c50f61a92b727c69f50a4d043876e5f1bcdff096c5a82bc37"}, + {file = "qdldl-0.1.5.post0-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:c10f91dda6a422a6d879668fd293132c8939431fffe64abd9700dce170a3aec4"}, + {file = "qdldl-0.1.5.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:e70c57ea65d0bb09708bc9ecafd4fb4b797e64af4da475404e6bcf5a2cc92596"}, + {file = "qdldl-0.1.5.post0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:64f531ef2a12cc77be85045cdffdb2fe339244c73fc07aebe49977541bfc7e79"}, + {file = "qdldl-0.1.5.post0-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:cff34b89258a3c9b1b398f1c8eeb480a9c4ae2ba14c4ffc8c78cdb123dab11c6"}, + {file = "qdldl-0.1.5.post0-cp38-cp38-win_amd64.whl", hash = "sha256:3a7ec584aaddff7036d22013c911132572f3d59c65ec6bf7e32beb01765accc4"}, + {file = "qdldl-0.1.5.post0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a7c086ae21a2b2e43b31ac6a5024c79dadf16e7c3152eda9e3263cefe1675e26"}, + {file = "qdldl-0.1.5.post0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:2c09f4b1a1c6f3a0579af004443417e084491e7c844ff9fb73170bb5d43f70b5"}, + {file = "qdldl-0.1.5.post0-cp39-cp39-win_amd64.whl", hash = "sha256:640cab781f87c2f1c04ada65615b3d1d136d319c4595176f37e2e3c99149108f"}, + {file = "qdldl-0.1.5.post0.tar.gz", hash = "sha256:c392c7427651d8b226423c7aba4a0f2338a1f38a4bbdabac6bc4afd8bc934f06"}, +] qtconsole = [ {file = "qtconsole-5.0.3-py3-none-any.whl", hash = "sha256:4a38053993ca2da058f76f8d75b3d8906efbf9183de516f92f222ac8e37d9614"}, {file = "qtconsole-5.0.3.tar.gz", hash = "sha256:c091a35607d2a2432e004c4a112d241ce908086570cf68594176dd52ccaa212d"}, @@ -4359,7 +4523,6 @@ scikit-learn = [ {file = "scikit_learn-0.24.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c658432d8a20e95398f6bb95ff9731ce9dfa343fdf21eea7ec6a7edfacd4b4d9"}, {file = "scikit_learn-0.24.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9dfa564ef27e8e674aa1cc74378416d580ac4ede1136c13dd555a87996e13422"}, {file = "scikit_learn-0.24.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:9c6097b6a9b2bafc5e0f31f659e6ab5e131383209c30c9e978c5b8abdac5ed2a"}, - {file = "scikit_learn-0.24.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5580eba7345a4d3b097be2f067cc71a306c44bab19e8717a30361f279c929bea"}, {file = "scikit_learn-0.24.1-cp36-cp36m-win32.whl", hash = "sha256:7b04691eb2f41d2c68dbda8d1bd3cb4ef421bdc43aaa56aeb6c762224552dfb6"}, {file = "scikit_learn-0.24.1-cp36-cp36m-win_amd64.whl", hash = "sha256:1adf483e91007a87171d7ce58c34b058eb5dab01b5fee6052f15841778a8ecd8"}, {file = "scikit_learn-0.24.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:ddb52d088889f5596bc4d1de981f2eca106b58243b6679e4782f3ba5096fd645"}, @@ -4367,7 +4530,6 @@ scikit-learn = [ {file = "scikit_learn-0.24.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0567a2d29ad08af98653300c623bd8477b448fe66ced7198bef4ed195925f082"}, {file = "scikit_learn-0.24.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:99349d77f54e11f962d608d94dfda08f0c9e5720d97132233ebdf35be2858b2d"}, {file = "scikit_learn-0.24.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:83b21ff053b1ff1c018a2d24db6dd3ea339b1acfbaa4d9c881731f43748d8b3b"}, - {file = "scikit_learn-0.24.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:abe835a851610f87201819cb315f8d554e1a3e8128912783a31e87264ba5ffb7"}, {file = "scikit_learn-0.24.1-cp37-cp37m-win32.whl", hash = "sha256:c3deb3b19dd9806acf00cf0d400e84562c227723013c33abefbbc3cf906596e9"}, {file = "scikit_learn-0.24.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d54dbaadeb1425b7d6a66bf44bee2bb2b899fe3e8850b8e94cfb9c904dcb46d0"}, {file = "scikit_learn-0.24.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:3c4f07f47c04e81b134424d53c3f5e16dfd7f494e44fd7584ba9ce9de2c5e6c1"}, @@ -4375,7 +4537,6 @@ scikit-learn = [ {file = "scikit_learn-0.24.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4ddd2b6f7449a5d539ff754fa92d75da22de261fd8fdcfb3596799fadf255101"}, {file = "scikit_learn-0.24.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:826b92bf45b8ad80444814e5f4ac032156dd481e48d7da33d611f8fe96d5f08b"}, {file = "scikit_learn-0.24.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:259ec35201e82e2db1ae2496f229e63f46d7f1695ae68eef9350b00dc74ba52f"}, - {file = "scikit_learn-0.24.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:9599a3f3bf33f73fed0fe06d1dfa4e6081365a58c1c807acb07271be0dce9733"}, {file = "scikit_learn-0.24.1-cp38-cp38-win32.whl", hash = "sha256:8772b99d683be8f67fcc04789032f1b949022a0e6880ee7b75a7ec97dbbb5d0b"}, {file = "scikit_learn-0.24.1-cp38-cp38-win_amd64.whl", hash = "sha256:ed9d65594948678827f4ff0e7ae23344e2f2b4cabbca057ccaed3118fdc392ca"}, {file = "scikit_learn-0.24.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:8aa1b3ac46b80eaa552b637eeadbbce3be5931e4b5002b964698e33a1b589e1e"}, @@ -4383,7 +4544,6 @@ scikit-learn = [ {file = "scikit_learn-0.24.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:087dfede39efb06ab30618f9ab55a0397f29c38d63cd0ab88d12b500b7d65fd7"}, {file = "scikit_learn-0.24.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:895dbf2030aa7337649e36a83a007df3c9811396b4e2fa672a851160f36ce90c"}, {file = "scikit_learn-0.24.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:9a24d1ccec2a34d4cd3f2a1f86409f3f5954cc23d4d2270ba0d03cf018aa4780"}, - {file = "scikit_learn-0.24.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:54be0a60a5a35005ad69c75902e0f5c9f699db4547ead427e97ef881c3242e6f"}, {file = "scikit_learn-0.24.1-cp39-cp39-win32.whl", hash = "sha256:fab31f48282ebf54dd69f6663cd2d9800096bad1bb67bbc9c9ac84eb77b41972"}, {file = "scikit_learn-0.24.1-cp39-cp39-win_amd64.whl", hash = "sha256:4562dcf4793e61c5d0f89836d07bc37521c3a1889da8f651e2c326463c4bd697"}, ] @@ -4417,6 +4577,9 @@ scipy = [ screeninfo = [ {file = "screeninfo-0.6.7.tar.gz", hash = "sha256:1c4bac1ca329da3f68cbc4d2fbc92256aa9bb8ff8583ee3e14f91f0a7baa69cb"}, ] +scs = [ + {file = "scs-2.1.2.tar.gz", hash = "sha256:667ed6019bb4e2f925bd9291161d2c61cc0077443094437b703ea905333fd585"}, +] seaborn = [ {file = "seaborn-0.11.1-py3-none-any.whl", hash = "sha256:4e1cce9489449a1c6ff3c567f2113cdb41122f727e27a984950d004a88ef3c5c"}, {file = "seaborn-0.11.1.tar.gz", hash = "sha256:44e78eaed937c5a87fc7a892c329a7cc091060b67ebd1d0d306b446a74ba01ad"}, diff --git a/pyproject.toml b/pyproject.toml index 5895d2c9dd0..1082086197e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,6 +54,7 @@ detecta = "^0.0.5" tradingview-ta = "^3.2.3" finvizfinance = "^0.9.3" Pillow = "^8.2.0" +pyportfolioopt = "^1.4.1" [tool.poetry.dev-dependencies] pytest = "^6.2.2" diff --git a/requirements-full.txt b/requirements-full.txt index 2bdd5e071dc..1c51e43294a 100644 --- a/requirements-full.txt +++ b/requirements-full.txt @@ -133,6 +133,7 @@ pycparser==2.20; python_version >= "3.5" and python_full_version < "3.0.0" or py pygments==2.8.1; python_version >= "3.6" pymeeus==0.5.9; python_version >= "3.5" pyparsing==2.4.7; python_full_version >= "3.6.0" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") +pyportfolioopt==1.4.1 pyrsistent==0.17.3; python_version >= "3.6" pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" pystan==2.19.1.1 diff --git a/requirements-old.txt b/requirements-old.txt index f06595a2ee6..3a4b6bb05c4 100644 --- a/requirements-old.txt +++ b/requirements-old.txt @@ -90,6 +90,7 @@ pyasn1==0.4.8; python_version >= "3.5" and python_full_version < "3.0.0" and pyt pycoingecko==1.4.0 pymeeus==0.4.3; python_version >= "3.5" pyparsing==2.4.7; python_full_version >= "3.6.0" and python_version >= "3.6" +pyportfolioopt==1.4.1 pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" pystan==2.18 python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.5" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "2.7" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "2.7" and python_version < "4" and python_full_version >= "3.3.0") diff --git a/requirements-win.txt b/requirements-win.txt index a5198652fef..560a2508261 100644 --- a/requirements-win.txt +++ b/requirements-win.txt @@ -92,6 +92,7 @@ pyasn1==0.4.8; python_version >= "3.5" and python_full_version < "3.0.0" and pyt pycoingecko==1.4.0 pymeeus==0.4.3; python_version >= "3.5" pyparsing==2.4.7; python_full_version >= "3.6.0" and python_version >= "3.6" +pyportfolioopt==1.4.1 pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" pystan==2.19.1.1 python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.5" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "2.7" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "2.7" and python_version < "4" and python_full_version >= "3.3.0") diff --git a/requirements.txt b/requirements.txt index b89711f769c..4f4ba01310a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -92,6 +92,7 @@ pycparser==2.20; python_version >= "3.5" and python_full_version < "3.0.0" or py pygments==2.8.1; python_version >= "3.6" pymeeus==0.5.9; python_version >= "3.5" pyparsing==2.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +pyportfolioopt==1.4.1 pyrsistent==0.17.3; python_version >= "3.6" pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") diff --git a/terminal.py b/terminal.py index 56783314efd..5223d568dae 100644 --- a/terminal.py +++ b/terminal.py @@ -15,7 +15,7 @@ from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal import thought_of_the_day as thought from gamestonk_terminal import res_menu as rm from gamestonk_terminal.discovery import disc_controller -from gamestonk_terminal.due_diligence import dd_menu as ddm +from gamestonk_terminal.due_diligence import dd_controller from gamestonk_terminal.fundamental_analysis import fa_menu as fam from gamestonk_terminal.helper_funcs import b_is_stock_market_open, get_flair from gamestonk_terminal.main_helper import clear, export, load, print_help, view, candle @@ -32,7 +32,7 @@ from gamestonk_terminal.portfolio import port_controller from gamestonk_terminal.cryptocurrency import crypto_controller from gamestonk_terminal.screener import screener_controller from gamestonk_terminal.oanda import oanda_controller - +from gamestonk_terminal.portfolio_optimization import po_controller # import warnings # warnings.simplefilter("always") @@ -91,7 +91,9 @@ def main(): "crypto", "ra", "fx", + "po", ] + menu_parser.add_argument("opt", choices=choices) completer = NestedCompleter.from_nested_dict({c: None for c in choices}) @@ -180,7 +182,8 @@ def main(): else: print( - "No ticker selected. Use 'load ticker' to load the ticker you want to look at." + "No ticker selected. Use 'load ticker' to load the ticker you want to look at.", + "\n", ) main_cmd = True @@ -213,7 +216,7 @@ def main(): b_quit = ta_controller.menu(df_stock, s_ticker, s_start, s_interval) elif ns_known_args.opt == "dd": - b_quit = ddm.dd_menu(df_stock, s_ticker, s_start, s_interval) + b_quit = dd_controller.menu(df_stock, s_ticker, s_start, s_interval) elif ns_known_args.opt == "eda": if s_interval == "1440min": @@ -236,7 +239,9 @@ def main(): b_quit = eda_controller.menu(df_stock, s_ticker, s_start, s_interval) elif ns_known_args.opt == "op": - b_quit = op_controller.menu(s_ticker) + b_quit = op_controller.menu( + s_ticker, df_stock["5. adjusted close"].values[-1] + ) elif ns_known_args.opt == "fred": b_quit = fred_controller.menu() @@ -247,6 +252,9 @@ def main(): elif ns_known_args.opt == "crypto": b_quit = crypto_controller.menu() + elif ns_known_args.opt == "po": + b_quit = po_controller.menu([s_ticker]) + elif ns_known_args.opt == "pred": if not gtff.ENABLE_PREDICT: @@ -257,7 +265,7 @@ def main(): try: # pylint: disable=import-outside-toplevel - from gamestonk_terminal.prediction_techniques import pred_menu as pm + from gamestonk_terminal.prediction_techniques import pred_controller except ModuleNotFoundError as e: print("One of the optional packages seems to be missing") print("Optional packages need to be installed") @@ -270,7 +278,7 @@ def main(): continue if s_interval == "1440min": - b_quit = pm.pred_menu(df_stock, s_ticker, s_start, s_interval) + b_quit = pred_controller.menu(df_stock, s_ticker, s_start, s_interval) # If stock data is intradaily, we need to get data again as prediction # techniques work on daily adjusted data. By default we load data from # Alpha Vantage because the historical data loaded gives a larger @@ -287,7 +295,7 @@ def main(): # pylint: disable=no-member df_stock_pred = df_stock_pred.sort_index(ascending=True) df_stock_pred = df_stock_pred[s_start:] - b_quit = pm.pred_menu( + b_quit = pred_controller.menu( df_stock_pred, s_ticker, s_start, s_interval="1440min" ) except Exception as e: diff --git a/tests/test_dd_financial_modeling_prep_api.py b/tests/test_dd_financial_modeling_prep_api.py deleted file mode 100644 index 952fecdfb73..00000000000 --- a/tests/test_dd_financial_modeling_prep_api.py +++ /dev/null @@ -1,9 +0,0 @@ -""" fundamental_analysis/business_insider_api.py tests """ -import unittest - -from gamestonk_terminal.due_diligence.financial_modeling_prep_api import rating - - -class TestDdFinancialModelingPrepApi(unittest.TestCase): - def test_rating(self): - rating([], "PLTR") diff --git a/tests/test_dd_financial_modeling_prep_view.py b/tests/test_dd_financial_modeling_prep_view.py new file mode 100644 index 00000000000..0db0c299699 --- /dev/null +++ b/tests/test_dd_financial_modeling_prep_view.py @@ -0,0 +1,9 @@ +""" fundamental_analysis/business_insider_api.py tests """ +import unittest + +from gamestonk_terminal.due_diligence.financial_modeling_prep_view import rating + + +class TestDdFinancialModelingPrepView(unittest.TestCase): + def test_rating(self): + rating([], "PLTR") diff --git a/tests/test_dd_finviz_api.py b/tests/test_dd_finviz_view.py similarity index 70% rename from tests/test_dd_finviz_api.py rename to tests/test_dd_finviz_view.py index b0e1e680b2e..8c062510029 100644 --- a/tests/test_dd_finviz_api.py +++ b/tests/test_dd_finviz_view.py @@ -1,7 +1,7 @@ """ due_diligence/finviz_api.py tests """ import unittest -from gamestonk_terminal.due_diligence.finviz_api import analyst +from gamestonk_terminal.due_diligence.finviz_view import analyst class TestDdFinvizApi(unittest.TestCase): From ca10f1ba34c3ea71992424cfdb7078c9322b0a39 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Thu, 15 Apr 2021 22:32:26 -0600 Subject: [PATCH 10/54] ? --- terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terminal.py b/terminal.py index 5223d568dae..f4d837fe71d 100644 --- a/terminal.py +++ b/terminal.py @@ -31,8 +31,8 @@ from gamestonk_terminal.residuals_analysis import ra_controller from gamestonk_terminal.portfolio import port_controller from gamestonk_terminal.cryptocurrency import crypto_controller from gamestonk_terminal.screener import screener_controller -from gamestonk_terminal.oanda import oanda_controller from gamestonk_terminal.portfolio_optimization import po_controller +from gamestonk_terminal.oanda import oanda_controller # import warnings # warnings.simplefilter("always") From ee5edcc50c10ca216425f392d306e2edba373ffd Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Thu, 15 Apr 2021 22:34:03 -0600 Subject: [PATCH 11/54] t --- terminal.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/terminal.py b/terminal.py index f4d837fe71d..1a5dcd1691b 100644 --- a/terminal.py +++ b/terminal.py @@ -32,7 +32,6 @@ from gamestonk_terminal.portfolio import port_controller from gamestonk_terminal.cryptocurrency import crypto_controller from gamestonk_terminal.screener import screener_controller from gamestonk_terminal.portfolio_optimization import po_controller -from gamestonk_terminal.oanda import oanda_controller # import warnings # warnings.simplefilter("always") @@ -90,7 +89,6 @@ def main(): "pa", "crypto", "ra", - "fx", "po", ] From 33cba93bb6a47a6f5f820289549963f4aee55c01 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Thu, 15 Apr 2021 22:35:37 -0600 Subject: [PATCH 12/54] ? --- terminal.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/terminal.py b/terminal.py index 1a5dcd1691b..35638648e2d 100644 --- a/terminal.py +++ b/terminal.py @@ -32,6 +32,7 @@ from gamestonk_terminal.portfolio import port_controller from gamestonk_terminal.cryptocurrency import crypto_controller from gamestonk_terminal.screener import screener_controller from gamestonk_terminal.portfolio_optimization import po_controller +from gamestonk_terminal.oanda import oanda_controller # import warnings # warnings.simplefilter("always") @@ -90,6 +91,7 @@ def main(): "crypto", "ra", "po", + "fx", ] menu_parser.add_argument("opt", choices=choices) From f533563f1df9778fbe95961d94f20c12b0ccff8e Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Thu, 15 Apr 2021 22:40:30 -0600 Subject: [PATCH 13/54] ? --- terminal.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/terminal.py b/terminal.py index 56783314efd..c17d47b359c 100644 --- a/terminal.py +++ b/terminal.py @@ -32,6 +32,8 @@ from gamestonk_terminal.portfolio import port_controller from gamestonk_terminal.cryptocurrency import crypto_controller from gamestonk_terminal.screener import screener_controller from gamestonk_terminal.oanda import oanda_controller +from gamestonk_terminal.portfolio_optimization import po_controller + # import warnings @@ -90,6 +92,7 @@ def main(): "pa", "crypto", "ra", + "po", "fx", ] menu_parser.add_argument("opt", choices=choices) From 206cb691c5da0a597c18f554ae3a62e8453d30db Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 07:21:33 -0600 Subject: [PATCH 14/54] added load function --- gamestonk_terminal/oanda/oanda_controller.py | 39 ++- gamestonk_terminal/oanda/oanda_functions.py | 259 ++++++++++--------- 2 files changed, 164 insertions(+), 134 deletions(-) diff --git a/gamestonk_terminal/oanda/oanda_controller.py b/gamestonk_terminal/oanda/oanda_controller.py index 8794f2f4656..ed4235745b6 100644 --- a/gamestonk_terminal/oanda/oanda_controller.py +++ b/gamestonk_terminal/oanda/oanda_controller.py @@ -22,24 +22,25 @@ class OandaController: "list", "orderbook", "positionbook", - "limitorder", + "order", + "load", "cancel", "positions", "closetrade", "trades", "candles", "pending", + "calendar", ] - def __init__( - self, - ): + def __init__( self): """Construct Data""" self.oanda_parser = argparse.ArgumentParser(add_help=False, prog="fx") self.oanda_parser.add_argument( "cmd", choices=self.CHOICES, ) + self.instrument = None @staticmethod def print_help(): @@ -50,18 +51,20 @@ class OandaController: print("q \t quit this menu and goes back to main menu") print("quit \t quit to abandon program") print("") + print("load \t Load an instrument to use") print("price \t shows price for selected instrument") print("summary shows account summary") print("list \t list order history") print("trades \t list open trades") print("orderbook \t print orderbook") print("positionbook \t print positionbook") - print("limitorder \t place limit order -u # of units -p price") + print("order \t place limit order -u # of units -p price") print("cancel \t cancel a pending order by ID -i order ID") print("pending \t get information on pending orders") print("positions \t Get open positions") print("closetrade \t Close a trade by id") print("candles \t show candles") + print("calendar \t show calendar") def switch(self, an_input: str): """Process and dispatch input @@ -90,29 +93,32 @@ class OandaController: """Process Quit command - exit the program""" return True - def call_price(self, other_args: List[str]): + def call_price(self, _): """Process Price Command""" - oanda_functions.get_fx_price(account, other_args) + oanda_functions.get_fx_price(account, self.instrument) + + def call_load(self, other_args): + self.instrument = oanda_functions.load(other_args) def call_summary(self, _): """Process account summary command""" oanda_functions.get_account_summary(account) - def call_orderbook(self, other_args: List[str]): + def call_orderbook(self, _): """Process Oanda Order Book""" - oanda_functions.get_order_book(other_args) + oanda_functions.get_order_book(self.instrument) - def call_positionbook(self, other_args: List[str]): + def call_positionbook(self, _): """Process Oanda Position Book""" - oanda_functions.get_position_book(other_args) + oanda_functions.get_position_book(self.instrument) def call_list(self, other_args: List[str]): """Process list orders command""" oanda_functions.list_orders(account, other_args) - def call_limitorder(self, other_args: List[str]): + def call_order(self, other_args: List[str]): """Place limit order""" - oanda_functions.create_limit_order(account, other_args) + oanda_functions.create_order(account, self.instrument, other_args) def call_cancel(self, other_args: List[str]): """Cancel pending order by ID""" @@ -131,13 +137,18 @@ class OandaController: oanda_functions.close_trade(account, other_args) def call_candles(self, _): - oanda_functions.show_candles(account) + oanda_functions.show_candles(account, self.instrument) def call_trades(self, _): """List open trades""" oanda_functions.get_open_trades(account) + def call_calendar(self, _): + """Call calendar""" + oanda_functions.calendar(self.instrument) + + def menu(): """Oanda Menu""" oanda_controller = OandaController() diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py index fa3db84bca5..92bc832fefa 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -7,43 +7,30 @@ import oandapyV20.endpoints.orders as orders import oandapyV20.endpoints.instruments as instruments import oandapyV20.endpoints.positions as positions import oandapyV20.endpoints.trades as trades +import oandapyV20.endpoints.forexlabs as labs from oandapyV20.exceptions import V20Error from gamestonk_terminal import config_terminal as cfg from gamestonk_terminal.helper_funcs import parse_known_args_and_warn import pandas as pd import matplotlib.pyplot as plt import mplfinance as mpf -import matplotlib.dates as mdates + client = API(access_token=cfg.OANDA_TOKEN, environment="live") account = cfg.OANDA_ACCOUNT -def get_fx_price(accountID, other_args: List[str]): - parser = argparse.ArgumentParser( - add_help=False, - prog="getfxprice", - description="Get price for forex", - ) - parser.add_argument( - "-i", "--instrument", dest="instrument", action="store", required=True - ) - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - +def get_fx_price(accountID, instrument): try: - parameters = {"instruments": ns_parser.instrument} + 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(ns_parser.instrument + " Bid: " + bid) - print(ns_parser.instrument + " Ask: " + ask) + print(instrument + " Bid: " + bid) + print(instrument + " Ask: " + ask) except V20Error as e: print(e) - except NameError as e: - print(e) def get_account_summary(accountID): @@ -65,8 +52,8 @@ def get_account_summary(accountID): print(f"Unrealized P/L: {unrealized_pl}") print(f"Total P/L: {total_pl}") print(f"Open Trade Count: {open_trade_count}") - print(f"Margin Available: {margin_available}") - print(f"Margin Used: {margin_used}") + print(f"Margin Available: ${margin_available}") + print(f"Margin Used: ${margin_used}") print(f"Margin Closeout {margin_closeout}") print(f"Margin Closeout Percent: {margin_closeout_percent}") print(f"Margin Closeout Position Value: {margin_closeout_position_value}") @@ -89,8 +76,6 @@ def list_orders(accountID, other_args: List[str]): return parameters = { - # "state": ns_parser.state, - # "count": ns_parser.count, } if ns_parser.state is not None: parameters["state"] = ns_parser.state @@ -119,74 +104,47 @@ def list_orders(accountID, other_args: List[str]): break -def get_order_book(other_args: List[str]): +def get_order_book(instrument): + try: + request = instruments.InstrumentsOrderBook(instrument=instrument) + response = client.request(request) + for i in range(len(response["orderBook"]["buckets"])): + order_instrument = response["orderBook"]["instrument"] + price = response["orderBook"]["buckets"][i]["price"] + short_count_percent = response["orderBook"]["buckets"][i]["shortCountPercent"] + long_count_percent = response["orderBook"]["buckets"][i]["longCountPercent"] + print(f"Instrument: {order_instrument}") + print(f"Price: {price}") + print(f"Short count percent: {short_count_percent}") + print(f"Long count percent: {long_count_percent}") + print("-" * 30) + except V20Error as e: + print(e) + + +def get_position_book(instrument): + try: + request = instruments.InstrumentsPositionBook(instrument=instrument) + response = client.request(request) + for i in range(len(response["positionBook"]["buckets"])): + order_instrument = response["positionBook"]["instrument"] + price = response["positionBook"]["buckets"][i]["price"] + short_count_percent = response["positionBook"]["buckets"][i][ "shortCountPercent" ] + long_count_percent = response["positionBook"]["buckets"][i]["longCountPercent"] + print(f"Instrument: {order_instrument}") + print(f"Price: {price}") + print(f"Short count percent: {short_count_percent}") + print(f"Long count percent: {long_count_percent}") + print("-" * 30) + except V20Error as e: + print(e) + + +def create_order(accountID, instrument, other_args: List[str]): parser = argparse.ArgumentParser( add_help=False, - prog="get_order_book", - description="Get Order book", - ) - parser.add_argument( - "-i", "--instrument", dest="instrument", action="store", required=True - ) - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - parameters = {"bucketWidth": 0.1} - request = instruments.InstrumentsOrderBook( - instrument=ns_parser.instrument, params=parameters - ) - response = client.request(request) - - for i in range(400, 700, 1): - order_instrument = response["orderBook"]["instrument"] - price = response["orderBook"]["buckets"][i]["price"] - short_count_percent = response["orderBook"]["buckets"][i]["shortCountPercent"] - long_count_percent = response["orderBook"]["buckets"][i]["longCountPercent"] - print(f"Instrument: {order_instrument}") - print(f"Price: {price}") - print(f"Short count percent: {short_count_percent}") - print(f"Long count percent: {long_count_percent}") - print("-" * 30) - - -def get_position_book(other_args: List[str]): - parser = argparse.ArgumentParser( - add_help=False, - prog="get_position_book ", - description="Get Position Book", - ) - parser.add_argument( - "-i", "--instrument", dest="instrument", action="store", required=True - ) - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - parameters = {"bucketWidth": 0.1} - request = instruments.InstrumentsPositionBook( - instrument=ns_parser.instrument, params=parameters - ) - response = client.request(request) - - for i in range(200, 400, 1): - order_instrument = response["positionBook"]["instrument"] - price = response["positionBook"]["buckets"][i]["price"] - short_count_percent = response["positionBook"]["buckets"][i][ - "shortCountPercent" - ] - long_count_percent = response["positionBook"]["buckets"][i]["longCountPercent"] - print(f"Instrument: {order_instrument}") - print(f"Price: {price}") - print(f"Short count percent: {short_count_percent}") - print(f"Long count percent: {long_count_percent}") - print("-" * 30) - - -def create_limit_order(accountID, other_args: List[str]): - parser = argparse.ArgumentParser( - add_help=False, - prog="create_limit_order", - description="Create limit order", + prog="create_order", + description="Create order", ) parser.add_argument( "-u", @@ -205,12 +163,6 @@ def create_limit_order(accountID, other_args: List[str]): type=float, required=True, ) - parser.add_argument( - "-i", - "--instrument", - dest="instrument", - action="store", - required=True) ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: @@ -219,7 +171,7 @@ def create_limit_order(accountID, other_args: List[str]): data = { "order": { "price": ns_parser.price, - "instrument": ns_parser.instrument, + "instrument": instrument, "units": ns_parser.units, "type": "LIMIT", "timeInForce": "GTC", @@ -312,7 +264,7 @@ def get_open_trades(accountID): current_units = response["trades"][i]["currentUnits"] price = response["trades"][i]["price"] unrealized_pl = response["trades"][i]["unrealizedPL"] - print(f"Order ID: {order_id}\n") + print(f"Order ID: {order_id}") print(f"Instrument: {instrument}") print(f"Initial Units: {initial_units}") print(f"Current Units: {current_units}") @@ -358,53 +310,120 @@ def close_trade(accountID, other_args: List[str]): print(e) -def show_candles(accountID): +def show_candles(accountID, instrument): + #TODO Add switches for granularity and count parameters = { - "granularity": "D" + "granularity": "D", + "count":"180" } try: - request = instruments.InstrumentsCandles("EUR_USD", params=parameters) + request = instruments.InstrumentsCandles(instrument, params=parameters) response = client.request(request) process_response(response) oanda_fix_date(".temp_candles.csv") - df = pd.read_csv(".candles.csv") - df["Datetime"] = pd.to_datetime(df["Datetime"]) - df.set_index(pd.DatetimeIndex(df["Datetime"]), inplace=True) - print(df) -# dvalues = df[["Open", "High", "Low", "Close"]].values.tolist() -# pdates = mdates.date2num(data.index) -# ohlc = [ ppdates[i] + dvalues[i] for i in range(len(pdates))] -# plt.style.use("fivethirtyeight") -# fix, ax = plt.subplots(figsize = (12, 6)) -# mpf.candlestick_ohlc(ax, ohlc[-50:], width=0.4) -# ax.set_xlabel("Date") -# ax.set_ylabel("Price") -# ax.set_title("Chart for EUR_USD") -# ax.xaxis.set_major_formatter(mdates.DateFormatter('%b %d')) -# fig.autofmt_xdate() - mpf.plot(df, type="candlestick") -# plt.show() - + df = pd.read_csv(".candles.csv", index_col=0) + df.index = pd.to_datetime(df.index) + df.columns = ['Open', 'High', 'Low', 'Close', 'Volume'] + mpf.plot(df, type="candle", style="charles", volume=True) except Exception as e: print(e) + except NameError as e: + print(e) def process_response(response): with open(".temp_candles.csv", 'w') as out: for i in range(len(response["candles"])): time = response["candles"][i]["time"] + volume = response["candles"][i]["volume"] o = response["candles"][i]["mid"]["o"] h = response["candles"][i]["mid"]["h"] l = response["candles"][i]["mid"]["l"] c = response["candles"][i]["mid"]["c"] - out.write(str(time) + "," + str(o) + "," + str(h) + "," + str(l) + "," + str(c) + "\n") + out.write(str(time) + "," + str(o) + "," + str(h) + "," + str(l) + "," + str(c) + "," + str(volume) + "\n") def oanda_fix_date(file): with open(file, 'r') as candle_file: lines = candle_file.readlines() with open(".candles.csv", 'w') as out: - out.write("Datetime, Open, High, Low, Close\n") + out.write("Datetime, Open, High, Low, Close, Volume\n") for line in lines: with open(".candles.csv", "a") as output: output.write(line[:10] + " " + line[11:19] + line[30:]) + + +def calendar(instrument): + parameters = { + "instrument": instrument, + "period": "604800" + } + request = labs.Calendar(params=parameters) + response = client.request(request) +# print(response) + for i in range(len(response)): + if "title" in response[i]: + title = response[i]["title"] + print(f"Title: {title}") + if "impact" in response[i]: + impact = response[i]["impact"] + print(f"Impact: {impact}") + if "market" in response[i]: + market = response[i]["market"] + unit = response[i]["unit"] + if unit != "Index": + print(f"Market Forecast: {market}{unit}") + else: + print(f"Market Forecast: {market}") + if "currency" in response[i]: + currency = response[i]["currency"] + print(f"Currency: {currency}") + if "region" in response[i]: + region = response[i]["region"] + print(f"Region: {region}") + if "actual" in response[i]: + actual = response[i]["actual"] + unit = response[i]["unit"] + if unit != "Index": + print(f"Actual: {actual}{unit}") + else: + print(f"Actual: {actual}") + if "previous" in response[i]: + previous = response[i]["previous"] + unit = response[i]["unit"] + if unit != "Index": + print(f"Previous: {previous}{unit}") + else: + print(f"Previous: {previous}") + print("-"*30) + + +def load(other_args: List[str]): + """Load a forex instrument to use""" + parser = argparse.ArgumentParser( + add_help=False, + prog="Forex", + description = "Forex using oanda", + ) + + parser.add_argument( + "-i", + "--instrument", + required=True, + type=str, + dest="instrument", + help="Instrument to use for function calls" + ) + + 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 + return ns_parser.instrument + except Exception as e: + print(e) From 35a7dc3e9b764c27de43ea1b5dce801524846035 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 07:24:39 -0600 Subject: [PATCH 15/54] Added upper to instrument --- gamestonk_terminal/oanda/oanda_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py index 92bc832fefa..a40aa67c49d 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -424,6 +424,6 @@ def load(other_args: List[str]): if not ns_parser: return - return ns_parser.instrument + return ns_parser.instrument.upper() except Exception as e: print(e) From 612fb3d0ff5de04877868bd4bcb8137f8c653cb1 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 07:54:16 -0600 Subject: [PATCH 16/54] Added switches for candles --- gamestonk_terminal/oanda/oanda_controller.py | 4 +- gamestonk_terminal/oanda/oanda_functions.py | 41 ++++++++++++++++---- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/gamestonk_terminal/oanda/oanda_controller.py b/gamestonk_terminal/oanda/oanda_controller.py index ed4235745b6..fada0fbb42b 100644 --- a/gamestonk_terminal/oanda/oanda_controller.py +++ b/gamestonk_terminal/oanda/oanda_controller.py @@ -136,8 +136,8 @@ class OandaController: """Close a trade by id""" oanda_functions.close_trade(account, other_args) - def call_candles(self, _): - oanda_functions.show_candles(account, self.instrument) + def call_candles(self, other_args: List[str]): + oanda_functions.show_candles(account, self.instrument, other_args) def call_trades(self, _): """List open trades""" diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py index a40aa67c49d..c2f37a52fb3 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -77,10 +77,8 @@ def list_orders(accountID, other_args: List[str]): parameters = { } - if ns_parser.state is not None: - parameters["state"] = ns_parser.state - if ns_parser.count is not None: - parameters["count"] = ns_parser.count + parameters["state"] = ns_parser.state + parameters["count"] = ns_parser.count request = orders.OrderList(accountID, parameters) response = client.request(request) @@ -310,12 +308,39 @@ def close_trade(accountID, other_args: List[str]): print(e) -def show_candles(accountID, instrument): - #TODO Add switches for granularity and count +def show_candles(accountID, instrument, other_args: List[str]): + parser = argparse.ArgumentParser( + add_help=False, + prog="show_candles", + description="Display Candle Data", + ) + parser.add_argument( + "-g", + "--granularity", + dest="granularity", + action="store", + type=str, + default="D", + required=False, + ) + parser.add_argument( + "-c", + "--count", + dest="candlecount", + action="store", + default=180, + type=int, + required=False, + ) + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + parameters = { - "granularity": "D", - "count":"180" } + parameters["granularity"] = ns_parser.granularity + parameters["count"] = ns_parser.candlecount try: request = instruments.InstrumentsCandles(instrument, params=parameters) response = client.request(request) From 166f601c9047b76623d5a697887e921852574f2d Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 08:05:02 -0600 Subject: [PATCH 17/54] Added title to chart --- gamestonk_terminal/oanda/oanda_functions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py index c2f37a52fb3..70b394b03b6 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -349,7 +349,8 @@ def show_candles(accountID, instrument, other_args: List[str]): df = pd.read_csv(".candles.csv", index_col=0) df.index = pd.to_datetime(df.index) df.columns = ['Open', 'High', 'Low', 'Close', 'Volume'] - mpf.plot(df, type="candle", style="charles", volume=True) + mpf.plot(df, type="candle", style="charles", volume=True, + title=f"{instrument} {ns_parser.granularity}") except Exception as e: print(e) except NameError as e: From 0dd93d21e5e2e3ebd3af13c04473940bfd2ff73a Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 11:59:45 -0600 Subject: [PATCH 18/54] added option to select calendar days --- gamestonk_terminal/oanda/oanda_controller.py | 4 +- gamestonk_terminal/oanda/oanda_functions.py | 137 +++++++++---------- 2 files changed, 69 insertions(+), 72 deletions(-) diff --git a/gamestonk_terminal/oanda/oanda_controller.py b/gamestonk_terminal/oanda/oanda_controller.py index fada0fbb42b..192b0bce157 100644 --- a/gamestonk_terminal/oanda/oanda_controller.py +++ b/gamestonk_terminal/oanda/oanda_controller.py @@ -144,9 +144,9 @@ class OandaController: oanda_functions.get_open_trades(account) - def call_calendar(self, _): + def call_calendar(self, other_args: List[str]): """Call calendar""" - oanda_functions.calendar(self.instrument) + oanda_functions.calendar(self.instrument, other_args) def menu(): diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py index 70b394b03b6..5f47e5e0f04 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -12,8 +12,8 @@ from oandapyV20.exceptions import V20Error from gamestonk_terminal import config_terminal as cfg from gamestonk_terminal.helper_funcs import parse_known_args_and_warn import pandas as pd -import matplotlib.pyplot as plt import mplfinance as mpf +from datetime import datetime client = API(access_token=cfg.OANDA_TOKEN, environment="live") @@ -82,24 +82,9 @@ def list_orders(accountID, other_args: List[str]): request = orders.OrderList(accountID, parameters) response = client.request(request) - for i in range(0, int(ns_parser.count), 1): - try: - order_id = response["orders"][i]["id"] - instrument = response["orders"][i]["instrument"] - units = response["orders"][i]["units"] - order_state = response["orders"][i]["state"] - order_type = response["orders"][i]["type"] - - print(f"Order id: {order_id}") - print(f"Instrument: {instrument}") - print(f"Units: {units}") - print(f"Order State: {order_state}") - print(f"Order Type: {order_type}") - print("-" * 30) - except KeyError: - continue - except IndexError: - break + df = pd.DataFrame.from_dict(response["orders"]) + df = df[["id", "instrument", "units", "price", "state", "type"]] + print(df) def get_order_book(instrument): @@ -124,16 +109,9 @@ def get_position_book(instrument): try: request = instruments.InstrumentsPositionBook(instrument=instrument) response = client.request(request) - for i in range(len(response["positionBook"]["buckets"])): - order_instrument = response["positionBook"]["instrument"] - price = response["positionBook"]["buckets"][i]["price"] - short_count_percent = response["positionBook"]["buckets"][i][ "shortCountPercent" ] - long_count_percent = response["positionBook"]["buckets"][i]["longCountPercent"] - print(f"Instrument: {order_instrument}") - print(f"Price: {price}") - print(f"Short count percent: {short_count_percent}") - print(f"Long count percent: {long_count_percent}") - print("-" * 30) + df = pd.DataFrame.from_dict(response["positionBook"]["buckets"]) + pd.set_option("display.max_rows", None) + print(df) except V20Error as e: print(e) @@ -230,47 +208,34 @@ def get_pending_orders(accountID): def get_open_positions(accountID): request = positions.OpenPositions(accountID) response = client.request(request) - for i in range(0, 100, 1): - try: - instrument = response["positions"][i]["instrument"] - long_units = response["positions"][i]["long"]["units"] - long_pl = response["positions"][i]["long"]["pl"] - long_upl = response["positions"][i]["long"]["unrealizedPL"] - short_units = response["positions"][i]["short"]["units"] - short_pl = response["positions"][i]["short"]["pl"] - short_upl = response["positions"][i]["short"]["unrealizedPL"] - print(f"Instrument: {instrument}\n") - print(f"Long Units: {long_units}") - print(f"Total Long P/L: {long_pl}") - print(f"Long Unrealized P/L: {long_upl}\n") - print(f"Short Units: {short_units}") - print(f"Total Short P/L: {short_pl}") - print(f"Short Unrealized P/L: {short_upl}") - print("-" * 30 + "\n") - except IndexError: - break + for i in range(len(response["positions"])): + instrument = response["positions"][i]["instrument"] + long_units = response["positions"][i]["long"]["units"] + long_pl = response["positions"][i]["long"]["pl"] + long_upl = response["positions"][i]["long"]["unrealizedPL"] + short_units = response["positions"][i]["short"]["units"] + short_pl = response["positions"][i]["short"]["pl"] + short_upl = response["positions"][i]["short"]["unrealizedPL"] + print(f"Instrument: {instrument}\n") + print(f"Long Units: {long_units}") + print(f"Total Long P/L: {long_pl}") + print(f"Long Unrealized P/L: {long_upl}\n") + print(f"Short Units: {short_units}") + print(f"Total Short P/L: {short_pl}") + print(f"Short Unrealized P/L: {short_upl}") + print("-" * 30 + "\n") def get_open_trades(accountID): request = trades.OpenTrades(accountID) response = client.request(request) - for i in range(len(response["trades"])): - try: - order_id = response["trades"][i]["id"] - instrument = response["trades"][i]["instrument"] - initial_units = response["trades"][i]["initialUnits"] - current_units = response["trades"][i]["currentUnits"] - price = response["trades"][i]["price"] - unrealized_pl = response["trades"][i]["unrealizedPL"] - print(f"Order ID: {order_id}") - print(f"Instrument: {instrument}") - print(f"Initial Units: {initial_units}") - print(f"Current Units: {current_units}") - print(f"Entry Price: {price}") - print(f"Unrealized P/L: {unrealized_pl}") - print("-" * 30 + "\n") - except IndexError: - break + 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"}) + print(df) def close_trade(accountID, other_args: List[str]): @@ -344,7 +309,7 @@ def show_candles(accountID, instrument, other_args: List[str]): try: request = instruments.InstrumentsCandles(instrument, params=parameters) response = client.request(request) - process_response(response) + process_candle_response(response) oanda_fix_date(".temp_candles.csv") df = pd.read_csv(".candles.csv", index_col=0) df.index = pd.to_datetime(df.index) @@ -357,7 +322,7 @@ def show_candles(accountID, instrument, other_args: List[str]): print(e) -def process_response(response): +def process_candle_response(response): with open(".temp_candles.csv", 'w') as out: for i in range(len(response["candles"])): time = response["candles"][i]["time"] @@ -379,21 +344,53 @@ def oanda_fix_date(file): output.write(line[:10] + " " + line[11:19] + line[30:]) -def calendar(instrument): +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, + ) + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + parameters = { "instrument": instrument, - "period": "604800" + "period": str(ns_parser.days * 86400 * -1) } request = labs.Calendar(params=parameters) response = client.request(request) -# print(response) + df = pd.DataFrame.from_dict(response) + df = df[["title", "timestamp", "impact" , "forecast", "unit", "market", + "region", "previous", "currency" ]] + print(df) for i in range(len(response)): if "title" in response[i]: title = response[i]["title"] print(f"Title: {title}") + if "timestamp" in response[i]: + timestamp = response[i]["timestamp"] + time = datetime.fromtimestamp(timestamp) + print(f"Time: {time}") if "impact" in response[i]: impact = response[i]["impact"] print(f"Impact: {impact}") + if "forecast" in response[i]: + forecast = response[i]["forecast"] + unit = response[i]["unit"] + if unit != "Index": + print(f"Forecast: {forecast}{unit}") + else: + print(f"Forecast: {forecast}") if "market" in response[i]: market = response[i]["market"] unit = response[i]["unit"] From 72f3390d66c9daaf56ccb07ea530f7d0a7fb5271 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 12:04:23 -0600 Subject: [PATCH 19/54] df change --- gamestonk_terminal/oanda/oanda_functions.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py index 5f47e5e0f04..4dfd3625ba6 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -91,16 +91,9 @@ def get_order_book(instrument): try: request = instruments.InstrumentsOrderBook(instrument=instrument) response = client.request(request) - for i in range(len(response["orderBook"]["buckets"])): - order_instrument = response["orderBook"]["instrument"] - price = response["orderBook"]["buckets"][i]["price"] - short_count_percent = response["orderBook"]["buckets"][i]["shortCountPercent"] - long_count_percent = response["orderBook"]["buckets"][i]["longCountPercent"] - print(f"Instrument: {order_instrument}") - print(f"Price: {price}") - print(f"Short count percent: {short_count_percent}") - print(f"Long count percent: {long_count_percent}") - print("-" * 30) + df = pd.DataFrame.from_dict(response["orderBook"]["buckets"]) + pd.set_option("display.max_rows", None) + print(df) except V20Error as e: print(e) @@ -369,10 +362,6 @@ def calendar(instrument, other_args: List[str]): } request = labs.Calendar(params=parameters) response = client.request(request) - df = pd.DataFrame.from_dict(response) - df = df[["title", "timestamp", "impact" , "forecast", "unit", "market", - "region", "previous", "currency" ]] - print(df) for i in range(len(response)): if "title" in response[i]: title = response[i]["title"] From 7e9fc90064d74e97dc9216a11a6c2548362a3212 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 18:55:29 -0600 Subject: [PATCH 20/54] Adjusted Help --- gamestonk_terminal/oanda/oanda_controller.py | 35 ++++---- gamestonk_terminal/oanda/oanda_functions.py | 91 +++++++++++++++----- 2 files changed, 86 insertions(+), 40 deletions(-) diff --git a/gamestonk_terminal/oanda/oanda_controller.py b/gamestonk_terminal/oanda/oanda_controller.py index 192b0bce157..90515612e30 100644 --- a/gamestonk_terminal/oanda/oanda_controller.py +++ b/gamestonk_terminal/oanda/oanda_controller.py @@ -47,24 +47,24 @@ class OandaController: """Print help""" print("\nForex Mode:") - print("help \t show this menu again") - print("q \t quit this menu and goes back to main menu") - print("quit \t quit to abandon program") + print(" help Show this menu again") + print(" q Quit this menu and goes back to main menu") + print(" quit Quit to abandon program") print("") - print("load \t Load an instrument to use") - print("price \t shows price for selected instrument") - print("summary shows account summary") - print("list \t list order history") - print("trades \t list open trades") - print("orderbook \t print orderbook") - print("positionbook \t print positionbook") - print("order \t place limit order -u # of units -p price") - print("cancel \t cancel a pending order by ID -i order ID") - print("pending \t get information on pending orders") - print("positions \t Get open positions") - print("closetrade \t Close a trade by id") - print("candles \t show candles") - print("calendar \t show calendar") + print(" cancel Cancel a pending order by ID -i order ID") + print(" candles Show candles") + print(" calendar Show calendar") + print(" closetrade Close a trade by id") + print(" list List order history") + print(" load Load an instrument to use") + print(" order Place limit order -u # of units -p price") + print(" orderbook Print orderbook") + print(" pending Get information on pending orders") + print(" positionbook Print positionbook") + print(" positions Get open positions") + print(" price Shows price for selected instrument") + print(" summary Shows account summary") + print(" trades List open trades") def switch(self, an_input: str): """Process and dispatch input @@ -143,7 +143,6 @@ class OandaController: """List open trades""" oanda_functions.get_open_trades(account) - def call_calendar(self, other_args: List[str]): """Call calendar""" oanda_functions.calendar(self.instrument, other_args) diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py index 4dfd3625ba6..d12e5d0131b 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -10,9 +10,13 @@ import oandapyV20.endpoints.trades as trades import oandapyV20.endpoints.forexlabs as labs from oandapyV20.exceptions import V20Error from gamestonk_terminal import config_terminal as cfg -from gamestonk_terminal.helper_funcs import parse_known_args_and_warn +from gamestonk_terminal import config_plot as cfgPlot +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, plot_autoscale import pandas as pd import mplfinance as mpf +import matplotlib.pyplot as plt +import seaborn as sns from datetime import datetime @@ -88,12 +92,14 @@ def list_orders(accountID, other_args: List[str]): def get_order_book(instrument): + parameters = {"bucketWidth":"1"} try: - request = instruments.InstrumentsOrderBook(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) - print(df) + df = df.take(range(527,727,1)) + book_plot(df, instrument, "Order Book") except V20Error as e: print(e) @@ -104,7 +110,8 @@ def get_position_book(instrument): response = client.request(request) df = pd.DataFrame.from_dict(response["positionBook"]["buckets"]) pd.set_option("display.max_rows", None) - print(df) + df = df.take(range(219,415,1)) + book_plot(df, instrument, "Position Book") except V20Error as e: print(e) @@ -199,24 +206,27 @@ def get_pending_orders(accountID): def get_open_positions(accountID): - request = positions.OpenPositions(accountID) - response = client.request(request) - for i in range(len(response["positions"])): - instrument = response["positions"][i]["instrument"] - long_units = response["positions"][i]["long"]["units"] - long_pl = response["positions"][i]["long"]["pl"] - long_upl = response["positions"][i]["long"]["unrealizedPL"] - short_units = response["positions"][i]["short"]["units"] - short_pl = response["positions"][i]["short"]["pl"] - short_upl = response["positions"][i]["short"]["unrealizedPL"] - print(f"Instrument: {instrument}\n") - print(f"Long Units: {long_units}") - print(f"Total Long P/L: {long_pl}") - print(f"Long Unrealized P/L: {long_upl}\n") - print(f"Short Units: {short_units}") - print(f"Total Short P/L: {short_pl}") - print(f"Short Unrealized P/L: {short_upl}") - print("-" * 30 + "\n") + try: + request = positions.OpenPositions(accountID) + response = client.request(request) + for i in range(len(response["positions"])): + instrument = response["positions"][i]["instrument"] + long_units = response["positions"][i]["long"]["units"] + long_pl = response["positions"][i]["long"]["pl"] + long_upl = response["positions"][i]["long"]["unrealizedPL"] + short_units = response["positions"][i]["short"]["units"] + short_pl = response["positions"][i]["short"]["pl"] + short_upl = response["positions"][i]["short"]["unrealizedPL"] + print(f"Instrument: {instrument}\n") + print(f"Long Units: {long_units}") + print(f"Total Long P/L: {long_pl}") + print(f"Long Unrealized P/L: {long_upl}\n") + print(f"Short Units: {short_units}") + print(f"Total Short P/L: {short_pl}") + print(f"Short Unrealized P/L: {short_upl}") + print("-" * 30 + "\n") + except V20Error as e: + print(e) def get_open_trades(accountID): @@ -307,6 +317,8 @@ def show_candles(accountID, instrument, other_args: List[str]): df = pd.read_csv(".candles.csv", index_col=0) df.index = pd.to_datetime(df.index) df.columns = ['Open', 'High', 'Low', 'Close', 'Volume'] + if gtff.USE_ION: + plt.ion() mpf.plot(df, type="candle", style="charles", volume=True, title=f"{instrument} {ns_parser.granularity}") except Exception as e: @@ -439,3 +451,38 @@ def load(other_args: List[str]): return ns_parser.instrument.upper() except Exception as e: print(e) + + +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") + + g = sns.barplot( + x="longCountPercent", + y="price", + data = df, + label = "Count Percent", + color="green", + orient="h", + ) + + g = sns.barplot( + x="shortCountPercent", + y="price", + data=df, + label="Prices", + color="red", + orient="h", + ) + + ax.invert_yaxis() + plt.title(f"{instrument} {book_type}") + sns.despine(left=True, bottom=True) + if gtff.USE_ION: + plt.ion() + plt.show() From 4365bb2042dd9858838e3d0135103ec35454fcc9 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 19:00:38 -0600 Subject: [PATCH 21/54] Added oandapyV20 to requirements --- requirements-full.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements-full.txt b/requirements-full.txt index 1c51e43294a..b77d7b1c099 100644 --- a/requirements-full.txt +++ b/requirements-full.txt @@ -100,6 +100,7 @@ nest-asyncio==1.5.1; python_full_version >= "3.6.1" and python_version >= "3.6" networkx==2.5; python_version >= "3.6" notebook==6.2.0; python_version >= "3.5" numpy==1.19.5; python_version >= "3.6" and python_full_version >= "3.6.2" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" and python_version >= "3.6") +oandapyV20==0.6.3; python_version >= "3.6" oauthlib==3.1.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" opt-einsum==3.3.0; python_version >= "3.5" overrides==3.1.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.6" From e0243d6e77f157d0674dfc3f05a6c4ccb45778dd Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 19:01:25 -0600 Subject: [PATCH 22/54] Added oanda api information to README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 54c5089fe7f..84dab9ee00c 100644 --- a/README.md +++ b/README.md @@ -257,10 +257,11 @@ These are the ones where a key is necessary: * Financial Modeling Prep: https://financialmodelingprep.com/developer * FRED: https://fred.stlouisfed.org/docs/api/api_key.html * News API: https://newsapi.org + * Oanda API: https://developer.oanda.com When these are obtained, don't forget to update [config_terminal.py](/gamestonk_terminal/config_terminal.py). -Alternatively, you can also set them to the following environment variables: GT_API_KEY_ALPHAVANTAGE, GT_API_KEY_FINANCIALMODELINGPREP, GT_API_KEY_QUANDL, GT_API_REDDIT_CLIENT_ID, GT_API_REDDIT_CLIENT_SECRET, GT_API_REDDIT_USERNAME, GT_API_REDDIT_USER_AGENT, GT_API_REDDIT_PASSWORD, GT_API_TWITTER_KEY, GT_API_TWITTER_SECRET_KEY, GT_API_TWITTER_BEARER_TOKEN, GT_API_POLYGON_KEY, GT_FRED_API_KEY, GT_API_NEWS_TOKEN. +Alternatively, you can also set them to the following environment variables: GT_API_KEY_ALPHAVANTAGE, GT_API_KEY_FINANCIALMODELINGPREP, GT_API_KEY_QUANDL, GT_API_REDDIT_CLIENT_ID, GT_API_REDDIT_CLIENT_SECRET, GT_API_REDDIT_USERNAME, GT_API_REDDIT_USER_AGENT, GT_API_REDDIT_PASSWORD, GT_API_TWITTER_KEY, GT_API_TWITTER_SECRET_KEY, GT_API_TWITTER_BEARER_TOKEN, GT_API_POLYGON_KEY, GT_FRED_API_KEY, GT_API_NEWS_TOKEN, GT_OANDA_TOKEN, GT_OANDA_ACCOUNT Example: ``` From 8613c431a2b20fc25a476fdb34ca340d2cd0bcc8 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 19:10:28 -0600 Subject: [PATCH 23/54] Adjusted yticks --- gamestonk_terminal/oanda/oanda_functions.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py index d12e5d0131b..07e6f367716 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -16,6 +16,7 @@ from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, plot_auto import pandas as pd import mplfinance as mpf import matplotlib.pyplot as plt +import matplotlib.ticker as mticker import seaborn as sns from datetime import datetime @@ -483,6 +484,7 @@ def book_plot(df, instrument, book_type): ax.invert_yaxis() plt.title(f"{instrument} {book_type}") sns.despine(left=True, bottom=True) + ax.yaxis.set_major_locator(mticker.MultipleLocator(5)) if gtff.USE_ION: plt.ion() plt.show() From 5cdf511da9a4b538ae10e0395d27b3f76085c41d Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 19:27:22 -0600 Subject: [PATCH 24/54] Run Linter --- gamestonk_terminal/oanda/oanda_controller.py | 4 +- gamestonk_terminal/oanda/oanda_functions.py | 175 +++++++++++-------- 2 files changed, 103 insertions(+), 76 deletions(-) diff --git a/gamestonk_terminal/oanda/oanda_controller.py b/gamestonk_terminal/oanda/oanda_controller.py index 90515612e30..1335b90675a 100644 --- a/gamestonk_terminal/oanda/oanda_controller.py +++ b/gamestonk_terminal/oanda/oanda_controller.py @@ -1,7 +1,7 @@ import argparse from typing import List from prompt_toolkit.completion import NestedCompleter -from gamestonk_terminal.helper_funcs import get_flair, parse_known_args_and_warn +from gamestonk_terminal.helper_funcs import get_flair from gamestonk_terminal import feature_flags as gtff from gamestonk_terminal.menu import session from gamestonk_terminal.oanda import oanda_functions @@ -33,7 +33,7 @@ class OandaController: "calendar", ] - def __init__( self): + def __init__(self): """Construct Data""" self.oanda_parser = argparse.ArgumentParser(add_help=False, prog="fx") self.oanda_parser.add_argument( diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py index 07e6f367716..b66512c7dcf 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -80,8 +80,7 @@ def list_orders(accountID, other_args: List[str]): if not ns_parser: return - parameters = { - } + parameters = {} parameters["state"] = ns_parser.state parameters["count"] = ns_parser.count @@ -93,13 +92,15 @@ def list_orders(accountID, other_args: List[str]): def get_order_book(instrument): - parameters = {"bucketWidth":"1"} + parameters = {"bucketWidth": "1"} try: - request = instruments.InstrumentsOrderBook(instrument=instrument, params = parameters) + 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)) + df = df.take(range(527, 727, 1)) book_plot(df, instrument, "Order Book") except V20Error as e: print(e) @@ -111,7 +112,7 @@ def get_position_book(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)) + df = df.take(range(219, 415, 1)) book_plot(df, instrument, "Position Book") except V20Error as e: print(e) @@ -234,16 +235,23 @@ def get_open_trades(accountID): request = trades.OpenTrades(accountID) response = client.request(request) 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"}) + 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", + } + ) print(df) def close_trade(accountID, other_args: List[str]): - parser = ArgumentParser( + parser = argparse.ArgumentParser( add_help=False, prog="close_trade", description="close a trade", @@ -273,25 +281,26 @@ def close_trade(accountID, other_args: List[str]): try: request = trades.TradeClose(accountID, ns_parser.orderID, data) response = client.request(request) + print(response) except V20Error as e: print(e) def show_candles(accountID, instrument, other_args: List[str]): parser = argparse.ArgumentParser( - add_help=False, - prog="show_candles", - description="Display Candle Data", - ) + add_help=False, + prog="show_candles", + description="Display Candle Data", + ) parser.add_argument( - "-g", - "--granularity", - dest="granularity", - action="store", - type=str, - default="D", - required=False, - ) + "-g", + "--granularity", + dest="granularity", + action="store", + type=str, + default="D", + required=False, + ) parser.add_argument( "-c", "--count", @@ -300,14 +309,13 @@ def show_candles(accountID, instrument, other_args: List[str]): default=180, type=int, required=False, - ) + ) ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: return - parameters = { - } + parameters = {} parameters["granularity"] = ns_parser.granularity parameters["count"] = ns_parser.candlecount try: @@ -317,11 +325,16 @@ def show_candles(accountID, instrument, other_args: List[str]): oanda_fix_date(".temp_candles.csv") df = pd.read_csv(".candles.csv", index_col=0) df.index = pd.to_datetime(df.index) - df.columns = ['Open', 'High', 'Low', 'Close', 'Volume'] + df.columns = ["Open", "High", "Low", "Close", "Volume"] if gtff.USE_ION: plt.ion() - mpf.plot(df, type="candle", style="charles", volume=True, - title=f"{instrument} {ns_parser.granularity}") + mpf.plot( + df, + type="candle", + style="charles", + volume=True, + title=f"{instrument} {ns_parser.granularity}", + ) except Exception as e: print(e) except NameError as e: @@ -329,21 +342,34 @@ def show_candles(accountID, instrument, other_args: List[str]): def process_candle_response(response): - with open(".temp_candles.csv", 'w') as out: + with open(".temp_candles.csv", "w") as out: for i in range(len(response["candles"])): time = response["candles"][i]["time"] volume = response["candles"][i]["volume"] o = response["candles"][i]["mid"]["o"] h = response["candles"][i]["mid"]["h"] - l = response["candles"][i]["mid"]["l"] + low = response["candles"][i]["mid"]["l"] c = response["candles"][i]["mid"]["c"] - out.write(str(time) + "," + str(o) + "," + str(h) + "," + str(l) + "," + str(c) + "," + str(volume) + "\n") + out.write( + str(time) + + "," + + str(o) + + "," + + str(h) + + "," + + str(low) + + "," + + str(c) + + "," + + str(volume) + + "\n" + ) def oanda_fix_date(file): - with open(file, 'r') as candle_file: + with open(file, "r") as candle_file: lines = candle_file.readlines() - with open(".candles.csv", 'w') as out: + with open(".candles.csv", "w") as out: out.write("Datetime, Open, High, Low, Close, Volume\n") for line in lines: with open(".candles.csv", "a") as output: @@ -352,27 +378,24 @@ def oanda_fix_date(file): def calendar(instrument, other_args: List[str]): parser = argparse.ArgumentParser( - add_help=False, - prog="calendar", - description="Show Calendar Data", - ) + add_help=False, + prog="calendar", + description="Show Calendar Data", + ) parser.add_argument( - "-d", - "--days", - dest="days", - action="store", - type=int, - default=7, - required=False, - ) + "-d", + "--days", + dest="days", + action="store", + type=int, + default=7, + required=False, + ) 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) - } + parameters = {"instrument": instrument, "period": str(ns_parser.days * 86400 * -1)} request = labs.Calendar(params=parameters) response = client.request(request) for i in range(len(response)): @@ -420,7 +443,7 @@ def calendar(instrument, other_args: List[str]): print(f"Previous: {previous}{unit}") else: print(f"Previous: {previous}") - print("-"*30) + print("-" * 30) def load(other_args: List[str]): @@ -428,16 +451,16 @@ def load(other_args: List[str]): parser = argparse.ArgumentParser( add_help=False, prog="Forex", - description = "Forex using oanda", + description="Forex using oanda", ) parser.add_argument( - "-i", - "--instrument", - required=True, - type=str, - dest="instrument", - help="Instrument to use for function calls" + "-i", + "--instrument", + required=True, + type=str, + dest="instrument", + help="Instrument to use for function calls", ) try: @@ -458,31 +481,35 @@ 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"]))) + axis_origin = max( + abs(max(df["longCountPercent"])), abs(max(df["shortCountPercent"])) + ) ax.set_xlim(-axis_origin, +axis_origin) sns.set_style(style="darkgrid") - g = sns.barplot( - x="longCountPercent", - y="price", - data = df, - label = "Count Percent", - color="green", - orient="h", + sns.barplot( + x="longCountPercent", + y="price", + data=df, + label="Count Percent", + color="green", + orient="h", ) - g = sns.barplot( - x="shortCountPercent", - y="price", - data=df, - label="Prices", - color="red", - 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: From c88b390e976ba773afc3c49953bbb91a24be8c48 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 19:30:23 -0600 Subject: [PATCH 25/54] Added Oanda API information to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84dab9ee00c..cf6080cfdb4 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,7 @@ These are the ones where a key is necessary: When these are obtained, don't forget to update [config_terminal.py](/gamestonk_terminal/config_terminal.py). -Alternatively, you can also set them to the following environment variables: GT_API_KEY_ALPHAVANTAGE, GT_API_KEY_FINANCIALMODELINGPREP, GT_API_KEY_QUANDL, GT_API_REDDIT_CLIENT_ID, GT_API_REDDIT_CLIENT_SECRET, GT_API_REDDIT_USERNAME, GT_API_REDDIT_USER_AGENT, GT_API_REDDIT_PASSWORD, GT_API_TWITTER_KEY, GT_API_TWITTER_SECRET_KEY, GT_API_TWITTER_BEARER_TOKEN, GT_API_POLYGON_KEY, GT_FRED_API_KEY, GT_API_NEWS_TOKEN, GT_OANDA_TOKEN, GT_OANDA_ACCOUNT +Alternatively, you can also set them to the following environment variables: GT_API_KEY_ALPHAVANTAGE, GT_API_KEY_FINANCIALMODELINGPREP, GT_API_KEY_QUANDL, GT_API_REDDIT_CLIENT_ID, GT_API_REDDIT_CLIENT_SECRET, GT_API_REDDIT_USERNAME, GT_API_REDDIT_USER_AGENT, GT_API_REDDIT_PASSWORD, GT_API_TWITTER_KEY, GT_API_TWITTER_SECRET_KEY, GT_API_TWITTER_BEARER_TOKEN, GT_API_POLYGON_KEY, GT_FRED_API_KEY, GT_API_NEWS_TOKEN, GT_OANDA_TOKEN, GT_OANDA_ACCOUNT. Example: ``` From 3a68402f857a0f2d459c8e92758fb34d22cc22a8 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 19:34:53 -0600 Subject: [PATCH 26/54] Added oanda import to terminal --- terminal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/terminal.py b/terminal.py index 706ec39a2e1..0e9652ee878 100644 --- a/terminal.py +++ b/terminal.py @@ -33,7 +33,6 @@ from gamestonk_terminal.cryptocurrency import crypto_controller from gamestonk_terminal.screener import screener_controller from gamestonk_terminal.portfolio_optimization import po_controller from gamestonk_terminal.oanda import oanda_controller -from gamestonk_terminal.portfolio_optimization import po_controller # import warnings From 29454818aa9705744eb0f975d4fd5cb9dfad7771 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 19:44:21 -0600 Subject: [PATCH 27/54] Fixed typo --- gamestonk_terminal/oanda/oanda_controller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamestonk_terminal/oanda/oanda_controller.py b/gamestonk_terminal/oanda/oanda_controller.py index 1335b90675a..ec59bf4591c 100644 --- a/gamestonk_terminal/oanda/oanda_controller.py +++ b/gamestonk_terminal/oanda/oanda_controller.py @@ -73,7 +73,7 @@ class OandaController: True, False, or None False - quit the menu True - quit the program - None - contiue in the menu + None - continue in the menu """ (known_args, other_args) = self.oanda_parser.parse_known_args(an_input.split()) From d286cf325fd6f529b5f6d07c3fdac90b88e382b6 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Fri, 16 Apr 2021 19:52:08 -0600 Subject: [PATCH 28/54] Appease Linter --- gamestonk_terminal/oanda/oanda_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py index b66512c7dcf..ff092321cf2 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -367,7 +367,7 @@ def process_candle_response(response): def oanda_fix_date(file): - with open(file, "r") as candle_file: + with open(file) as candle_file: lines = candle_file.readlines() with open(".candles.csv", "w") as out: out.write("Datetime, Open, High, Low, Close, Volume\n") From b5c25fafe4632678280fe124a55dd94c45bedf72 Mon Sep 17 00:00:00 2001 From: DJ Date: Sat, 17 Apr 2021 11:18:33 -0600 Subject: [PATCH 29/54] Delete .candles.csv --- .candles.csv | 181 --------------------------------------------------- 1 file changed, 181 deletions(-) delete mode 100644 .candles.csv diff --git a/.candles.csv b/.candles.csv deleted file mode 100644 index 1fc96fbf978..00000000000 --- a/.candles.csv +++ /dev/null @@ -1,181 +0,0 @@ -Datetime, Open, High, Low, Close, Volume -2021-03-07 22:00:00,1.19188,1.19324,1.19176,1.19273,9854 -2021-03-08 02:00:00,1.19270,1.19285,1.18987,1.19124,13962 -2021-03-08 06:00:00,1.19123,1.19148,1.18652,1.18722,27758 -2021-03-08 10:00:00,1.18724,1.18808,1.18568,1.18712,27248 -2021-03-08 14:00:00,1.18712,1.18873,1.18458,1.18566,39112 -2021-03-08 18:00:00,1.18568,1.18650,1.18442,1.18460,13787 -2021-03-08 22:00:00,1.18488,1.18528,1.18356,1.18384,9992 -2021-03-09 02:00:00,1.18384,1.18672,1.18381,1.18540,15136 -2021-03-09 06:00:00,1.18541,1.19092,1.18513,1.19056,27596 -2021-03-09 10:00:00,1.19054,1.19158,1.18824,1.18918,26680 -2021-03-09 14:00:00,1.18920,1.19091,1.18814,1.18864,33339 -2021-03-09 18:00:00,1.18862,1.19050,1.18848,1.19016,12812 -2021-03-09 22:00:00,1.18987,1.19029,1.18841,1.18852,6519 -2021-03-10 02:00:00,1.18853,1.18856,1.18705,1.18751,9576 -2021-03-10 06:00:00,1.18753,1.19016,1.18690,1.18998,18835 -2021-03-10 10:00:00,1.19000,1.19130,1.18818,1.19107,27887 -2021-03-10 14:00:00,1.19108,1.19251,1.18860,1.19083,34165 -2021-03-10 18:00:00,1.19084,1.19299,1.19016,1.19286,17331 -2021-03-10 22:00:00,1.19280,1.19294,1.19158,1.19218,5078 -2021-03-11 02:00:00,1.19220,1.19348,1.19220,1.19272,8526 -2021-03-11 06:00:00,1.19275,1.19690,1.19191,1.19637,21714 -2021-03-11 10:00:00,1.19638,1.19737,1.19428,1.19572,30296 -2021-03-11 14:00:00,1.19571,1.19791,1.19280,1.19722,33170 -2021-03-11 18:00:00,1.19721,1.19900,1.19714,1.19856,15124 -2021-03-11 22:00:00,1.19818,1.19886,1.19802,1.19843,4595 -2021-03-12 02:00:00,1.19846,1.19846,1.19598,1.19635,9685 -2021-03-12 06:00:00,1.19636,1.19636,1.19111,1.19178,26509 -2021-03-12 10:00:00,1.19180,1.19403,1.19099,1.19266,24895 -2021-03-12 14:00:00,1.19266,1.19568,1.19114,1.19480,33856 -2021-03-12 18:00:00,1.19480,1.19614,1.19446,1.19525,9883 -2021-03-14 21:00:00,1.19516,1.19563,1.19426,1.19514,4912 -2021-03-15 01:00:00,1.19512,1.19674,1.19426,1.19433,9580 -2021-03-15 05:00:00,1.19436,1.19438,1.19179,1.19368,17512 -2021-03-15 09:00:00,1.19367,1.19403,1.19192,1.19250,17179 -2021-03-15 13:00:00,1.19251,1.19374,1.19112,1.19231,23674 -2021-03-15 17:00:00,1.19230,1.19337,1.19194,1.19302,7967 -2021-03-15 21:00:00,1.19262,1.19362,1.19224,1.19229,2779 -2021-03-16 01:00:00,1.19232,1.19388,1.19222,1.19378,6773 -2021-03-16 05:00:00,1.19376,1.19389,1.19136,1.19268,12372 -2021-03-16 09:00:00,1.19267,1.19519,1.19204,1.19230,20725 -2021-03-16 13:00:00,1.19228,1.19274,1.18822,1.18932,30412 -2021-03-16 17:00:00,1.18931,1.19102,1.18926,1.19026,12764 -2021-03-16 21:00:00,1.19009,1.19062,1.19000,1.19014,3119 -2021-03-17 01:00:00,1.19012,1.19042,1.18927,1.19036,5441 -2021-03-17 05:00:00,1.19034,1.19082,1.18863,1.18981,9490 -2021-03-17 09:00:00,1.18982,1.19167,1.18913,1.19118,16996 -2021-03-17 13:00:00,1.19116,1.19152,1.18970,1.19016,20537 -2021-03-17 17:00:00,1.19018,1.19856,1.18977,1.19794,36731 -2021-03-17 21:00:00,1.19781,1.19877,1.19774,1.19855,5270 -2021-03-18 01:00:00,1.19854,1.19866,1.19554,1.19614,10080 -2021-03-18 05:00:00,1.19612,1.19890,1.19350,1.19474,22385 -2021-03-18 09:00:00,1.19472,1.19504,1.19220,1.19282,25889 -2021-03-18 13:00:00,1.19280,1.19388,1.19086,1.19204,31273 -2021-03-18 17:00:00,1.19205,1.19318,1.19062,1.19160,17091 -2021-03-18 21:00:00,1.19124,1.19206,1.19044,1.19074,4060 -2021-03-19 01:00:00,1.19075,1.19200,1.19020,1.19189,9134 -2021-03-19 05:00:00,1.19187,1.19371,1.19110,1.19230,15763 -2021-03-19 09:00:00,1.19231,1.19234,1.18906,1.18998,18436 -2021-03-19 13:00:00,1.18997,1.19120,1.18739,1.19076,37510 -2021-03-19 17:00:00,1.19074,1.19126,1.19020,1.19042,9236 -2021-03-21 21:00:00,1.18740,1.18927,1.18676,1.18852,4498 -2021-03-22 01:00:00,1.18850,1.18991,1.18752,1.18936,6336 -2021-03-22 05:00:00,1.18935,1.19062,1.18746,1.19022,16718 -2021-03-22 09:00:00,1.19023,1.19315,1.18933,1.19232,18796 -2021-03-22 13:00:00,1.19230,1.19396,1.19108,1.19392,22535 -2021-03-22 17:00:00,1.19392,1.19470,1.19324,1.19326,6931 -2021-03-22 21:00:00,1.19331,1.19409,1.19286,1.19336,3283 -2021-03-23 01:00:00,1.19335,1.19372,1.19230,1.19234,7044 -2021-03-23 05:00:00,1.19232,1.19319,1.18859,1.18988,15287 -2021-03-23 09:00:00,1.18987,1.19002,1.18774,1.18892,22322 -2021-03-23 13:00:00,1.18893,1.18915,1.18583,1.18635,22177 -2021-03-23 17:00:00,1.18636,1.18662,1.18422,1.18493,10943 -2021-03-23 21:00:00,1.18483,1.18534,1.18362,1.18428,3828 -2021-03-24 01:00:00,1.18430,1.18511,1.18398,1.18465,6936 -2021-03-24 05:00:00,1.18464,1.18474,1.18124,1.18268,19025 -2021-03-24 09:00:00,1.18270,1.18450,1.18189,1.18338,19165 -2021-03-24 13:00:00,1.18336,1.18391,1.18144,1.18236,22688 -2021-03-24 17:00:00,1.18238,1.18306,1.18097,1.18129,10938 -2021-03-24 21:00:00,1.18133,1.18177,1.18090,1.18177,3131 -2021-03-25 01:00:00,1.18174,1.18278,1.18042,1.18207,9040 -2021-03-25 05:00:00,1.18206,1.18262,1.18014,1.18051,14688 -2021-03-25 09:00:00,1.18052,1.18182,1.17912,1.17974,18887 -2021-03-25 13:00:00,1.17972,1.18156,1.17626,1.17628,29987 -2021-03-25 17:00:00,1.17629,1.17819,1.17618,1.17644,15767 -2021-03-25 21:00:00,1.17650,1.17839,1.17650,1.17821,3703 -2021-03-26 01:00:00,1.17822,1.17848,1.17747,1.17766,4753 -2021-03-26 05:00:00,1.17768,1.17893,1.17752,1.17875,11155 -2021-03-26 09:00:00,1.17874,1.17972,1.17718,1.17762,15885 -2021-03-26 13:00:00,1.17761,1.18050,1.17761,1.17988,22670 -2021-03-26 17:00:00,1.17990,1.18002,1.17843,1.17941,10782 -2021-03-28 21:00:00,1.17944,1.17944,1.17778,1.17806,3048 -2021-03-29 01:00:00,1.17804,1.17911,1.17751,1.17789,5261 -2021-03-29 05:00:00,1.17788,1.17938,1.17630,1.17666,17392 -2021-03-29 09:00:00,1.17666,1.17886,1.17646,1.17856,17452 -2021-03-29 13:00:00,1.17855,1.17928,1.17606,1.17737,23298 -2021-03-29 17:00:00,1.17736,1.17784,1.17630,1.17650,8777 -2021-03-29 21:00:00,1.17640,1.17758,1.17640,1.17704,2501 -2021-03-30 01:00:00,1.17702,1.17738,1.17601,1.17642,5090 -2021-03-30 05:00:00,1.17640,1.17656,1.17330,1.17464,17511 -2021-03-30 09:00:00,1.17464,1.17498,1.17273,1.17314,17714 -2021-03-30 13:00:00,1.17312,1.17376,1.17114,1.17185,22998 -2021-03-30 17:00:00,1.17186,1.17232,1.17139,1.17158,5623 -2021-03-30 21:00:00,1.17175,1.17261,1.17151,1.17226,2918 -2021-03-31 01:00:00,1.17224,1.17241,1.17042,1.17077,5803 -2021-03-31 05:00:00,1.17078,1.17481,1.17048,1.17464,16485 -2021-03-31 09:00:00,1.17462,1.17463,1.17212,1.17256,16044 -2021-03-31 13:00:00,1.17257,1.17600,1.17211,1.17500,23648 -2021-03-31 17:00:00,1.17498,1.17554,1.17196,1.17308,10221 -2021-03-31 21:00:00,1.17298,1.17323,1.17211,1.17293,4047 -2021-04-01 01:00:00,1.17294,1.17330,1.17145,1.17158,6528 -2021-04-01 05:00:00,1.17156,1.17446,1.17126,1.17278,16300 -2021-04-01 09:00:00,1.17276,1.17539,1.17243,1.17453,15395 -2021-04-01 13:00:00,1.17454,1.17800,1.17390,1.17732,22727 -2021-04-01 17:00:00,1.17730,1.17787,1.17668,1.17774,7236 -2021-04-01 21:00:00,1.17770,1.17842,1.17703,1.17716,2091 -2021-04-02 01:00:00,1.17715,1.17814,1.17693,1.17780,4305 -2021-04-02 05:00:00,1.17782,1.17868,1.17696,1.17822,4985 -2021-04-02 09:00:00,1.17821,1.17862,1.17575,1.17688,7029 -2021-04-02 13:00:00,1.17687,1.17690,1.17494,1.17612,7176 -2021-04-02 17:00:00,1.17606,1.17662,1.17581,1.17602,1175 -2021-04-04 21:00:00,1.17608,1.17681,1.17558,1.17651,3427 -2021-04-05 01:00:00,1.17650,1.17720,1.17590,1.17591,3082 -2021-04-05 05:00:00,1.17592,1.17608,1.17408,1.17474,6965 -2021-04-05 09:00:00,1.17472,1.17810,1.17376,1.17790,8879 -2021-04-05 13:00:00,1.17791,1.18167,1.17781,1.18161,13710 -2021-04-05 17:00:00,1.18160,1.18196,1.18078,1.18123,4339 -2021-04-05 21:00:00,1.18123,1.18217,1.18118,1.18140,1803 -2021-04-06 01:00:00,1.18140,1.18172,1.17998,1.18056,6691 -2021-04-06 05:00:00,1.18058,1.18210,1.17950,1.18138,15413 -2021-04-06 09:00:00,1.18141,1.18334,1.18024,1.18288,17856 -2021-04-06 13:00:00,1.18290,1.18560,1.18262,1.18556,15721 -2021-04-06 17:00:00,1.18556,1.18778,1.18521,1.18765,7376 -2021-04-06 21:00:00,1.18758,1.18788,1.18686,1.18724,2632 -2021-04-07 01:00:00,1.18723,1.18780,1.18656,1.18708,6352 -2021-04-07 05:00:00,1.18710,1.18875,1.18640,1.18862,17542 -2021-04-07 09:00:00,1.18862,1.18984,1.18770,1.18848,18535 -2021-04-07 13:00:00,1.18846,1.19149,1.18818,1.18823,21581 -2021-04-07 17:00:00,1.18822,1.18869,1.18608,1.18718,8368 -2021-04-07 21:00:00,1.18670,1.18741,1.18656,1.18666,2406 -2021-04-08 01:00:00,1.18667,1.18740,1.18608,1.18728,5086 -2021-04-08 05:00:00,1.18730,1.18932,1.18673,1.18814,13100 -2021-04-08 09:00:00,1.18812,1.18950,1.18618,1.18927,16547 -2021-04-08 13:00:00,1.18928,1.19275,1.18836,1.19216,22154 -2021-04-08 17:00:00,1.19214,1.19232,1.19120,1.19154,6063 -2021-04-08 21:00:00,1.19150,1.19202,1.19107,1.19192,2559 -2021-04-09 01:00:00,1.19194,1.19202,1.19030,1.19042,5124 -2021-04-09 05:00:00,1.19041,1.19044,1.18838,1.18858,16244 -2021-04-09 09:00:00,1.18856,1.19004,1.18671,1.18761,17093 -2021-04-09 13:00:00,1.18763,1.19034,1.18706,1.19002,17963 -2021-04-09 17:00:00,1.19000,1.19106,1.18990,1.18998,5679 -2021-04-11 21:00:00,1.18906,1.19042,1.18905,1.18976,3324 -2021-04-12 01:00:00,1.18978,1.18988,1.18856,1.18882,7866 -2021-04-12 05:00:00,1.18880,1.18943,1.18712,1.18839,13449 -2021-04-12 09:00:00,1.18838,1.19184,1.18838,1.19142,15150 -2021-04-12 13:00:00,1.19143,1.19192,1.19036,1.19159,15895 -2021-04-12 17:00:00,1.19158,1.19174,1.18990,1.19114,5969 -2021-04-12 21:00:00,1.19106,1.19160,1.19023,1.19042,3037 -2021-04-13 01:00:00,1.19040,1.19046,1.18860,1.18874,7176 -2021-04-13 05:00:00,1.18876,1.19089,1.18828,1.19011,15321 -2021-04-13 09:00:00,1.19012,1.19346,1.18780,1.19244,27624 -2021-04-13 13:00:00,1.19243,1.19436,1.19232,1.19380,21071 -2021-04-13 17:00:00,1.19381,1.19560,1.19379,1.19486,8911 -2021-04-13 21:00:00,1.19480,1.19585,1.19476,1.19544,3196 -2021-04-14 01:00:00,1.19545,1.19642,1.19526,1.19628,7173 -2021-04-14 05:00:00,1.19626,1.19740,1.19550,1.19668,15951 -2021-04-14 09:00:00,1.19666,1.19696,1.19511,1.19638,14215 -2021-04-14 13:00:00,1.19639,1.19874,1.19529,1.19851,19938 -2021-04-14 17:00:00,1.19852,1.19856,1.19698,1.19790,7738 -2021-04-14 21:00:00,1.19785,1.19888,1.19778,1.19864,2666 -2021-04-15 01:00:00,1.19861,1.19900,1.19698,1.19704,7032 -2021-04-15 05:00:00,1.19702,1.19904,1.19702,1.19810,12298 -2021-04-15 09:00:00,1.19808,1.19934,1.19571,1.19584,17057 -2021-04-15 13:00:00,1.19584,1.19840,1.19562,1.19710,21044 -2021-04-15 17:00:00,1.19708,1.19774,1.19571,1.19665,8953 -2021-04-15 21:00:00,1.19665,1.19748,1.19562,1.19571,2562 -2021-04-16 01:00:00,1.19572,1.19642,1.19506,1.19634,7057 -2021-04-16 05:00:00,1.19632,1.19889,1.19584,1.19781,16098 -2021-04-16 09:00:00,1.19782,1.19950,1.19692,1.19838,16901 -2021-04-16 13:00:00,1.19838,1.19938,1.19750,1.19806,12900 -2021-04-16 17:00:00,1.19807,1.19836,1.19724,1.19822,3937 From d433091a12e2d6424a2162771b2af02027ca62cd Mon Sep 17 00:00:00 2001 From: DJ Date: Sat, 17 Apr 2021 11:18:54 -0600 Subject: [PATCH 30/54] Delete .temp_candles.csv --- .temp_candles.csv | 180 ---------------------------------------------- 1 file changed, 180 deletions(-) delete mode 100644 .temp_candles.csv diff --git a/.temp_candles.csv b/.temp_candles.csv deleted file mode 100644 index 45a45c32dc8..00000000000 --- a/.temp_candles.csv +++ /dev/null @@ -1,180 +0,0 @@ -2021-03-07T22:00:00.000000000Z,1.19188,1.19324,1.19176,1.19273,9854 -2021-03-08T02:00:00.000000000Z,1.19270,1.19285,1.18987,1.19124,13962 -2021-03-08T06:00:00.000000000Z,1.19123,1.19148,1.18652,1.18722,27758 -2021-03-08T10:00:00.000000000Z,1.18724,1.18808,1.18568,1.18712,27248 -2021-03-08T14:00:00.000000000Z,1.18712,1.18873,1.18458,1.18566,39112 -2021-03-08T18:00:00.000000000Z,1.18568,1.18650,1.18442,1.18460,13787 -2021-03-08T22:00:00.000000000Z,1.18488,1.18528,1.18356,1.18384,9992 -2021-03-09T02:00:00.000000000Z,1.18384,1.18672,1.18381,1.18540,15136 -2021-03-09T06:00:00.000000000Z,1.18541,1.19092,1.18513,1.19056,27596 -2021-03-09T10:00:00.000000000Z,1.19054,1.19158,1.18824,1.18918,26680 -2021-03-09T14:00:00.000000000Z,1.18920,1.19091,1.18814,1.18864,33339 -2021-03-09T18:00:00.000000000Z,1.18862,1.19050,1.18848,1.19016,12812 -2021-03-09T22:00:00.000000000Z,1.18987,1.19029,1.18841,1.18852,6519 -2021-03-10T02:00:00.000000000Z,1.18853,1.18856,1.18705,1.18751,9576 -2021-03-10T06:00:00.000000000Z,1.18753,1.19016,1.18690,1.18998,18835 -2021-03-10T10:00:00.000000000Z,1.19000,1.19130,1.18818,1.19107,27887 -2021-03-10T14:00:00.000000000Z,1.19108,1.19251,1.18860,1.19083,34165 -2021-03-10T18:00:00.000000000Z,1.19084,1.19299,1.19016,1.19286,17331 -2021-03-10T22:00:00.000000000Z,1.19280,1.19294,1.19158,1.19218,5078 -2021-03-11T02:00:00.000000000Z,1.19220,1.19348,1.19220,1.19272,8526 -2021-03-11T06:00:00.000000000Z,1.19275,1.19690,1.19191,1.19637,21714 -2021-03-11T10:00:00.000000000Z,1.19638,1.19737,1.19428,1.19572,30296 -2021-03-11T14:00:00.000000000Z,1.19571,1.19791,1.19280,1.19722,33170 -2021-03-11T18:00:00.000000000Z,1.19721,1.19900,1.19714,1.19856,15124 -2021-03-11T22:00:00.000000000Z,1.19818,1.19886,1.19802,1.19843,4595 -2021-03-12T02:00:00.000000000Z,1.19846,1.19846,1.19598,1.19635,9685 -2021-03-12T06:00:00.000000000Z,1.19636,1.19636,1.19111,1.19178,26509 -2021-03-12T10:00:00.000000000Z,1.19180,1.19403,1.19099,1.19266,24895 -2021-03-12T14:00:00.000000000Z,1.19266,1.19568,1.19114,1.19480,33856 -2021-03-12T18:00:00.000000000Z,1.19480,1.19614,1.19446,1.19525,9883 -2021-03-14T21:00:00.000000000Z,1.19516,1.19563,1.19426,1.19514,4912 -2021-03-15T01:00:00.000000000Z,1.19512,1.19674,1.19426,1.19433,9580 -2021-03-15T05:00:00.000000000Z,1.19436,1.19438,1.19179,1.19368,17512 -2021-03-15T09:00:00.000000000Z,1.19367,1.19403,1.19192,1.19250,17179 -2021-03-15T13:00:00.000000000Z,1.19251,1.19374,1.19112,1.19231,23674 -2021-03-15T17:00:00.000000000Z,1.19230,1.19337,1.19194,1.19302,7967 -2021-03-15T21:00:00.000000000Z,1.19262,1.19362,1.19224,1.19229,2779 -2021-03-16T01:00:00.000000000Z,1.19232,1.19388,1.19222,1.19378,6773 -2021-03-16T05:00:00.000000000Z,1.19376,1.19389,1.19136,1.19268,12372 -2021-03-16T09:00:00.000000000Z,1.19267,1.19519,1.19204,1.19230,20725 -2021-03-16T13:00:00.000000000Z,1.19228,1.19274,1.18822,1.18932,30412 -2021-03-16T17:00:00.000000000Z,1.18931,1.19102,1.18926,1.19026,12764 -2021-03-16T21:00:00.000000000Z,1.19009,1.19062,1.19000,1.19014,3119 -2021-03-17T01:00:00.000000000Z,1.19012,1.19042,1.18927,1.19036,5441 -2021-03-17T05:00:00.000000000Z,1.19034,1.19082,1.18863,1.18981,9490 -2021-03-17T09:00:00.000000000Z,1.18982,1.19167,1.18913,1.19118,16996 -2021-03-17T13:00:00.000000000Z,1.19116,1.19152,1.18970,1.19016,20537 -2021-03-17T17:00:00.000000000Z,1.19018,1.19856,1.18977,1.19794,36731 -2021-03-17T21:00:00.000000000Z,1.19781,1.19877,1.19774,1.19855,5270 -2021-03-18T01:00:00.000000000Z,1.19854,1.19866,1.19554,1.19614,10080 -2021-03-18T05:00:00.000000000Z,1.19612,1.19890,1.19350,1.19474,22385 -2021-03-18T09:00:00.000000000Z,1.19472,1.19504,1.19220,1.19282,25889 -2021-03-18T13:00:00.000000000Z,1.19280,1.19388,1.19086,1.19204,31273 -2021-03-18T17:00:00.000000000Z,1.19205,1.19318,1.19062,1.19160,17091 -2021-03-18T21:00:00.000000000Z,1.19124,1.19206,1.19044,1.19074,4060 -2021-03-19T01:00:00.000000000Z,1.19075,1.19200,1.19020,1.19189,9134 -2021-03-19T05:00:00.000000000Z,1.19187,1.19371,1.19110,1.19230,15763 -2021-03-19T09:00:00.000000000Z,1.19231,1.19234,1.18906,1.18998,18436 -2021-03-19T13:00:00.000000000Z,1.18997,1.19120,1.18739,1.19076,37510 -2021-03-19T17:00:00.000000000Z,1.19074,1.19126,1.19020,1.19042,9236 -2021-03-21T21:00:00.000000000Z,1.18740,1.18927,1.18676,1.18852,4498 -2021-03-22T01:00:00.000000000Z,1.18850,1.18991,1.18752,1.18936,6336 -2021-03-22T05:00:00.000000000Z,1.18935,1.19062,1.18746,1.19022,16718 -2021-03-22T09:00:00.000000000Z,1.19023,1.19315,1.18933,1.19232,18796 -2021-03-22T13:00:00.000000000Z,1.19230,1.19396,1.19108,1.19392,22535 -2021-03-22T17:00:00.000000000Z,1.19392,1.19470,1.19324,1.19326,6931 -2021-03-22T21:00:00.000000000Z,1.19331,1.19409,1.19286,1.19336,3283 -2021-03-23T01:00:00.000000000Z,1.19335,1.19372,1.19230,1.19234,7044 -2021-03-23T05:00:00.000000000Z,1.19232,1.19319,1.18859,1.18988,15287 -2021-03-23T09:00:00.000000000Z,1.18987,1.19002,1.18774,1.18892,22322 -2021-03-23T13:00:00.000000000Z,1.18893,1.18915,1.18583,1.18635,22177 -2021-03-23T17:00:00.000000000Z,1.18636,1.18662,1.18422,1.18493,10943 -2021-03-23T21:00:00.000000000Z,1.18483,1.18534,1.18362,1.18428,3828 -2021-03-24T01:00:00.000000000Z,1.18430,1.18511,1.18398,1.18465,6936 -2021-03-24T05:00:00.000000000Z,1.18464,1.18474,1.18124,1.18268,19025 -2021-03-24T09:00:00.000000000Z,1.18270,1.18450,1.18189,1.18338,19165 -2021-03-24T13:00:00.000000000Z,1.18336,1.18391,1.18144,1.18236,22688 -2021-03-24T17:00:00.000000000Z,1.18238,1.18306,1.18097,1.18129,10938 -2021-03-24T21:00:00.000000000Z,1.18133,1.18177,1.18090,1.18177,3131 -2021-03-25T01:00:00.000000000Z,1.18174,1.18278,1.18042,1.18207,9040 -2021-03-25T05:00:00.000000000Z,1.18206,1.18262,1.18014,1.18051,14688 -2021-03-25T09:00:00.000000000Z,1.18052,1.18182,1.17912,1.17974,18887 -2021-03-25T13:00:00.000000000Z,1.17972,1.18156,1.17626,1.17628,29987 -2021-03-25T17:00:00.000000000Z,1.17629,1.17819,1.17618,1.17644,15767 -2021-03-25T21:00:00.000000000Z,1.17650,1.17839,1.17650,1.17821,3703 -2021-03-26T01:00:00.000000000Z,1.17822,1.17848,1.17747,1.17766,4753 -2021-03-26T05:00:00.000000000Z,1.17768,1.17893,1.17752,1.17875,11155 -2021-03-26T09:00:00.000000000Z,1.17874,1.17972,1.17718,1.17762,15885 -2021-03-26T13:00:00.000000000Z,1.17761,1.18050,1.17761,1.17988,22670 -2021-03-26T17:00:00.000000000Z,1.17990,1.18002,1.17843,1.17941,10782 -2021-03-28T21:00:00.000000000Z,1.17944,1.17944,1.17778,1.17806,3048 -2021-03-29T01:00:00.000000000Z,1.17804,1.17911,1.17751,1.17789,5261 -2021-03-29T05:00:00.000000000Z,1.17788,1.17938,1.17630,1.17666,17392 -2021-03-29T09:00:00.000000000Z,1.17666,1.17886,1.17646,1.17856,17452 -2021-03-29T13:00:00.000000000Z,1.17855,1.17928,1.17606,1.17737,23298 -2021-03-29T17:00:00.000000000Z,1.17736,1.17784,1.17630,1.17650,8777 -2021-03-29T21:00:00.000000000Z,1.17640,1.17758,1.17640,1.17704,2501 -2021-03-30T01:00:00.000000000Z,1.17702,1.17738,1.17601,1.17642,5090 -2021-03-30T05:00:00.000000000Z,1.17640,1.17656,1.17330,1.17464,17511 -2021-03-30T09:00:00.000000000Z,1.17464,1.17498,1.17273,1.17314,17714 -2021-03-30T13:00:00.000000000Z,1.17312,1.17376,1.17114,1.17185,22998 -2021-03-30T17:00:00.000000000Z,1.17186,1.17232,1.17139,1.17158,5623 -2021-03-30T21:00:00.000000000Z,1.17175,1.17261,1.17151,1.17226,2918 -2021-03-31T01:00:00.000000000Z,1.17224,1.17241,1.17042,1.17077,5803 -2021-03-31T05:00:00.000000000Z,1.17078,1.17481,1.17048,1.17464,16485 -2021-03-31T09:00:00.000000000Z,1.17462,1.17463,1.17212,1.17256,16044 -2021-03-31T13:00:00.000000000Z,1.17257,1.17600,1.17211,1.17500,23648 -2021-03-31T17:00:00.000000000Z,1.17498,1.17554,1.17196,1.17308,10221 -2021-03-31T21:00:00.000000000Z,1.17298,1.17323,1.17211,1.17293,4047 -2021-04-01T01:00:00.000000000Z,1.17294,1.17330,1.17145,1.17158,6528 -2021-04-01T05:00:00.000000000Z,1.17156,1.17446,1.17126,1.17278,16300 -2021-04-01T09:00:00.000000000Z,1.17276,1.17539,1.17243,1.17453,15395 -2021-04-01T13:00:00.000000000Z,1.17454,1.17800,1.17390,1.17732,22727 -2021-04-01T17:00:00.000000000Z,1.17730,1.17787,1.17668,1.17774,7236 -2021-04-01T21:00:00.000000000Z,1.17770,1.17842,1.17703,1.17716,2091 -2021-04-02T01:00:00.000000000Z,1.17715,1.17814,1.17693,1.17780,4305 -2021-04-02T05:00:00.000000000Z,1.17782,1.17868,1.17696,1.17822,4985 -2021-04-02T09:00:00.000000000Z,1.17821,1.17862,1.17575,1.17688,7029 -2021-04-02T13:00:00.000000000Z,1.17687,1.17690,1.17494,1.17612,7176 -2021-04-02T17:00:00.000000000Z,1.17606,1.17662,1.17581,1.17602,1175 -2021-04-04T21:00:00.000000000Z,1.17608,1.17681,1.17558,1.17651,3427 -2021-04-05T01:00:00.000000000Z,1.17650,1.17720,1.17590,1.17591,3082 -2021-04-05T05:00:00.000000000Z,1.17592,1.17608,1.17408,1.17474,6965 -2021-04-05T09:00:00.000000000Z,1.17472,1.17810,1.17376,1.17790,8879 -2021-04-05T13:00:00.000000000Z,1.17791,1.18167,1.17781,1.18161,13710 -2021-04-05T17:00:00.000000000Z,1.18160,1.18196,1.18078,1.18123,4339 -2021-04-05T21:00:00.000000000Z,1.18123,1.18217,1.18118,1.18140,1803 -2021-04-06T01:00:00.000000000Z,1.18140,1.18172,1.17998,1.18056,6691 -2021-04-06T05:00:00.000000000Z,1.18058,1.18210,1.17950,1.18138,15413 -2021-04-06T09:00:00.000000000Z,1.18141,1.18334,1.18024,1.18288,17856 -2021-04-06T13:00:00.000000000Z,1.18290,1.18560,1.18262,1.18556,15721 -2021-04-06T17:00:00.000000000Z,1.18556,1.18778,1.18521,1.18765,7376 -2021-04-06T21:00:00.000000000Z,1.18758,1.18788,1.18686,1.18724,2632 -2021-04-07T01:00:00.000000000Z,1.18723,1.18780,1.18656,1.18708,6352 -2021-04-07T05:00:00.000000000Z,1.18710,1.18875,1.18640,1.18862,17542 -2021-04-07T09:00:00.000000000Z,1.18862,1.18984,1.18770,1.18848,18535 -2021-04-07T13:00:00.000000000Z,1.18846,1.19149,1.18818,1.18823,21581 -2021-04-07T17:00:00.000000000Z,1.18822,1.18869,1.18608,1.18718,8368 -2021-04-07T21:00:00.000000000Z,1.18670,1.18741,1.18656,1.18666,2406 -2021-04-08T01:00:00.000000000Z,1.18667,1.18740,1.18608,1.18728,5086 -2021-04-08T05:00:00.000000000Z,1.18730,1.18932,1.18673,1.18814,13100 -2021-04-08T09:00:00.000000000Z,1.18812,1.18950,1.18618,1.18927,16547 -2021-04-08T13:00:00.000000000Z,1.18928,1.19275,1.18836,1.19216,22154 -2021-04-08T17:00:00.000000000Z,1.19214,1.19232,1.19120,1.19154,6063 -2021-04-08T21:00:00.000000000Z,1.19150,1.19202,1.19107,1.19192,2559 -2021-04-09T01:00:00.000000000Z,1.19194,1.19202,1.19030,1.19042,5124 -2021-04-09T05:00:00.000000000Z,1.19041,1.19044,1.18838,1.18858,16244 -2021-04-09T09:00:00.000000000Z,1.18856,1.19004,1.18671,1.18761,17093 -2021-04-09T13:00:00.000000000Z,1.18763,1.19034,1.18706,1.19002,17963 -2021-04-09T17:00:00.000000000Z,1.19000,1.19106,1.18990,1.18998,5679 -2021-04-11T21:00:00.000000000Z,1.18906,1.19042,1.18905,1.18976,3324 -2021-04-12T01:00:00.000000000Z,1.18978,1.18988,1.18856,1.18882,7866 -2021-04-12T05:00:00.000000000Z,1.18880,1.18943,1.18712,1.18839,13449 -2021-04-12T09:00:00.000000000Z,1.18838,1.19184,1.18838,1.19142,15150 -2021-04-12T13:00:00.000000000Z,1.19143,1.19192,1.19036,1.19159,15895 -2021-04-12T17:00:00.000000000Z,1.19158,1.19174,1.18990,1.19114,5969 -2021-04-12T21:00:00.000000000Z,1.19106,1.19160,1.19023,1.19042,3037 -2021-04-13T01:00:00.000000000Z,1.19040,1.19046,1.18860,1.18874,7176 -2021-04-13T05:00:00.000000000Z,1.18876,1.19089,1.18828,1.19011,15321 -2021-04-13T09:00:00.000000000Z,1.19012,1.19346,1.18780,1.19244,27624 -2021-04-13T13:00:00.000000000Z,1.19243,1.19436,1.19232,1.19380,21071 -2021-04-13T17:00:00.000000000Z,1.19381,1.19560,1.19379,1.19486,8911 -2021-04-13T21:00:00.000000000Z,1.19480,1.19585,1.19476,1.19544,3196 -2021-04-14T01:00:00.000000000Z,1.19545,1.19642,1.19526,1.19628,7173 -2021-04-14T05:00:00.000000000Z,1.19626,1.19740,1.19550,1.19668,15951 -2021-04-14T09:00:00.000000000Z,1.19666,1.19696,1.19511,1.19638,14215 -2021-04-14T13:00:00.000000000Z,1.19639,1.19874,1.19529,1.19851,19938 -2021-04-14T17:00:00.000000000Z,1.19852,1.19856,1.19698,1.19790,7738 -2021-04-14T21:00:00.000000000Z,1.19785,1.19888,1.19778,1.19864,2666 -2021-04-15T01:00:00.000000000Z,1.19861,1.19900,1.19698,1.19704,7032 -2021-04-15T05:00:00.000000000Z,1.19702,1.19904,1.19702,1.19810,12298 -2021-04-15T09:00:00.000000000Z,1.19808,1.19934,1.19571,1.19584,17057 -2021-04-15T13:00:00.000000000Z,1.19584,1.19840,1.19562,1.19710,21044 -2021-04-15T17:00:00.000000000Z,1.19708,1.19774,1.19571,1.19665,8953 -2021-04-15T21:00:00.000000000Z,1.19665,1.19748,1.19562,1.19571,2562 -2021-04-16T01:00:00.000000000Z,1.19572,1.19642,1.19506,1.19634,7057 -2021-04-16T05:00:00.000000000Z,1.19632,1.19889,1.19584,1.19781,16098 -2021-04-16T09:00:00.000000000Z,1.19782,1.19950,1.19692,1.19838,16901 -2021-04-16T13:00:00.000000000Z,1.19838,1.19938,1.19750,1.19806,12900 -2021-04-16T17:00:00.000000000Z,1.19807,1.19836,1.19724,1.19822,3937 From 58f5afbed2cafea9b4e5a70b3e8838f6b63d5d6b Mon Sep 17 00:00:00 2001 From: DJ Date: Sat, 17 Apr 2021 11:19:49 -0600 Subject: [PATCH 31/54] Delete fx_news.py --- gamestonk_terminal/oanda/fx_news.py | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 gamestonk_terminal/oanda/fx_news.py diff --git a/gamestonk_terminal/oanda/fx_news.py b/gamestonk_terminal/oanda/fx_news.py deleted file mode 100644 index 4faf11dd54f..00000000000 --- a/gamestonk_terminal/oanda/fx_news.py +++ /dev/null @@ -1,19 +0,0 @@ -import requests -import pandas as pd -from bs4 import BeautifulSoup -from gamestonk_terminal.helper_funcs import get_user_agent - - -def daily_fx_news(): - daily_fx_url = "https://www.dailyfx.com/market-news/articles" - daily_fx_soup = BeautifulSoup( - requests.get(daily_fx_url, headers={"User-Agent": get_user_agent()}).text, - "lxml", - ) - daily_fx_news_header_list = list() - for daily_fx_news_header in daily_fx_soup.findAll("a", {"class": "dfx-articleListItem jsdfx-articleListItem d-flex mb-3"}): - daily_fx_news_header_list.append( - daily_fx_news_header.text.strip("\n").split("\n")) - print(daily_fx_news_header) - - From 269eadbfaf0c8a12d0bad2835c1d5e1341368eb7 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Sat, 17 Apr 2021 11:50:18 -0600 Subject: [PATCH 32/54] requirements updated with poetry --- requirements-full.txt | 146 +++++++++++++++--------------------------- requirements.txt | 68 ++++++++++++-------- 2 files changed, 93 insertions(+), 121 deletions(-) diff --git a/requirements-full.txt b/requirements-full.txt index b77d7b1c099..29b46fbad43 100644 --- a/requirements-full.txt +++ b/requirements-full.txt @@ -1,4 +1,3 @@ -absl-py==0.12.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" aiohttp==3.7.4.post0; python_version >= "3.6" alpaca-trade-api==1.1.0 alpha-vantage==2.3.1 @@ -6,90 +5,66 @@ ansiwrap==0.8.4; python_version >= "3.6" appdirs==1.4.4; python_version >= "3.6" appnope==0.1.2; platform_system == "Darwin" and python_version >= "3.6" and sys_platform == "darwin" argon2-cffi==20.1.0; python_version >= "3.5" -astunparse==1.6.3 async-generator==1.10; python_full_version >= "3.6.1" and python_version >= "3.6" async-timeout==3.0.1; python_full_version >= "3.5.3" and python_version >= "3.6" attrs==20.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" backcall==0.2.0; python_version >= "3.6" -beautifulsoup4==4.9.3 +beautifulsoup4==4.9.3; python_version >= "3.5" black==20.8b1; python_version >= "3.6" bleach==3.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -bpemb==0.3.2; python_version >= "3.6" bs4==0.0.1 -cachetools==4.2.1; python_version >= "3.5" and python_version < "4.0" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") certifi==2020.12.5; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" cffi==1.14.5; implementation_name == "pypy" and python_version >= "3.6" chardet==4.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -click==7.1.2; python_full_version >= "3.6.0" and python_version >= "3.6" -cloudpickle==1.6.0; python_version >= "3.6" -cmdstanpy==0.4.0 +click==7.1.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" colorama==0.4.4; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -convertdate==2.3.1; python_version >= "3.5" +convertdate==2.3.2; python_version >= "3.5" +cryptography==3.4.6; python_version >= "3.6" cssselect==1.1.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" +cvxpy==1.1.12; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" cycler==0.10.0; python_version >= "3.6" -cython==0.29.14; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" -dataclasses==0.8; python_version >= "3.6" and python_version < "3.7" and python_full_version >= "3.6.2" +cython==0.29.17; python_version >= "2.6" and python_full_version < "3.0.0" and sys_platform == "darwin" or python_full_version >= "3.3.0" and sys_platform == "darwin" +dataclasses==0.8; python_version >= "3.6" and python_version < "3.7" +datetime==4.3; python_version >= "3.5" decorator==4.4.2; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" and python_version >= "3.6") defusedxml==0.7.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -deprecated==1.2.11; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -detecta==0.0.5 +detecta==0.0.5; python_version >= "3.6" +ecos==2.0.7.post1; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" entrypoints==0.3; python_version >= "3.6" -ephem==3.7.7.1; python_version >= "2.7" and python_version < "4" -fbprophet==0.6 -filelock==3.0.12; python_full_version >= "3.6.0" and python_version >= "3.6" finviz==1.3.4 -finvizfinance==0.9.3 -flair==0.7; python_version >= "3.6" -flatbuffers==1.12 +finvizfinance==0.9.3; python_version >= "3.5" fredapi==0.4.3 -ftfy==5.9; python_version >= "3.6" fundamentalanalysis==0.2.6 -future==0.18.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" -gast==0.3.3; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" -gdown==3.12.2; python_version >= "3.6" -gensim==3.8.3; python_version >= "3.6" -google-auth-oauthlib==0.4.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" and python_version >= "3.6" -google-auth==1.27.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -google-pasta==0.2.0 -grpcio==1.32.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" h5py==2.10.0 hijri-converter==2.1.1; python_version >= "3.6" holidays==0.10.5.2 -hyperopt==0.2.5; python_version >= "3.6" idna-ssl==1.1.0; python_version < "3.7" and python_version >= "3.6" idna==2.10; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version < "3.7" and python_version >= "3.6" and python_full_version >= "3.5.0" -importlib-metadata==3.7.2; python_version < "3.8" and python_version >= "3.6" and python_full_version >= "3.6.1" and python_full_version < "4.0.0" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.2.0" and python_version >= "3.6" and python_version < "3.8") +importlib-metadata==3.7.3; python_version < "3.8" and python_version >= "3.6" inflection==0.5.1; python_version >= "3.5" ipykernel==5.5.0; python_version >= "3.6" ipython-genutils==0.2.0; python_full_version >= "3.6.1" and python_version >= "3.6" ipython==7.16.1; python_version >= "3.6" ipywidgets==7.6.3 iso8601==0.1.14 -janome==0.4.1; python_version >= "3.6" jedi==0.18.0; python_version >= "3.6" jinja2==2.11.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -joblib==1.0.1; python_version >= "3.6" and python_full_version >= "3.6.0" jsonschema==3.2.0; python_version >= "3.6" -jupyter-client==6.1.11; python_full_version >= "3.6.1" and python_version >= "3.6" -jupyter-console==6.2.0; python_version >= "3.6" +jupyter-client==6.1.12; python_full_version >= "3.6.1" and python_version >= "3.6" +jupyter-console==6.3.0; python_version >= "3.6" jupyter-core==4.7.1; python_full_version >= "3.6.1" and python_version >= "3.6" jupyter==1.0.0 jupyterlab-pygments==0.1.2; python_version >= "3.6" jupyterlab-widgets==1.0.0; python_version >= "3.6" -keras-preprocessing==1.1.2 kiwisolver==1.3.1; python_version >= "3.6" -konoha==4.6.4; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.6" korean-lunar-calendar==0.2.1 -langdetect==1.0.8; python_version >= "3.6" -lunarcalendar==0.0.9; python_version >= "2.7" and python_version < "4" -lxml==4.6.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -markdown==3.3.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" and python_version >= "3.6" +lxml==4.6.2; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" markupsafe==1.1.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" matplotlib==3.3.4; python_version >= "3.6" mistune==0.8.4; python_version >= "3.6" more-itertools==8.7.0; python_version >= "3.5" -mpld3==0.3; python_version >= "3.6" -mplfinance==0.12.7a7 +mplfinance==0.12.7a10 +msgpack==1.0.2 multidict==5.1.0; python_version >= "3.6" multitasking==0.0.9 mypy-extensions==0.4.3; python_version >= "3.6" @@ -97,109 +72,88 @@ nbclient==0.5.3; python_full_version >= "3.6.1" and python_version >= "3.6" nbconvert==6.0.7; python_version >= "3.6" nbformat==5.1.2; python_full_version >= "3.6.1" and python_version >= "3.6" nest-asyncio==1.5.1; python_full_version >= "3.6.1" and python_version >= "3.6" -networkx==2.5; python_version >= "3.6" notebook==6.2.0; python_version >= "3.5" -numpy==1.19.5; python_version >= "3.6" and python_full_version >= "3.6.2" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" and python_version >= "3.6") -oandapyV20==0.6.3; python_version >= "3.6" -oauthlib==3.1.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -opt-einsum==3.3.0; python_version >= "3.5" -overrides==3.1.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.6" -packaging==20.9; python_full_version >= "3.6.0" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") +numpy==1.19.5; python_version >= "3.6" +oauthlib==3.1.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" +osqp==0.6.2.post0; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" +packaging==20.9; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" pandas-ta==0.2.45b pandas==1.1.5; python_full_version >= "3.6.1" pandocfilters==1.4.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" papermill==2.3.3; python_version >= "3.6" parso==0.8.1; python_version >= "3.6" pathspec==0.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -patsy==0.5.1; python_version >= "3.6" pexpect==4.8.0; sys_platform != "win32" and python_version >= "3.6" pickleshare==0.7.5; python_version >= "3.6" -pillow==8.1.2; python_version >= "3.6" +pillow==8.2.0; python_version >= "3.6" plotly==4.14.3 -pmdarima==1.8.0; python_version >= "3.6" praw==7.2.0; python_version >= "3.6" and python_version < "4.0" prawcore==2.0.0; python_version >= "3.6" and python_version < "4.0" prettytable==2.1.0; python_version >= "3.6" prometheus-client==0.9.0; python_version >= "3.5" prompt-toolkit==3.0.17; python_full_version >= "3.6.1" -protobuf==3.15.6; python_full_version >= "3.6.0" and python_version >= "3.6" psaw==0.0.12; python_version >= "3" ptyprocess==0.7.0; os_name != "nt" and python_version >= "3.6" and sys_platform != "win32" py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" and implementation_name == "pypy" or implementation_name == "pypy" and python_version >= "3.6" and python_full_version >= "3.4.0" pyally==1.1.2 -pyasn1-modules==0.2.8; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" -pyasn1==0.4.8; python_version >= "3.5" and python_full_version < "3.0.0" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") or python_version >= "3.5" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") and python_full_version >= "3.6.0" pycoingecko==1.4.0 -pycparser==2.20; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" +pycparser==2.20; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" pygments==2.8.1; python_version >= "3.6" -pymeeus==0.5.9; python_version >= "3.5" -pyparsing==2.4.7; python_full_version >= "3.6.0" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") -pyportfolioopt==1.4.1 -pyrsistent==0.17.3; python_version >= "3.6" +pymeeus==0.5.11; python_version >= "3.5" +pyobjc-core==7.1; python_version >= "3.6" and sys_platform == "darwin" +pyobjc-framework-cocoa==7.1; python_version >= "3.6" and sys_platform == "darwin" +pyotp==2.6.0; python_version >= "3" +pyparsing==2.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +pyportfolioopt==1.4.1; python_full_version >= "3.6.1" and python_full_version < "4.0.0" +pyrsistent==0.14.11 pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -pystan==2.19.1.1 -python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "2.7" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "2.7" and python_version < "4" and python_full_version >= "3.3.0") +python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") +python-dotenv==0.15.0; python_version >= "3" pytrends==4.7.3 -pytz==2021.1; python_full_version >= "3.6.1" and python_version >= "3.5" and python_version < "4" +pytz==2021.1; python_full_version >= "3.6.1" and python_version >= "3.5" pywin32==300; sys_platform == "win32" and python_version >= "3.6" pywinpty==0.5.7; os_name == "nt" and python_version >= "3.6" pyyaml==5.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" pyzmq==22.0.3; python_full_version >= "3.6.1" and python_version >= "3.6" -qtconsole==5.0.2; python_version >= "3.6" +qdldl==0.1.5.post0; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" +qtconsole==5.0.3; python_version >= "3.6" qtpy==1.9.0; python_version >= "3.6" quandl==3.6.1; python_version >= "3.5" -rapidfuzz==1.2.1; python_version >= "2.7" -regex==2020.11.13; python_full_version >= "3.6.0" and python_version >= "3.6" -requests-oauthlib==1.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" +rapidfuzz==1.3.0; python_version >= "3.5" +regex==2020.11.13 +requests-oauthlib==1.3.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") retrying==1.3.3 -robin-stocks==2.0.3 -rsa==4.7.2; python_version >= "3.5" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") -sacremoses==0.0.43; python_full_version >= "3.6.0" and python_version >= "3.6" -scikit-learn==0.24.1; python_version >= "3.6" +robin-stocks==2.0.3; python_version >= "3" scipy==1.5.4; python_version >= "3.6" +screeninfo==0.6.7 +scs==2.1.2; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" seaborn==0.11.1; python_version >= "3.6" -segtok==1.5.10; python_version >= "3.6" send2trash==1.5.0; python_version >= "3.5" -sentencepiece==0.1.91; python_full_version >= "3.6.0" and python_version >= "3.6" -setuptools-git==1.2 -six==1.15.0; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") and (python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.5") -smart-open==4.2.0; python_version >= "3.6" -soupsieve==2.2; python_version >= "3.6" -sqlitedict==1.7.0; python_version >= "3.6" -statsmodels==0.12.2; python_version >= "3.6" -screeninfo==0.6.7; -tabulate==0.8.9; python_version >= "3.6" +six==1.15.0; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") and (python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.5") +soupsieve==2.2.1; python_version >= "3.6" tenacity==7.0.0; python_version >= "3.6" -tensorboard-plugin-wit==1.8.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" -tensorboard==2.4.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" -tensorflow-estimator==2.4.0 -tensorflow==2.4.1 termcolor==1.1.0 -terminado==0.9.2; python_version >= "3.6" +terminado==0.9.3; python_version >= "3.6" testpath==0.4.4; python_version >= "3.6" textwrap3==0.9.2; python_version >= "3.6" -threadpoolctl==2.1.0; python_version >= "3.6" timeseriescrossvalidation @ git+https://github.com/DidierRLopes/TimeSeriesCrossValidation.git@master -tokenizers==0.9.3; python_full_version >= "3.6.0" and python_version >= "3.6" toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" -torch==1.7.1; python_full_version >= "3.6.2" tornado==6.1; python_full_version >= "3.6.1" and python_version >= "3.6" -tqdm==4.59.0; python_full_version >= "3.6.0" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6") +tqdm==4.59.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" +tradingview-ta==3.2.3; python_version >= "3.5" traitlets==4.3.3; python_full_version >= "3.6.1" and python_version >= "3.6" -tradingview-ta==3.2.3 -transformers==3.5.1; python_full_version >= "3.6.0" typed-ast==1.4.2; python_version >= "3.6" -typing-extensions==3.7.4.3; python_full_version >= "3.6.2" and python_version >= "3.6" and python_version < "3.8" +typing-extensions==3.7.4.3; python_version < "3.8" and python_version >= "3.6" update-checker==0.18.0; python_version >= "3.6" and python_version < "4.0" -urllib3==1.26.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.6" +urllib3==1.26.4; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" user-agent==0.1.9 wcwidth==0.2.5; python_version >= "3.6" and python_full_version >= "3.6.1" webencodings==0.5.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" websocket-client==0.58.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" -werkzeug==1.0.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +websockets==8.1; python_full_version >= "3.6.1" widgetsnbextension==3.5.1 -wrapt==1.12.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" yarl==1.6.3; python_version >= "3.6" yfinance==0.1.55 zipp==3.4.1; python_version < "3.8" and python_version >= "3.6" +zope.interface==5.3.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" diff --git a/requirements.txt b/requirements.txt index 4f4ba01310a..29b46fbad43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,7 +9,7 @@ async-generator==1.10; python_full_version >= "3.6.1" and python_version >= "3.6 async-timeout==3.0.1; python_full_version >= "3.5.3" and python_version >= "3.6" attrs==20.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" backcall==0.2.0; python_version >= "3.6" -beautifulsoup4==4.9.3 +beautifulsoup4==4.9.3; python_version >= "3.5" black==20.8b1; python_version >= "3.6" bleach==3.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" bs4==0.0.1 @@ -18,23 +18,29 @@ cffi==1.14.5; implementation_name == "pypy" and python_version >= "3.6" chardet==4.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" click==7.1.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" colorama==0.4.4; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") -convertdate==2.3.1; python_version >= "3.5" +convertdate==2.3.2; python_version >= "3.5" +cryptography==3.4.6; python_version >= "3.6" cssselect==1.1.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" +cvxpy==1.1.12; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" cycler==0.10.0; python_version >= "3.6" +cython==0.29.17; python_version >= "2.6" and python_full_version < "3.0.0" and sys_platform == "darwin" or python_full_version >= "3.3.0" and sys_platform == "darwin" dataclasses==0.8; python_version >= "3.6" and python_version < "3.7" +datetime==4.3; python_version >= "3.5" decorator==4.4.2; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" and python_version >= "3.6") defusedxml==0.7.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -detecta==0.0.5 +detecta==0.0.5; python_version >= "3.6" +ecos==2.0.7.post1; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" entrypoints==0.3; python_version >= "3.6" finviz==1.3.4 -finvizfinance==0.9.3 +finvizfinance==0.9.3; python_version >= "3.5" fredapi==0.4.3 fundamentalanalysis==0.2.6 +h5py==2.10.0 hijri-converter==2.1.1; python_version >= "3.6" holidays==0.10.5.2 idna-ssl==1.1.0; python_version < "3.7" and python_version >= "3.6" idna==2.10; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version < "3.7" and python_version >= "3.6" and python_full_version >= "3.5.0" -importlib-metadata==3.7.2; python_version < "3.8" and python_version >= "3.6" +importlib-metadata==3.7.3; python_version < "3.8" and python_version >= "3.6" inflection==0.5.1; python_version >= "3.5" ipykernel==5.5.0; python_version >= "3.6" ipython-genutils==0.2.0; python_full_version >= "3.6.1" and python_version >= "3.6" @@ -44,20 +50,21 @@ iso8601==0.1.14 jedi==0.18.0; python_version >= "3.6" jinja2==2.11.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" jsonschema==3.2.0; python_version >= "3.6" -jupyter-client==6.1.11; python_full_version >= "3.6.1" and python_version >= "3.6" -jupyter-console==6.2.0; python_version >= "3.6" +jupyter-client==6.1.12; python_full_version >= "3.6.1" and python_version >= "3.6" +jupyter-console==6.3.0; python_version >= "3.6" jupyter-core==4.7.1; python_full_version >= "3.6.1" and python_version >= "3.6" jupyter==1.0.0 jupyterlab-pygments==0.1.2; python_version >= "3.6" jupyterlab-widgets==1.0.0; python_version >= "3.6" kiwisolver==1.3.1; python_version >= "3.6" korean-lunar-calendar==0.2.1 -lxml==4.6.2; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" +lxml==4.6.2; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" markupsafe==1.1.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" matplotlib==3.3.4; python_version >= "3.6" mistune==0.8.4; python_version >= "3.6" more-itertools==8.7.0; python_version >= "3.5" -mplfinance==0.12.7a7 +mplfinance==0.12.7a10 +msgpack==1.0.2 multidict==5.1.0; python_version >= "3.6" multitasking==0.0.9 mypy-extensions==0.4.3; python_version >= "3.6" @@ -66,7 +73,9 @@ nbconvert==6.0.7; python_version >= "3.6" nbformat==5.1.2; python_full_version >= "3.6.1" and python_version >= "3.6" nest-asyncio==1.5.1; python_full_version >= "3.6.1" and python_version >= "3.6" notebook==6.2.0; python_version >= "3.5" -numpy==1.19.5; python_version >= "3.6" and python_full_version >= "3.6.1" +numpy==1.19.5; python_version >= "3.6" +oauthlib==3.1.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" +osqp==0.6.2.post0; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" packaging==20.9; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" pandas-ta==0.2.45b pandas==1.1.5; python_full_version >= "3.6.1" @@ -76,7 +85,7 @@ parso==0.8.1; python_version >= "3.6" pathspec==0.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" pexpect==4.8.0; sys_platform != "win32" and python_version >= "3.6" pickleshare==0.7.5; python_version >= "3.6" -pillow==8.1.2; python_version >= "3.6" +pillow==8.2.0; python_version >= "3.6" plotly==4.14.3 praw==7.2.0; python_version >= "3.6" and python_version < "4.0" prawcore==2.0.0; python_version >= "3.6" and python_version < "4.0" @@ -88,54 +97,63 @@ ptyprocess==0.7.0; os_name != "nt" and python_version >= "3.6" and sys_platform py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" and implementation_name == "pypy" or implementation_name == "pypy" and python_version >= "3.6" and python_full_version >= "3.4.0" pyally==1.1.2 pycoingecko==1.4.0 -pycparser==2.20; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.5" +pycparser==2.20; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" pygments==2.8.1; python_version >= "3.6" -pymeeus==0.5.9; python_version >= "3.5" +pymeeus==0.5.11; python_version >= "3.5" +pyobjc-core==7.1; python_version >= "3.6" and sys_platform == "darwin" +pyobjc-framework-cocoa==7.1; python_version >= "3.6" and sys_platform == "darwin" +pyotp==2.6.0; python_version >= "3" pyparsing==2.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -pyportfolioopt==1.4.1 -pyrsistent==0.17.3; python_version >= "3.6" +pyportfolioopt==1.4.1; python_full_version >= "3.6.1" and python_full_version < "4.0.0" +pyrsistent==0.14.11 pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") +python-dotenv==0.15.0; python_version >= "3" pytrends==4.7.3 pytz==2021.1; python_full_version >= "3.6.1" and python_version >= "3.5" pywin32==300; sys_platform == "win32" and python_version >= "3.6" pywinpty==0.5.7; os_name == "nt" and python_version >= "3.6" pyyaml==5.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" pyzmq==22.0.3; python_full_version >= "3.6.1" and python_version >= "3.6" -qtconsole==5.0.2; python_version >= "3.6" +qdldl==0.1.5.post0; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" +qtconsole==5.0.3; python_version >= "3.6" qtpy==1.9.0; python_version >= "3.6" quandl==3.6.1; python_version >= "3.5" -rapidfuzz==1.2.1; python_version >= "2.7" -regex==2020.11.13; python_version >= "3.6" +rapidfuzz==1.3.0; python_version >= "3.5" +regex==2020.11.13 +requests-oauthlib==1.3.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") retrying==1.3.3 -robin-stocks==2.0.3 +robin-stocks==2.0.3; python_version >= "3" scipy==1.5.4; python_version >= "3.6" +screeninfo==0.6.7 +scs==2.1.2; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" seaborn==0.11.1; python_version >= "3.6" send2trash==1.5.0; python_version >= "3.5" six==1.15.0; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") and (python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.5") -soupsieve==2.2; python_version >= "3.6" -screeninfo==0.6.7; -statsmodels==0.12.2; python_version >= "3.6" +soupsieve==2.2.1; python_version >= "3.6" tenacity==7.0.0; python_version >= "3.6" -terminado==0.9.2; python_version >= "3.6" +termcolor==1.1.0 +terminado==0.9.3; python_version >= "3.6" testpath==0.4.4; python_version >= "3.6" textwrap3==0.9.2; python_version >= "3.6" timeseriescrossvalidation @ git+https://github.com/DidierRLopes/TimeSeriesCrossValidation.git@master toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" tornado==6.1; python_full_version >= "3.6.1" and python_version >= "3.6" tqdm==4.59.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" -tradingview-ta==3.2.3 +tradingview-ta==3.2.3; python_version >= "3.5" traitlets==4.3.3; python_full_version >= "3.6.1" and python_version >= "3.6" typed-ast==1.4.2; python_version >= "3.6" typing-extensions==3.7.4.3; python_version < "3.8" and python_version >= "3.6" update-checker==0.18.0; python_version >= "3.6" and python_version < "4.0" -urllib3==1.26.3; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" +urllib3==1.26.4; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" user-agent==0.1.9 wcwidth==0.2.5; python_version >= "3.6" and python_full_version >= "3.6.1" webencodings==0.5.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" websocket-client==0.58.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" +websockets==8.1; python_full_version >= "3.6.1" widgetsnbextension==3.5.1 yarl==1.6.3; python_version >= "3.6" yfinance==0.1.55 zipp==3.4.1; python_version < "3.8" and python_version >= "3.6" +zope.interface==5.3.0; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" From 076b7698ab99a30e217881d99f4d7b09a3c0d49d Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Sat, 17 Apr 2021 13:01:28 -0600 Subject: [PATCH 33/54] added fx to main helper menu --- gamestonk_terminal/main_helper.py | 1 + 1 file changed, 1 insertion(+) diff --git a/gamestonk_terminal/main_helper.py b/gamestonk_terminal/main_helper.py index 40c8f6714ba..4b5810fef6c 100644 --- a/gamestonk_terminal/main_helper.py +++ b/gamestonk_terminal/main_helper.py @@ -63,6 +63,7 @@ def print_help(s_ticker, s_start, s_interval, b_is_market_open): print( " po portfolio optimization, \t\t optimal portfolio weights from pyportfolioopt" ) + print(" fx forex menu, \t\t\t forex support through Oanda") if s_ticker: print( From 1f80048d4f20328cede40b80c8761a428a310420 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Sat, 17 Apr 2021 13:02:01 -0600 Subject: [PATCH 34/54] renamed oanda_controller to fx_controller --- gamestonk_terminal/forex/fx_controller.py | 177 ++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 gamestonk_terminal/forex/fx_controller.py diff --git a/gamestonk_terminal/forex/fx_controller.py b/gamestonk_terminal/forex/fx_controller.py new file mode 100644 index 00000000000..d867fe0b476 --- /dev/null +++ b/gamestonk_terminal/forex/fx_controller.py @@ -0,0 +1,177 @@ +import argparse +from typing import List +from prompt_toolkit.completion import NestedCompleter +from gamestonk_terminal.helper_funcs import get_flair +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.menu import session +from gamestonk_terminal.forex import fx_view +from gamestonk_terminal import config_terminal as cfg + +account = cfg.OANDA_ACCOUNT + + +class ForexController: + """Oanda Controller class""" + + CHOICES = [ + "help", + "q", + "quit", + "price", + "summary", + "list", + "orderbook", + "positionbook", + "order", + "load", + "cancel", + "positions", + "closetrade", + "trades", + "candles", + "pending", + "calendar", + ] + + def __init__(self): + """Construct Data""" + self.fx_parser = argparse.ArgumentParser(add_help=False, prog="fx") + self.fx_parser.add_argument( + "cmd", + choices=self.CHOICES, + ) + self.instrument = None + + @staticmethod + def print_help(): + """Print help""" + + print("\nForex Mode:") + print(" help Show this menu again") + print(" q Quit this menu and goes back to main menu") + print(" quit Quit to abandon program") + print("") + print(" cancel Cancel a pending order by ID -i order ID") + print(" candles Show candles") + print(" calendar Show calendar") + print(" closetrade Close a trade by id") + print(" list List order history") + print(" load Load an instrument to use") + print(" order Place limit order -u # of units -p price") + print(" orderbook Print orderbook") + print(" pending Get information on pending orders") + print(" positionbook Print positionbook") + print(" positions Get open positions") + print(" price Shows price for selected instrument") + print(" summary Shows account summary") + print(" trades List open trades") + print("") + + 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 + """ + (known_args, other_args) = self.fx_parser.parse_known_args(an_input.split()) + + 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 - exit the program""" + return True + + def call_price(self, _): + """Process Price Command""" + fx_view.get_fx_price(account, self.instrument) + + def call_load(self, other_args): + self.instrument = fx_view.load(other_args) + + def call_summary(self, _): + """Process account summary command""" + fx_view.get_account_summary(account) + + def call_orderbook(self, _): + """Process Oanda Order Book""" + fx_view.get_order_book(self.instrument) + + def call_positionbook(self, _): + """Process Oanda Position Book""" + fx_view.get_position_book(self.instrument) + + def call_list(self, other_args: List[str]): + """Process list orders command""" + fx_view.list_orders(account, other_args) + + def call_order(self, other_args: List[str]): + """Place limit order""" + fx_view.create_order(account, self.instrument, other_args) + + def call_cancel(self, other_args: List[str]): + """Cancel pending order by ID""" + fx_view.cancel_pending_order(account, other_args) + + def call_positions(self, _): + """Get Open Positions""" + fx_view.get_open_positions(account) + + def call_pending(self, _): + """See up to 25 pending orders""" + fx_view.get_pending_orders(account) + + def call_closetrade(self, other_args: List[str]): + """Close a trade by id""" + fx_view.close_trade(account, other_args) + + def call_candles(self, other_args: List[str]): + fx_view.show_candles(account, self.instrument, other_args) + + def call_trades(self, _): + """List open trades""" + fx_view.get_open_trades(account) + + def call_calendar(self, other_args: List[str]): + """Call calendar""" + fx_view.calendar(self.instrument, other_args) + + +def menu(): + """Oanda Menu""" + fx_controller = ForexController() + fx_controller.call_help(None) + while True: + if session and gtff.USE_PROMPT_TOOLKIT: + completer = NestedCompleter.from_nested_dict( + {c: None for c in fx_controller.CHOICES} + ) + + an_input = session.prompt( + f"{get_flair()} (fx)> ", + completer=completer, + ) + else: + an_input = input(f"{get_flair()} (fx)> ") + + try: + process_input = fx_controller.switch(an_input) + + if process_input is not None: + return process_input + + except SystemExit: + print("The command selected doesn't exit\n") + continue From 20b54438b0c209b12f7516b1bfc7788dd8c45a10 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Sat, 17 Apr 2021 13:03:21 -0600 Subject: [PATCH 35/54] renamed oanda_functions to fx_view --- gamestonk_terminal/forex/__init.py__ | 0 gamestonk_terminal/forex/fx_view.py | 535 +++++++++++++++++++++++++++ 2 files changed, 535 insertions(+) create mode 100644 gamestonk_terminal/forex/__init.py__ create mode 100644 gamestonk_terminal/forex/fx_view.py diff --git a/gamestonk_terminal/forex/__init.py__ b/gamestonk_terminal/forex/__init.py__ new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gamestonk_terminal/forex/fx_view.py b/gamestonk_terminal/forex/fx_view.py new file mode 100644 index 00000000000..bba28eef5b4 --- /dev/null +++ b/gamestonk_terminal/forex/fx_view.py @@ -0,0 +1,535 @@ +import argparse +from oandapyV20 import API +from typing import List +import oandapyV20.endpoints.pricing as pricing +import oandapyV20.endpoints.accounts as accounts +import oandapyV20.endpoints.orders as orders +import oandapyV20.endpoints.instruments as instruments +import oandapyV20.endpoints.positions as positions +import oandapyV20.endpoints.trades as trades +import oandapyV20.endpoints.forexlabs as labs +from oandapyV20.exceptions import V20Error +from gamestonk_terminal import config_terminal as cfg +from gamestonk_terminal import config_plot as cfgPlot +from gamestonk_terminal import feature_flags as gtff +from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, plot_autoscale +import pandas as pd +import mplfinance as mpf +import matplotlib.pyplot as plt +import matplotlib.ticker as mticker +import seaborn as sns +from datetime import datetime + + +client = API(access_token=cfg.OANDA_TOKEN, environment="live") +account = cfg.OANDA_ACCOUNT + + +def get_fx_price(accountID, instrument): + 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) + except V20Error as e: + print(e) + + +def get_account_summary(accountID): + try: + request = accounts.AccountSummary(accountID=accountID) + response = client.request(request) + balance = response["account"]["balance"] + margin_available = response["account"]["marginAvailable"] + margin_closeout = response["account"]["marginCloseoutNAV"] + margin_closeout_percent = response["account"]["marginCloseoutPercent"] + margin_closeout_position_value = response["account"]["marginCloseoutPositionValue"] + margin_used = response["account"]["marginUsed"] + net_asset_value = response["account"]["NAV"] + open_trade_count = response["account"]["openTradeCount"] + total_pl = response["account"]["pl"] + unrealized_pl = response["account"]["unrealizedPL"] + + print(f"Balance: {balance}") + print(f"NAV: {net_asset_value}") + print(f"Unrealized P/L: {unrealized_pl}") + print(f"Total P/L: {total_pl}") + print(f"Open Trade Count: {open_trade_count}") + print(f"Margin Available: ${margin_available}") + print(f"Margin Used: ${margin_used}") + print(f"Margin Closeout {margin_closeout}") + print(f"Margin Closeout Percent: {margin_closeout_percent}") + print(f"Margin Closeout Position Value: {margin_closeout_position_value}") + except V20Error as e: + print(e) + + +def list_orders(accountID, other_args: List[str]): + parser = argparse.ArgumentParser( + add_help=False, + prog="listorders", + description="List order history", + ) + parser.add_argument( + "-s", "--state", dest="state", action="store", default="ALL", required=False + ) + parser.add_argument( + "-c", "--count", dest="count", action="store", default=50, required=False + ) + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + parameters = {} + parameters["state"] = ns_parser.state + 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) + except V20Error as e: + print(e) + + +def get_order_book(instrument): + parameters = {"bucketWidth": "1"} + try: + 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") + except V20Error as e: + print(e) + + +def get_position_book(instrument): + try: + 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") + except V20Error as e: + print(e) + + +def create_order(accountID, instrument, other_args: List[str]): + parser = argparse.ArgumentParser( + add_help=False, + prog="create_order", + description="Create order", + ) + parser.add_argument( + "-u", + "--unit", + dest="units", + action="store", + type=int, + default=0, + required=True, + ) + parser.add_argument( + "-p", + "--price", + dest="price", + action="store", + type=float, + required=True, + ) + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + 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) + print(response) + except V20Error as e: + print(e) + + +def cancel_pending_order(accountID, other_args: List[str]): + parser = argparse.ArgumentParser( + add_help=False, + prog="cancelpendingorder", + description="Cancel Pending Order", + ) + parser.add_argument( + "-i", + "--id", + dest="orderID", + action="store", + type=str, + required=True, + ) + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + try: + request = orders.OrderCancel(accountID, ns_parser.orderID) + client.request(request) + except V20Error as e: + print(e) + + +def get_pending_orders(accountID): + try: + request = orders.OrdersPending(accountID) + response = client.request(request) + for i in range(len(response["orders"])): + try: + order_id = response["orders"][i]["id"] + instrument = response["orders"][i]["instrument"] + price = response["orders"][i]["price"] + units = response["orders"][i]["units"] + create_time = response["orders"][i]["createTime"] + time_in_force = response["orders"][i]["timeInForce"] + print(f"Order ID: {order_id}") + print(f"Instrument: {instrument}") + print(f"Price: {price}") + print(f"Units: {units}") + print(f"Time created: {create_time}") + print(f"Time in force: {time_in_force}") + print("-" * 30) + except IndexError: + break + except V20Error as e: + print(e) + + +def get_open_positions(accountID): + try: + request = positions.OpenPositions(accountID) + response = client.request(request) + for i in range(len(response["positions"])): + instrument = response["positions"][i]["instrument"] + long_units = response["positions"][i]["long"]["units"] + long_pl = response["positions"][i]["long"]["pl"] + long_upl = response["positions"][i]["long"]["unrealizedPL"] + short_units = response["positions"][i]["short"]["units"] + short_pl = response["positions"][i]["short"]["pl"] + short_upl = response["positions"][i]["short"]["unrealizedPL"] + print(f"Instrument: {instrument}\n") + print(f"Long Units: {long_units}") + print(f"Total Long P/L: {long_pl}") + print(f"Long Unrealized P/L: {long_upl}\n") + print(f"Short Units: {short_units}") + print(f"Total Short P/L: {short_pl}") + print(f"Short Unrealized P/L: {short_upl}") + print("-" * 30 + "\n") + except V20Error as e: + print(e) + + +def get_open_trades(accountID): + try: + request = trades.OpenTrades(accountID) + response = client.request(request) + 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", + } + ) + print(df) + except V20Error as e: + print(e) + + +def close_trade(accountID, other_args: List[str]): + parser = argparse.ArgumentParser( + add_help=False, + prog="close_trade", + description="close a trade", + ) + parser.add_argument( + "-i", + "--id", + dest="orderID", + action="store", + type=str, + required=True, + ) + parser.add_argument( + "-u", + "--units", + dest="units", + action="store", + type=str, + required=False, + ) + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + data = { + "units": ns_parser.units, + } + try: + request = trades.TradeClose(accountID, ns_parser.orderID, data) + response = client.request(request) + print(response) + except V20Error as e: + print(e) + + +def show_candles(accountID, instrument, other_args: List[str]): + parser = argparse.ArgumentParser( + add_help=False, + prog="show_candles", + description="Display Candle Data", + ) + parser.add_argument( + "-g", + "--granularity", + dest="granularity", + action="store", + type=str, + default="D", + required=False, + ) + parser.add_argument( + "-c", + "--count", + dest="candlecount", + action="store", + default=180, + type=int, + required=False, + ) + + ns_parser = parse_known_args_and_warn(parser, other_args) + if not ns_parser: + return + + parameters = {} + parameters["granularity"] = ns_parser.granularity + parameters["count"] = ns_parser.candlecount + try: + request = instruments.InstrumentsCandles(instrument, params=parameters) + response = client.request(request) + process_candle_response(response) + oanda_fix_date(".temp_candles.csv") + df = pd.read_csv(".candles.csv", index_col=0) + df.index = pd.to_datetime(df.index) + df.columns = ["Open", "High", "Low", "Close", "Volume"] + if gtff.USE_ION: + plt.ion() + mpf.plot( + df, + type="candle", + style="charles", + volume=True, + title=f"{instrument} {ns_parser.granularity}", + ) + except Exception as e: + print(e) + except NameError as e: + print(e) + + +def process_candle_response(response): + with open(".temp_candles.csv", "w") as out: + for i in range(len(response["candles"])): + time = response["candles"][i]["time"] + volume = response["candles"][i]["volume"] + o = response["candles"][i]["mid"]["o"] + h = response["candles"][i]["mid"]["h"] + low = response["candles"][i]["mid"]["l"] + c = response["candles"][i]["mid"]["c"] + out.write( + str(time) + + "," + + str(o) + + "," + + str(h) + + "," + + str(low) + + "," + + str(c) + + "," + + str(volume) + + "\n" + ) + + +def oanda_fix_date(file): + with open(file) as candle_file: + lines = candle_file.readlines() + with open(".candles.csv", "w") as out: + out.write("Datetime, Open, High, Low, Close, Volume\n") + for line in lines: + with open(".candles.csv", "a") as output: + output.write(line[:10] + " " + line[11:19] + line[30:]) + + +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, + ) + 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 = labs.Calendar(params=parameters) + response = client.request(request) + for i in range(len(response)): + if "title" in response[i]: + title = response[i]["title"] + print(f"Title: {title}") + if "timestamp" in response[i]: + timestamp = response[i]["timestamp"] + time = datetime.fromtimestamp(timestamp) + print(f"Time: {time}") + if "impact" in response[i]: + impact = response[i]["impact"] + print(f"Impact: {impact}") + if "forecast" in response[i]: + forecast = response[i]["forecast"] + unit = response[i]["unit"] + if unit != "Index": + print(f"Forecast: {forecast}{unit}") + else: + print(f"Forecast: {forecast}") + if "market" in response[i]: + market = response[i]["market"] + unit = response[i]["unit"] + if unit != "Index": + print(f"Market Forecast: {market}{unit}") + else: + print(f"Market Forecast: {market}") + if "currency" in response[i]: + currency = response[i]["currency"] + print(f"Currency: {currency}") + if "region" in response[i]: + region = response[i]["region"] + print(f"Region: {region}") + if "actual" in response[i]: + actual = response[i]["actual"] + unit = response[i]["unit"] + if unit != "Index": + print(f"Actual: {actual}{unit}") + else: + print(f"Actual: {actual}") + if "previous" in response[i]: + previous = response[i]["previous"] + unit = response[i]["unit"] + if unit != "Index": + print(f"Previous: {previous}{unit}") + else: + print(f"Previous: {previous}") + print("-" * 30) + except V20Error as e: + print(e) + + +def load(other_args: List[str]): + """Load a forex instrument to use""" + parser = argparse.ArgumentParser( + add_help=False, + prog="Forex", + description="Forex using oanda", + ) + + parser.add_argument( + "-i", + "--instrument", + required=True, + type=str, + dest="instrument", + help="Instrument to use for function calls", + ) + + 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 + return ns_parser.instrument.upper() + except Exception as e: + print(e) + + +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() From 4b7eb26367789bd0a102c5bc666eb1df2fe59129 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Sat, 17 Apr 2021 13:03:41 -0600 Subject: [PATCH 36/54] renamed import --- terminal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terminal.py b/terminal.py index 0e9652ee878..f246ebd00bb 100644 --- a/terminal.py +++ b/terminal.py @@ -32,7 +32,7 @@ from gamestonk_terminal.portfolio import port_controller from gamestonk_terminal.cryptocurrency import crypto_controller from gamestonk_terminal.screener import screener_controller from gamestonk_terminal.portfolio_optimization import po_controller -from gamestonk_terminal.oanda import oanda_controller +from gamestonk_terminal.forex import fx_controller # import warnings @@ -211,7 +211,7 @@ def main(): b_quit = fam.fa_menu(s_ticker, s_start, s_interval) elif ns_known_args.opt == "fx": - b_quit = oanda_controller.menu() + b_quit = fx_controller.menu() elif ns_known_args.opt == "ta": b_quit = ta_controller.menu(df_stock, s_ticker, s_start, s_interval) From 8ff6d662cd652bb29dd0dc731f3fe8639ad780c6 Mon Sep 17 00:00:00 2001 From: DJ Nieto Date: Sat, 17 Apr 2021 13:06:47 -0600 Subject: [PATCH 37/54] ran linter --- gamestonk_terminal/forex/fx_view.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/gamestonk_terminal/forex/fx_view.py b/gamestonk_terminal/forex/fx_view.py index bba28eef5b4..fafa478f9ad 100644 --- a/gamestonk_terminal/forex/fx_view.py +++ b/gamestonk_terminal/forex/fx_view.py @@ -46,7 +46,9 @@ def get_account_summary(accountID): margin_available = response["account"]["marginAvailable"] margin_closeout = response["account"]["marginCloseoutNAV"] margin_closeout_percent = response["account"]["marginCloseoutPercent"] - margin_closeout_position_value = response["account"]["marginCloseoutPositionValue"] + margin_closeout_position_value = response["account"][ + "marginCloseoutPositionValue" + ] margin_used = response["account"]["marginUsed"] net_asset_value = response["account"]["NAV"] open_trade_count = response["account"]["openTradeCount"] @@ -249,7 +251,14 @@ def get_open_trades(accountID): response = client.request(request) df = pd.DataFrame.from_dict(response["trades"]) df = df[ - ["id", "instrument", "initialUnits", "currentUnits", "price", "unrealizedPL"] + [ + "id", + "instrument", + "initialUnits", + "currentUnits", + "price", + "unrealizedPL", + ] ] df = df.rename( columns={ From 59ae4421fd8817a6c3d9419ec29015f3aa5bf6b7 Mon Sep 17 00:00:00 2001 From: DJ Date: Sat, 17 Apr 2021 14:27:49 -0600 Subject: [PATCH 38/54] Delete oanda_controller.py --- gamestonk_terminal/oanda/oanda_controller.py | 176 ------------------- 1 file changed, 176 deletions(-) delete mode 100644 gamestonk_terminal/oanda/oanda_controller.py diff --git a/gamestonk_terminal/oanda/oanda_controller.py b/gamestonk_terminal/oanda/oanda_controller.py deleted file mode 100644 index ec59bf4591c..00000000000 --- a/gamestonk_terminal/oanda/oanda_controller.py +++ /dev/null @@ -1,176 +0,0 @@ -import argparse -from typing import List -from prompt_toolkit.completion import NestedCompleter -from gamestonk_terminal.helper_funcs import get_flair -from gamestonk_terminal import feature_flags as gtff -from gamestonk_terminal.menu import session -from gamestonk_terminal.oanda import oanda_functions -from gamestonk_terminal import config_terminal as cfg - -account = cfg.OANDA_ACCOUNT - - -class OandaController: - """Oanda Controller class""" - - CHOICES = [ - "help", - "q", - "quit", - "price", - "summary", - "list", - "orderbook", - "positionbook", - "order", - "load", - "cancel", - "positions", - "closetrade", - "trades", - "candles", - "pending", - "calendar", - ] - - def __init__(self): - """Construct Data""" - self.oanda_parser = argparse.ArgumentParser(add_help=False, prog="fx") - self.oanda_parser.add_argument( - "cmd", - choices=self.CHOICES, - ) - self.instrument = None - - @staticmethod - def print_help(): - """Print help""" - - print("\nForex Mode:") - print(" help Show this menu again") - print(" q Quit this menu and goes back to main menu") - print(" quit Quit to abandon program") - print("") - print(" cancel Cancel a pending order by ID -i order ID") - print(" candles Show candles") - print(" calendar Show calendar") - print(" closetrade Close a trade by id") - print(" list List order history") - print(" load Load an instrument to use") - print(" order Place limit order -u # of units -p price") - print(" orderbook Print orderbook") - print(" pending Get information on pending orders") - print(" positionbook Print positionbook") - print(" positions Get open positions") - print(" price Shows price for selected instrument") - print(" summary Shows account summary") - print(" trades List open trades") - - 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 - """ - (known_args, other_args) = self.oanda_parser.parse_known_args(an_input.split()) - - 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 - exit the program""" - return True - - def call_price(self, _): - """Process Price Command""" - oanda_functions.get_fx_price(account, self.instrument) - - def call_load(self, other_args): - self.instrument = oanda_functions.load(other_args) - - def call_summary(self, _): - """Process account summary command""" - oanda_functions.get_account_summary(account) - - def call_orderbook(self, _): - """Process Oanda Order Book""" - oanda_functions.get_order_book(self.instrument) - - def call_positionbook(self, _): - """Process Oanda Position Book""" - oanda_functions.get_position_book(self.instrument) - - def call_list(self, other_args: List[str]): - """Process list orders command""" - oanda_functions.list_orders(account, other_args) - - def call_order(self, other_args: List[str]): - """Place limit order""" - oanda_functions.create_order(account, self.instrument, other_args) - - def call_cancel(self, other_args: List[str]): - """Cancel pending order by ID""" - oanda_functions.cancel_pending_order(account, other_args) - - def call_positions(self, _): - """Get Open Positions""" - oanda_functions.get_open_positions(account) - - def call_pending(self, _): - """See up to 25 pending orders""" - oanda_functions.get_pending_orders(account) - - def call_closetrade(self, other_args: List[str]): - """Close a trade by id""" - oanda_functions.close_trade(account, other_args) - - def call_candles(self, other_args: List[str]): - oanda_functions.show_candles(account, self.instrument, other_args) - - def call_trades(self, _): - """List open trades""" - oanda_functions.get_open_trades(account) - - def call_calendar(self, other_args: List[str]): - """Call calendar""" - oanda_functions.calendar(self.instrument, other_args) - - -def menu(): - """Oanda 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()} (fx)> ", - completer=completer, - ) - else: - an_input = input(f"{get_flair()} (fx)> ") - - 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 From c7211e8eaa03d1c805c789c69547a4e91c13f807 Mon Sep 17 00:00:00 2001 From: DJ Date: Sat, 17 Apr 2021 14:29:28 -0600 Subject: [PATCH 39/54] Delete oanda_functions.py --- gamestonk_terminal/oanda/oanda_functions.py | 517 -------------------- 1 file changed, 517 deletions(-) delete mode 100644 gamestonk_terminal/oanda/oanda_functions.py diff --git a/gamestonk_terminal/oanda/oanda_functions.py b/gamestonk_terminal/oanda/oanda_functions.py deleted file mode 100644 index ff092321cf2..00000000000 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ /dev/null @@ -1,517 +0,0 @@ -import argparse -from oandapyV20 import API -from typing import List -import oandapyV20.endpoints.pricing as pricing -import oandapyV20.endpoints.accounts as accounts -import oandapyV20.endpoints.orders as orders -import oandapyV20.endpoints.instruments as instruments -import oandapyV20.endpoints.positions as positions -import oandapyV20.endpoints.trades as trades -import oandapyV20.endpoints.forexlabs as labs -from oandapyV20.exceptions import V20Error -from gamestonk_terminal import config_terminal as cfg -from gamestonk_terminal import config_plot as cfgPlot -from gamestonk_terminal import feature_flags as gtff -from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, plot_autoscale -import pandas as pd -import mplfinance as mpf -import matplotlib.pyplot as plt -import matplotlib.ticker as mticker -import seaborn as sns -from datetime import datetime - - -client = API(access_token=cfg.OANDA_TOKEN, environment="live") -account = cfg.OANDA_ACCOUNT - - -def get_fx_price(accountID, instrument): - 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) - except V20Error as e: - print(e) - - -def get_account_summary(accountID): - request = accounts.AccountSummary(accountID=accountID) - response = client.request(request) - balance = response["account"]["balance"] - margin_available = response["account"]["marginAvailable"] - margin_closeout = response["account"]["marginCloseoutNAV"] - margin_closeout_percent = response["account"]["marginCloseoutPercent"] - margin_closeout_position_value = response["account"]["marginCloseoutPositionValue"] - margin_used = response["account"]["marginUsed"] - net_asset_value = response["account"]["NAV"] - open_trade_count = response["account"]["openTradeCount"] - total_pl = response["account"]["pl"] - unrealized_pl = response["account"]["unrealizedPL"] - - print(f"Balance: {balance}") - print(f"NAV: {net_asset_value}") - print(f"Unrealized P/L: {unrealized_pl}") - print(f"Total P/L: {total_pl}") - print(f"Open Trade Count: {open_trade_count}") - print(f"Margin Available: ${margin_available}") - print(f"Margin Used: ${margin_used}") - print(f"Margin Closeout {margin_closeout}") - print(f"Margin Closeout Percent: {margin_closeout_percent}") - print(f"Margin Closeout Position Value: {margin_closeout_position_value}") - - -def list_orders(accountID, other_args: List[str]): - parser = argparse.ArgumentParser( - add_help=False, - prog="listorders", - description="List order history", - ) - parser.add_argument( - "-s", "--state", dest="state", action="store", default="ALL", required=False - ) - parser.add_argument( - "-c", "--count", dest="count", action="store", default=50, required=False - ) - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - parameters = {} - parameters["state"] = ns_parser.state - parameters["count"] = ns_parser.count - - 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) - - -def get_order_book(instrument): - parameters = {"bucketWidth": "1"} - try: - 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") - except V20Error as e: - print(e) - - -def get_position_book(instrument): - try: - 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") - except V20Error as e: - print(e) - - -def create_order(accountID, instrument, other_args: List[str]): - parser = argparse.ArgumentParser( - add_help=False, - prog="create_order", - description="Create order", - ) - parser.add_argument( - "-u", - "--unit", - dest="units", - action="store", - type=int, - default=0, - required=True, - ) - parser.add_argument( - "-p", - "--price", - dest="price", - action="store", - type=float, - required=True, - ) - - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - data = { - "order": { - "price": ns_parser.price, - "instrument": instrument, - "units": ns_parser.units, - "type": "LIMIT", - "timeInForce": "GTC", - "positionFill": "DEFAULT", - } - } - request = orders.OrderCreate(accountID, data) - response = client.request(request) - print(response) - - -def cancel_pending_order(accountID, other_args: List[str]): - parser = argparse.ArgumentParser( - add_help=False, - prog="cancelpendingorder", - description="Cancel Pending Order", - ) - parser.add_argument( - "-i", - "--id", - dest="orderID", - action="store", - type=str, - required=True, - ) - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - try: - request = orders.OrderCancel(accountID, ns_parser.orderID) - client.request(request) - except V20Error as e: - print(e) - - -def get_pending_orders(accountID): - request = orders.OrdersPending(accountID) - response = client.request(request) - for i in range(len(response["orders"])): - try: - order_id = response["orders"][i]["id"] - instrument = response["orders"][i]["instrument"] - price = response["orders"][i]["price"] - units = response["orders"][i]["units"] - create_time = response["orders"][i]["createTime"] - time_in_force = response["orders"][i]["timeInForce"] - print(f"Order ID: {order_id}") - print(f"Instrument: {instrument}") - print(f"Price: {price}") - print(f"Units: {units}") - print(f"Time created: {create_time}") - print(f"Time in force: {time_in_force}") - print("-" * 30) - except IndexError: - break - - -def get_open_positions(accountID): - try: - request = positions.OpenPositions(accountID) - response = client.request(request) - for i in range(len(response["positions"])): - instrument = response["positions"][i]["instrument"] - long_units = response["positions"][i]["long"]["units"] - long_pl = response["positions"][i]["long"]["pl"] - long_upl = response["positions"][i]["long"]["unrealizedPL"] - short_units = response["positions"][i]["short"]["units"] - short_pl = response["positions"][i]["short"]["pl"] - short_upl = response["positions"][i]["short"]["unrealizedPL"] - print(f"Instrument: {instrument}\n") - print(f"Long Units: {long_units}") - print(f"Total Long P/L: {long_pl}") - print(f"Long Unrealized P/L: {long_upl}\n") - print(f"Short Units: {short_units}") - print(f"Total Short P/L: {short_pl}") - print(f"Short Unrealized P/L: {short_upl}") - print("-" * 30 + "\n") - except V20Error as e: - print(e) - - -def get_open_trades(accountID): - request = trades.OpenTrades(accountID) - response = client.request(request) - 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", - } - ) - print(df) - - -def close_trade(accountID, other_args: List[str]): - parser = argparse.ArgumentParser( - add_help=False, - prog="close_trade", - description="close a trade", - ) - parser.add_argument( - "-i", - "--id", - dest="orderID", - action="store", - type=str, - required=True, - ) - parser.add_argument( - "-u", - "--units", - dest="units", - action="store", - type=str, - required=False, - ) - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - data = { - "units": ns_parser.units, - } - try: - request = trades.TradeClose(accountID, ns_parser.orderID, data) - response = client.request(request) - print(response) - except V20Error as e: - print(e) - - -def show_candles(accountID, instrument, other_args: List[str]): - parser = argparse.ArgumentParser( - add_help=False, - prog="show_candles", - description="Display Candle Data", - ) - parser.add_argument( - "-g", - "--granularity", - dest="granularity", - action="store", - type=str, - default="D", - required=False, - ) - parser.add_argument( - "-c", - "--count", - dest="candlecount", - action="store", - default=180, - type=int, - required=False, - ) - - ns_parser = parse_known_args_and_warn(parser, other_args) - if not ns_parser: - return - - parameters = {} - parameters["granularity"] = ns_parser.granularity - parameters["count"] = ns_parser.candlecount - try: - request = instruments.InstrumentsCandles(instrument, params=parameters) - response = client.request(request) - process_candle_response(response) - oanda_fix_date(".temp_candles.csv") - df = pd.read_csv(".candles.csv", index_col=0) - df.index = pd.to_datetime(df.index) - df.columns = ["Open", "High", "Low", "Close", "Volume"] - if gtff.USE_ION: - plt.ion() - mpf.plot( - df, - type="candle", - style="charles", - volume=True, - title=f"{instrument} {ns_parser.granularity}", - ) - except Exception as e: - print(e) - except NameError as e: - print(e) - - -def process_candle_response(response): - with open(".temp_candles.csv", "w") as out: - for i in range(len(response["candles"])): - time = response["candles"][i]["time"] - volume = response["candles"][i]["volume"] - o = response["candles"][i]["mid"]["o"] - h = response["candles"][i]["mid"]["h"] - low = response["candles"][i]["mid"]["l"] - c = response["candles"][i]["mid"]["c"] - out.write( - str(time) - + "," - + str(o) - + "," - + str(h) - + "," - + str(low) - + "," - + str(c) - + "," - + str(volume) - + "\n" - ) - - -def oanda_fix_date(file): - with open(file) as candle_file: - lines = candle_file.readlines() - with open(".candles.csv", "w") as out: - out.write("Datetime, Open, High, Low, Close, Volume\n") - for line in lines: - with open(".candles.csv", "a") as output: - output.write(line[:10] + " " + line[11:19] + line[30:]) - - -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, - ) - 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)} - request = labs.Calendar(params=parameters) - response = client.request(request) - for i in range(len(response)): - if "title" in response[i]: - title = response[i]["title"] - print(f"Title: {title}") - if "timestamp" in response[i]: - timestamp = response[i]["timestamp"] - time = datetime.fromtimestamp(timestamp) - print(f"Time: {time}") - if "impact" in response[i]: - impact = response[i]["impact"] - print(f"Impact: {impact}") - if "forecast" in response[i]: - forecast = response[i]["forecast"] - unit = response[i]["unit"] - if unit != "Index": - print(f"Forecast: {forecast}{unit}") - else: - print(f"Forecast: {forecast}") - if "market" in response[i]: - market = response[i]["market"] - unit = response[i]["unit"] - if unit != "Index": - print(f"Market Forecast: {market}{unit}") - else: - print(f"Market Forecast: {market}") - if "currency" in response[i]: - currency = response[i]["currency"] - print(f"Currency: {currency}") - if "region" in response[i]: - region = response[i]["region"] - print(f"Region: {region}") - if "actual" in response[i]: - actual = response[i]["actual"] - unit = response[i]["unit"] - if unit != "Index": - print(f"Actual: {actual}{unit}") - else: - print(f"Actual: {actual}") - if "previous" in response[i]: - previous = response[i]["previous"] - unit = response[i]["unit"] - if unit != "Index": - print(f"Previous: {previous}{unit}") - else: - print(f"Previous: {previous}") - print("-" * 30) - - -def load(other_args: List[str]): - """Load a forex instrument to use""" - parser = argparse.ArgumentParser( - add_help=False, - prog="Forex", - description="Forex using oanda", - ) - - parser.add_argument( - "-i", - "--instrument", - required=True, - type=str, - dest="instrument", - help="Instrument to use for function calls", - ) - - 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 - return ns_parser.instrument.upper() - except Exception as e: - print(e) - - -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() From a4914b775b9bdab9fcecda93ddfd9f5ac3ecf329 Mon Sep 17 00:00:00 2001 From: DJ Date: Mon, 19 Apr 2021 12:08:00 -0600 Subject: [PATCH 40/54] Requested Changes --- gamestonk_terminal/forex/fx_controller.py | 56 ++-- gamestonk_terminal/forex/fx_view.py | 334 +++++++++++++++++----- 2 files changed, 299 insertions(+), 91 deletions(-) diff --git a/gamestonk_terminal/forex/fx_controller.py b/gamestonk_terminal/forex/fx_controller.py index d867fe0b476..14f1eb7e151 100644 --- a/gamestonk_terminal/forex/fx_controller.py +++ b/gamestonk_terminal/forex/fx_controller.py @@ -43,7 +43,7 @@ class ForexController: self.instrument = None @staticmethod - def print_help(): + def print_help(self): """Print help""" print("\nForex Mode:") @@ -51,21 +51,23 @@ class ForexController: print(" q Quit this menu and goes back to main menu") print(" quit Quit to abandon program") print("") - print(" cancel Cancel a pending order by ID -i order ID") - print(" candles Show candles") - print(" calendar Show calendar") - print(" closetrade Close a trade by id") - print(" list List order history") print(" load Load an instrument to use") - print(" order Place limit order -u # of units -p price") - print(" orderbook Print orderbook") - print(" pending Get information on pending orders") - print(" positionbook Print positionbook") - print(" positions Get open positions") - print(" price Shows price for selected instrument") 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("") + if self.instrument is not None: + 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 @@ -84,7 +86,7 @@ class ForexController: def call_help(self, _): """Process Help Command""" - self.print_help() + self.print_help(self) def call_q(self, _): """Process Q command - quit the menu""" @@ -94,24 +96,24 @@ class ForexController: """Process Quit command - exit the program""" return True - def call_price(self, _): + def call_price(self, other_args): """Process Price Command""" - fx_view.get_fx_price(account, self.instrument) + fx_view.get_fx_price(account, self.instrument, other_args) def call_load(self, other_args): self.instrument = fx_view.load(other_args) - def call_summary(self, _): + def call_summary(self, other_args): """Process account summary command""" - fx_view.get_account_summary(account) + fx_view.get_account_summary(account, other_args) - def call_orderbook(self, _): + def call_orderbook(self, other_args): """Process Oanda Order Book""" - fx_view.get_order_book(self.instrument) + fx_view.get_order_book(self.instrument, other_args) - def call_positionbook(self, _): + def call_positionbook(self, other_args): """Process Oanda Position Book""" - fx_view.get_position_book(self.instrument) + fx_view.get_position_book(self.instrument, other_args) def call_list(self, other_args: List[str]): """Process list orders command""" @@ -125,13 +127,13 @@ class ForexController: """Cancel pending order by ID""" fx_view.cancel_pending_order(account, other_args) - def call_positions(self, _): + def call_positions(self, other_args): """Get Open Positions""" - fx_view.get_open_positions(account) + fx_view.get_open_positions(account, other_args) - def call_pending(self, _): + def call_pending(self, other_args): """See up to 25 pending orders""" - fx_view.get_pending_orders(account) + fx_view.get_pending_orders(account, other_args) def call_closetrade(self, other_args: List[str]): """Close a trade by id""" @@ -140,9 +142,9 @@ class ForexController: def call_candles(self, other_args: List[str]): fx_view.show_candles(account, self.instrument, other_args) - def call_trades(self, _): + def call_trades(self, other_args): """List open trades""" - fx_view.get_open_trades(account) + fx_view.get_open_trades(account, other_args) def call_calendar(self, other_args: List[str]): """Call calendar""" diff --git a/gamestonk_terminal/forex/fx_view.py b/gamestonk_terminal/forex/fx_view.py index fafa478f9ad..97f54f342ac 100644 --- a/gamestonk_terminal/forex/fx_view.py +++ b/gamestonk_terminal/forex/fx_view.py @@ -25,7 +25,16 @@ client = API(access_token=cfg.OANDA_TOKEN, environment="live") account = cfg.OANDA_ACCOUNT -def get_fx_price(accountID, instrument): +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) @@ -34,11 +43,24 @@ def get_fx_price(accountID, instrument): ask = response["prices"][0]["asks"][0]["price"] print(instrument + " Bid: " + bid) print(instrument + " Ask: " + ask) + print("") except V20Error as e: - print(e) + msg_length = len(e.msg) + msg = e.msg[17 : msg_length - 2] + print(msg) + print("") -def get_account_summary(accountID): +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) @@ -65,21 +87,37 @@ def get_account_summary(accountID): print(f"Margin Closeout {margin_closeout}") print(f"Margin Closeout Percent: {margin_closeout_percent}") print(f"Margin Closeout Position Value: {margin_closeout_position_value}") + print("") except V20Error as e: - print(e) + msg_length = len(e.msg) + msg = e.msg[17 : msg_length - 2] + print(msg) + print("") def list_orders(accountID, other_args: List[str]): parser = argparse.ArgumentParser( add_help=False, - prog="listorders", + prog="list", description="List order history", ) parser.add_argument( - "-s", "--state", dest="state", action="store", default="ALL", required=False + "-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=50, required=False + "-c", + "--count", + dest="count", + action="store", + default=50, + required=False, + help="the number of orders to retrive ", ) ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: @@ -92,14 +130,31 @@ def list_orders(accountID, other_args: List[str]): 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) + try: + 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") + print("") except V20Error as e: - print(e) + msg_length = len(e.msg) + msg = e.msg[17 : msg_length - 2] + print(msg) + print("") -def get_order_book(instrument): +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: request = instruments.InstrumentsOrderBook( @@ -110,11 +165,24 @@ def get_order_book(instrument): 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: - print(e) + msg_length = len(e.msg) + msg = e.msg[17 : msg_length - 2] + print(msg) + print("") -def get_position_book(instrument): +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: request = instruments.InstrumentsPositionBook(instrument=instrument) response = client.request(request) @@ -122,14 +190,18 @@ def get_position_book(instrument): 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: - print(e) + msg_length = len(e.msg) + msg = e.msg[17 : msg_length - 2] + print(msg) + print("") def create_order(accountID, instrument, other_args: List[str]): parser = argparse.ArgumentParser( add_help=False, - prog="create_order", + prog="order", description="Create order", ) parser.add_argument( @@ -140,6 +212,8 @@ def create_order(accountID, instrument, other_args: List[str]): 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", @@ -148,6 +222,7 @@ def create_order(accountID, instrument, other_args: List[str]): action="store", type=float, required=True, + help="The price to set for the limit order. ", ) ns_parser = parse_known_args_and_warn(parser, other_args) @@ -167,16 +242,52 @@ def create_order(accountID, instrument, other_args: List[str]): try: request = orders.OrderCreate(accountID, data) response = client.request(request) - print(response) + try: + if "orderFillTransaction" in response["orderCreateTransaction"]: + order_id = response["orderCreateTransaction"]["orderFillTransaction"][ + "id" + ] + order_instrument = response["orderCreateTransaction"][ + "orderFillTransaction" + ]["instrument"] + units = response["orderCreateTransaction"]["orderFillTransaction"][ + "units" + ] + price = response["orderCreateTransaction"]["orderFillTransaction"][ + "price" + ] + print("Order Filled:") + print(f"ID: {order_id}") + print(f"Instrument: {order_instrument}") + print(f"Units: {units}") + print(f"Price: {price}") + print("") + else: + order_creation_id = response["orderCreateTransaction"]["id"] + order_instrument = response["orderCreateTransaction"]["instrument"] + units = response["orderCreateTransaction"]["units"] + price = response["orderCreateTransaction"]["price"] + print("Order created:") + print(f"ID: {order_creation_id}") + print(f"Instrument: {order_instrument}") + print(f"Units: {units}") + print(f"Price: {price}") + print("") + + except Exception as e: + print(e) except V20Error as e: - print(e) + msg_length = len(e.msg) + msg = e.msg[17 : msg_length - 2] + print(msg) + print("") def cancel_pending_order(accountID, other_args: List[str]): parser = argparse.ArgumentParser( add_help=False, - prog="cancelpendingorder", - description="Cancel Pending Order", + prog="pending", + description="Cancel Pending Order ", ) parser.add_argument( "-i", @@ -184,19 +295,37 @@ def cancel_pending_order(accountID, other_args: List[str]): dest="orderID", action="store", type=str, - required=True, + 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) - client.request(request) + response = client.request(request) + order_id = response["orderCancelTransaction"]["orderID"] + print(f"Order {order_id} canceled.") + print("") except V20Error as e: - print(e) + msg_length = len(e.msg) + msg = e.msg[17 : msg_length - 2] + print(msg) + print("") -def get_pending_orders(accountID): +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) @@ -217,11 +346,23 @@ def get_pending_orders(accountID): print("-" * 30) except IndexError: break + print("") except V20Error as e: - print(e) + msg_length = len(e.msg) + msg = e.msg[17 : msg_length - 2] + print(msg) + print("") -def get_open_positions(accountID): +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) @@ -241,44 +382,65 @@ def get_open_positions(accountID): print(f"Total Short P/L: {short_pl}") print(f"Short Unrealized P/L: {short_upl}") print("-" * 30 + "\n") + print("") except V20Error as e: - print(e) + msg_length = len(e.msg) + msg = e.msg[17 : msg_length - 2] + print(msg) + print("") -def get_open_trades(accountID): +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) - df = pd.DataFrame.from_dict(response["trades"]) - df = df[ - [ - "id", - "instrument", - "initialUnits", - "currentUnits", - "price", - "unrealizedPL", + 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", - } - ) - print(df) + df = df.rename( + columns={ + "id": "ID", + "instrument": "Instrument", + "initialUnits": "Initial Units", + "currentUnits": "Current Units", + "price": "Entry Price", + } + ) + print(df) + print("") + except KeyError: + print("No trades were found") + print("") except V20Error as e: - print(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="close_trade", - description="close a trade", + prog="closetrade", + description="Close a trade by id.", ) parser.add_argument( "-i", @@ -287,6 +449,7 @@ def close_trade(accountID, other_args: List[str]): action="store", type=str, required=True, + help="The Trade ID to close. ", ) parser.add_argument( "-u", @@ -295,25 +458,55 @@ def close_trade(accountID, other_args: List[str]): action="store", type=str, required=False, + help="The number of units on the trade to close. If not set it " + + "defaults to all units. ", ) + 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 - data = { - "units": ns_parser.units, - } + 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) - print(response) + try: + order_id = response["orderCreateTransaction"]["tradeClose"]["tradeID"] + order_instrument = response["orderFillTransaction"]["instrument"] + units = response["orderCreateTransaction"]["units"] + price = response["orderFillTransaction"]["price"] + pl = response["orderFillTransaction"]["pl"] + print("Order closed:") + print(f"ID: {order_id}") + print(f"Instrument: {order_instrument}") + print(f"Units: {units}") + print(f"Price: {price}") + print(f"P/L: {pl}") + print("") + except Exception as e: + print(e) + print("") + except V20Error as e: - print(e) + if "orderRejectTransaction" in e.msg: + msg_length = len(e.msg) + order_id = e.msg[355 : msg_length - 149] + print(f"Order ID {order_id} doesn't exist") + print("") + else: + msg_length = len(e.msg) + msg = e.msg[17 : msg_length - 2] + print(msg) + print("") def show_candles(accountID, instrument, other_args: List[str]): parser = argparse.ArgumentParser( add_help=False, - prog="show_candles", + prog="candles", description="Display Candle Data", ) parser.add_argument( @@ -324,6 +517,9 @@ def show_candles(accountID, instrument, other_args: List[str]): 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, Wee: W Month: M", ) parser.add_argument( "-c", @@ -333,6 +529,7 @@ def show_candles(accountID, instrument, other_args: List[str]): default=180, type=int, required=False, + help="The number of candles to retrieve. Default:180 ", ) ns_parser = parse_known_args_and_warn(parser, other_args) @@ -359,10 +556,12 @@ def show_candles(accountID, instrument, other_args: List[str]): volume=True, title=f"{instrument} {ns_parser.granularity}", ) - except Exception as e: - print(e) - except NameError as e: - print(e) + print("") + except V20Error as e: + msg_length = len(e.msg) + msg = e.msg[17 : msg_length - 2] + print(msg) + print("") def process_candle_response(response): @@ -414,6 +613,8 @@ def calendar(instrument, other_args: List[str]): 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: @@ -469,15 +670,20 @@ def calendar(instrument, other_args: List[str]): else: print(f"Previous: {previous}") print("-" * 30) + print("") except V20Error as e: - print(e) + msg_length = len(e.msg) + msg = e.msg[17 : msg_length - 2] + print(msg) + print("") + print("") def load(other_args: List[str]): """Load a forex instrument to use""" parser = argparse.ArgumentParser( add_help=False, - prog="Forex", + prog="load", description="Forex using oanda", ) @@ -487,7 +693,7 @@ def load(other_args: List[str]): required=True, type=str, dest="instrument", - help="Instrument to use for function calls", + help="Forex pair to use. ", ) try: From 64e198f62122131067bfd2683c1c3fd8fd3abf2f Mon Sep 17 00:00:00 2001 From: DJ Date: Mon, 19 Apr 2021 12:12:16 -0600 Subject: [PATCH 41/54] Spelling --- gamestonk_terminal/forex/fx_view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gamestonk_terminal/forex/fx_view.py b/gamestonk_terminal/forex/fx_view.py index 97f54f342ac..fd8b0668cb9 100644 --- a/gamestonk_terminal/forex/fx_view.py +++ b/gamestonk_terminal/forex/fx_view.py @@ -117,7 +117,7 @@ def list_orders(accountID, other_args: List[str]): action="store", default=50, required=False, - help="the number of orders to retrive ", + help="the number of orders to retrieve ", ) ns_parser = parse_known_args_and_warn(parser, other_args) if not ns_parser: @@ -519,7 +519,7 @@ def show_candles(accountID, instrument, other_args: List[str]): 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, Wee: W Month: M", + + "Day (default): D, Week: W Month: M", ) parser.add_argument( "-c", From e25ac791a1edecd96e7029956d418bfa17fd805c Mon Sep 17 00:00:00 2001 From: DJ Date: Mon, 19 Apr 2021 12:15:28 -0600 Subject: [PATCH 42/54] Added current instrument to help --- gamestonk_terminal/forex/fx_controller.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gamestonk_terminal/forex/fx_controller.py b/gamestonk_terminal/forex/fx_controller.py index 14f1eb7e151..3b96f5c2911 100644 --- a/gamestonk_terminal/forex/fx_controller.py +++ b/gamestonk_terminal/forex/fx_controller.py @@ -51,6 +51,8 @@ class ForexController: print(" q Quit this menu and goes back to main menu") print(" quit Quit to abandon program") print("") + if self.instrument is not None: + print("Currently loaded instrument " + self.instrument) print(" load Load an instrument to use") print(" summary Shows account summary") print(" calendar Show calendar") From 00ff6e0ad3fd6e9debad6d6cfaef199267e0b384 Mon Sep 17 00:00:00 2001 From: didier Date: Mon, 19 Apr 2021 23:51:31 +0100 Subject: [PATCH 43/54] improve fx_view code --- gamestonk_terminal/forex/fx_controller.py | 42 +-- gamestonk_terminal/forex/fx_view.py | 362 ++++++++++------------ 2 files changed, 193 insertions(+), 211 deletions(-) diff --git a/gamestonk_terminal/forex/fx_controller.py b/gamestonk_terminal/forex/fx_controller.py index 3b96f5c2911..b1ab4dc7c68 100644 --- a/gamestonk_terminal/forex/fx_controller.py +++ b/gamestonk_terminal/forex/fx_controller.py @@ -47,29 +47,29 @@ class ForexController: """Print help""" print("\nForex Mode:") - print(" help Show this menu again") - print(" q Quit this menu and goes back to main menu") - print(" quit Quit to abandon program") + print(" help show this menu again") + print(" q quit this menu and goes back to main menu") + print(" quit quit to abandon program") print("") - if self.instrument is not None: - print("Currently loaded instrument " + self.instrument) - print(" load Load an instrument to use") - 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(" 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("") - if self.instrument is not None: - 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 diff --git a/gamestonk_terminal/forex/fx_view.py b/gamestonk_terminal/forex/fx_view.py index fd8b0668cb9..ca5742e5640 100644 --- a/gamestonk_terminal/forex/fx_view.py +++ b/gamestonk_terminal/forex/fx_view.py @@ -44,11 +44,10 @@ def get_fx_price(accountID, instrument, other_args: List[str]): print(instrument + " Bid: " + bid) print(instrument + " Ask: " + ask) print("") + except V20Error as e: - msg_length = len(e.msg) - msg = e.msg[17 : msg_length - 2] - print(msg) - print("") + d_error = eval(e.msg) + print(d_error["message"], "\n") def get_account_summary(accountID, other_args: List[str]): @@ -64,35 +63,46 @@ def get_account_summary(accountID, other_args: List[str]): try: request = accounts.AccountSummary(accountID=accountID) response = client.request(request) - balance = response["account"]["balance"] - margin_available = response["account"]["marginAvailable"] - margin_closeout = response["account"]["marginCloseoutNAV"] - margin_closeout_percent = response["account"]["marginCloseoutPercent"] - margin_closeout_position_value = response["account"][ - "marginCloseoutPositionValue" - ] - margin_used = response["account"]["marginUsed"] - net_asset_value = response["account"]["NAV"] - open_trade_count = response["account"]["openTradeCount"] - total_pl = response["account"]["pl"] - unrealized_pl = response["account"]["unrealizedPL"] - print(f"Balance: {balance}") - print(f"NAV: {net_asset_value}") - print(f"Unrealized P/L: {unrealized_pl}") - print(f"Total P/L: {total_pl}") - print(f"Open Trade Count: {open_trade_count}") - print(f"Margin Available: ${margin_available}") - print(f"Margin Used: ${margin_used}") - print(f"Margin Closeout {margin_closeout}") - print(f"Margin Closeout Percent: {margin_closeout_percent}") - print(f"Margin Closeout Position Value: {margin_closeout_position_value}") + 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: - msg_length = len(e.msg) - msg = e.msg[17 : msg_length - 2] - print(msg) - print("") + d_error = eval(e.msg) + print(d_error["errorMessage"], "\n") def list_orders(accountID, other_args: List[str]): @@ -130,20 +140,18 @@ def list_orders(accountID, other_args: List[str]): try: request = orders.OrderList(accountID, parameters) response = client.request(request) - try: - 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") - print("") - except V20Error as e: - msg_length = len(e.msg) - msg = e.msg[17 : msg_length - 2] - print(msg) + + 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: + d_error = eval(e.msg) + print(d_error["errorMessage"], "\n") + def get_order_book(instrument, other_args): parser = argparse.ArgumentParser( @@ -166,11 +174,10 @@ def get_order_book(instrument, other_args): df = df.take(range(527, 727, 1)) book_plot(df, instrument, "Order Book") print("") + except V20Error as e: - msg_length = len(e.msg) - msg = e.msg[17 : msg_length - 2] - print(msg) - print("") + d_error = eval(e.msg) + print(d_error["errorMessage"], "\n") def get_position_book(instrument, other_args): @@ -191,11 +198,10 @@ def get_position_book(instrument, other_args): df = df.take(range(219, 415, 1)) book_plot(df, instrument, "Position Book") print("") + except V20Error as e: - msg_length = len(e.msg) - msg = e.msg[17 : msg_length - 2] - print(msg) - print("") + d_error = eval(e.msg) + print(d_error["errorMessage"], "\n") def create_order(accountID, instrument, other_args: List[str]): @@ -242,45 +248,37 @@ def create_order(accountID, instrument, other_args: List[str]): try: request = orders.OrderCreate(accountID, data) response = client.request(request) - try: - if "orderFillTransaction" in response["orderCreateTransaction"]: - order_id = response["orderCreateTransaction"]["orderFillTransaction"][ - "id" - ] - order_instrument = response["orderCreateTransaction"][ - "orderFillTransaction" - ]["instrument"] - units = response["orderCreateTransaction"]["orderFillTransaction"][ - "units" - ] - price = response["orderCreateTransaction"]["orderFillTransaction"][ - "price" - ] - print("Order Filled:") - print(f"ID: {order_id}") - print(f"Instrument: {order_instrument}") - print(f"Units: {units}") - print(f"Price: {price}") - print("") - else: - order_creation_id = response["orderCreateTransaction"]["id"] - order_instrument = response["orderCreateTransaction"]["instrument"] - units = response["orderCreateTransaction"]["units"] - price = response["orderCreateTransaction"]["price"] - print("Order created:") - print(f"ID: {order_creation_id}") - print(f"Instrument: {order_instrument}") - print(f"Units: {units}") - print(f"Price: {price}") - print("") - except Exception as e: - print(e) + if "orderFillTransaction" in response["orderCreateTransaction"]: + order_id = response["orderCreateTransaction"]["orderFillTransaction"]["id"] + order_instrument = response["orderCreateTransaction"][ + "orderFillTransaction" + ]["instrument"] + units = response["orderCreateTransaction"]["orderFillTransaction"]["units"] + price = response["orderCreateTransaction"]["orderFillTransaction"]["price"] + print("Order Filled:") + print(f"ID: {order_id}") + print(f"Instrument: {order_instrument}") + print(f"Units: {units}") + print(f"Price: {price}") + print("") + else: + order_creation_id = response["orderCreateTransaction"]["id"] + order_instrument = response["orderCreateTransaction"]["instrument"] + units = response["orderCreateTransaction"]["units"] + price = response["orderCreateTransaction"]["price"] + print("Order created:") + print(f"ID: {order_creation_id}") + print(f"Instrument: {order_instrument}") + print(f"Units: {units}") + print(f"Price: {price}") + print("") + + except Exception as e: + print(e) except V20Error as e: - msg_length = len(e.msg) - msg = e.msg[17 : msg_length - 2] - print(msg) - print("") + d_error = eval(e.msg) + print(d_error["message"], "\n") def cancel_pending_order(accountID, other_args: List[str]): @@ -310,10 +308,8 @@ def cancel_pending_order(accountID, other_args: List[str]): print(f"Order {order_id} canceled.") print("") except V20Error as e: - msg_length = len(e.msg) - msg = e.msg[17 : msg_length - 2] - print(msg) - print("") + d_error = eval(e.msg) + print(d_error["errorMessage"], "\n") def get_pending_orders(accountID, other_args): @@ -330,28 +326,23 @@ def get_pending_orders(accountID, other_args): request = orders.OrdersPending(accountID) response = client.request(request) for i in range(len(response["orders"])): - try: - order_id = response["orders"][i]["id"] - instrument = response["orders"][i]["instrument"] - price = response["orders"][i]["price"] - units = response["orders"][i]["units"] - create_time = response["orders"][i]["createTime"] - time_in_force = response["orders"][i]["timeInForce"] - print(f"Order ID: {order_id}") - print(f"Instrument: {instrument}") - print(f"Price: {price}") - print(f"Units: {units}") - print(f"Time created: {create_time}") - print(f"Time in force: {time_in_force}") - print("-" * 30) - except IndexError: - break + order_id = response["orders"][i]["id"] + instrument = response["orders"][i]["instrument"] + price = response["orders"][i]["price"] + units = response["orders"][i]["units"] + create_time = response["orders"][i]["createTime"] + time_in_force = response["orders"][i]["timeInForce"] + print(f"Order ID: {order_id}") + print(f"Instrument: {instrument}") + print(f"Price: {price}") + print(f"Units: {units}") + print(f"Time created: {create_time}") + print(f"Time in force: {time_in_force}") + print("-" * 30) print("") except V20Error as e: - msg_length = len(e.msg) - msg = e.msg[17 : msg_length - 2] - print(msg) - print("") + d_error = eval(e.msg) + print(d_error["errorMessage"], "\n") def get_open_positions(accountID, other_args): @@ -383,11 +374,10 @@ def get_open_positions(accountID, other_args): print(f"Short Unrealized P/L: {short_upl}") print("-" * 30 + "\n") print("") + except V20Error as e: - msg_length = len(e.msg) - msg = e.msg[17 : msg_length - 2] - print(msg) - print("") + d_error = eval(e.msg) + print(d_error["errorMessage"], "\n") def get_open_trades(accountID, other_args): @@ -467,40 +457,33 @@ def close_trade(accountID, other_args: List[str]): 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) - try: - order_id = response["orderCreateTransaction"]["tradeClose"]["tradeID"] - order_instrument = response["orderFillTransaction"]["instrument"] - units = response["orderCreateTransaction"]["units"] - price = response["orderFillTransaction"]["price"] - pl = response["orderFillTransaction"]["pl"] - print("Order closed:") - print(f"ID: {order_id}") - print(f"Instrument: {order_instrument}") - print(f"Units: {units}") - print(f"Price: {price}") - print(f"P/L: {pl}") - print("") - except Exception as e: - print(e) - print("") + order_id = response["orderCreateTransaction"]["tradeClose"]["tradeID"] + order_instrument = response["orderFillTransaction"]["instrument"] + units = response["orderCreateTransaction"]["units"] + price = response["orderFillTransaction"]["price"] + pl = response["orderFillTransaction"]["pl"] + print("Order closed:") + print(f"ID: {order_id}") + print(f"Instrument: {order_instrument}") + print(f"Units: {units}") + print(f"Price: {price}") + print(f"P/L: {pl}") + print("") + + except Exception as e: + d_error = eval(e.msg) + print(d_error["errorMessage"], "\n") except V20Error as e: - if "orderRejectTransaction" in e.msg: - msg_length = len(e.msg) - order_id = e.msg[355 : msg_length - 149] - print(f"Order ID {order_id} doesn't exist") - print("") - else: - msg_length = len(e.msg) - msg = e.msg[17 : msg_length - 2] - print(msg) - print("") + d_error = eval(e.msg) + print(d_error["errorMessage"], "\n") def show_candles(accountID, instrument, other_args: List[str]): @@ -558,10 +541,8 @@ def show_candles(accountID, instrument, other_args: List[str]): ) print("") except V20Error as e: - msg_length = len(e.msg) - msg = e.msg[17 : msg_length - 2] - print(msg) - print("") + d_error = eval(e.msg) + print(d_error["errorMessage"], "\n") def process_candle_response(response): @@ -624,59 +605,57 @@ def calendar(instrument, other_args: List[str]): try: request = labs.Calendar(params=parameters) response = client.request(request) + + l_data = [] for i in range(len(response)): - if "title" in response[i]: - title = response[i]["title"] - print(f"Title: {title}") - if "timestamp" in response[i]: - timestamp = response[i]["timestamp"] - time = datetime.fromtimestamp(timestamp) - print(f"Time: {time}") - if "impact" in response[i]: - impact = response[i]["impact"] - print(f"Impact: {impact}") if "forecast" in response[i]: forecast = response[i]["forecast"] - unit = response[i]["unit"] - if unit != "Index": - print(f"Forecast: {forecast}{unit}") - else: - print(f"Forecast: {forecast}") + if response[i]["unit"] != "Index": + forecast += response[i]["unit"] + else: + forecast = "" + if "market" in response[i]: market = response[i]["market"] - unit = response[i]["unit"] - if unit != "Index": - print(f"Market Forecast: {market}{unit}") - else: - print(f"Market Forecast: {market}") - if "currency" in response[i]: - currency = response[i]["currency"] - print(f"Currency: {currency}") - if "region" in response[i]: - region = response[i]["region"] - print(f"Region: {region}") + if response[i]["unit"] != "Index": + market += response[i]["unit"] + else: + market = "" + if "actual" in response[i]: actual = response[i]["actual"] - unit = response[i]["unit"] - if unit != "Index": - print(f"Actual: {actual}{unit}") - else: - print(f"Actual: {actual}") + if response[i]["unit"] != "Index": + actual += response[i]["unit"] + else: + actual = "" + if "previous" in response[i]: previous = response[i]["previous"] - unit = response[i]["unit"] - if unit != "Index": - print(f"Previous: {previous}{unit}") - else: - print(f"Previous: {previous}") - print("-" * 30) - print("") - except V20Error as e: - msg_length = len(e.msg) - msg = e.msg[17 : msg_length - 2] - print(msg) + if response[i]["unit"] != "Index": + previous += response[i]["unit"] + else: + previous = "" + + l_data.append( + { + "Title": response[i]["title"], + "Time": datetime.fromtimestamp(response[i]["timestamp"]), + "Impact": response[i]["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("") - print("") + + except V20Error as e: + d_error = eval(e.msg) + print(d_error["message"], "\n") def load(other_args: List[str]): @@ -702,12 +681,15 @@ def load(other_args: List[str]): 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) + print(e, "\n") + return None def book_plot(df, instrument, book_type): From 9e2da2d4731c3c12846e7b62a1bd203f44ffb9b5 Mon Sep 17 00:00:00 2001 From: didier Date: Tue, 20 Apr 2021 00:01:56 +0100 Subject: [PATCH 44/54] fix mypy --- gamestonk_terminal/forex/fx_view.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gamestonk_terminal/forex/fx_view.py b/gamestonk_terminal/forex/fx_view.py index ca5742e5640..63f378495aa 100644 --- a/gamestonk_terminal/forex/fx_view.py +++ b/gamestonk_terminal/forex/fx_view.py @@ -479,8 +479,7 @@ def close_trade(accountID, other_args: List[str]): print("") except Exception as e: - d_error = eval(e.msg) - print(d_error["errorMessage"], "\n") + print(e, "\n") except V20Error as e: d_error = eval(e.msg) print(d_error["errorMessage"], "\n") From 761d06ff250b5cea3e42f5a5acdc6bce4d9a7998 Mon Sep 17 00:00:00 2001 From: DJ Date: Mon, 19 Apr 2021 18:05:38 -0600 Subject: [PATCH 45/54] changed message to errorMessage --- gamestonk_terminal/forex/fx_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamestonk_terminal/forex/fx_view.py b/gamestonk_terminal/forex/fx_view.py index 63f378495aa..bf8b50c14ea 100644 --- a/gamestonk_terminal/forex/fx_view.py +++ b/gamestonk_terminal/forex/fx_view.py @@ -47,7 +47,7 @@ def get_fx_price(accountID, instrument, other_args: List[str]): except V20Error as e: d_error = eval(e.msg) - print(d_error["message"], "\n") + print(d_error["errorMessage"], "\n") def get_account_summary(accountID, other_args: List[str]): From 2b2548f625b33b3858c2d282b763e7fbc0878fc3 Mon Sep 17 00:00:00 2001 From: DJ Date: Tue, 20 Apr 2021 08:41:48 -0600 Subject: [PATCH 46/54] added check for separator --- gamestonk_terminal/forex/fx_view.py | 33 ++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/gamestonk_terminal/forex/fx_view.py b/gamestonk_terminal/forex/fx_view.py index bf8b50c14ea..cc4f1c59842 100644 --- a/gamestonk_terminal/forex/fx_view.py +++ b/gamestonk_terminal/forex/fx_view.py @@ -125,7 +125,7 @@ def list_orders(accountID, other_args: List[str]): "--count", dest="count", action="store", - default=50, + default=20, required=False, help="the number of orders to retrieve ", ) @@ -134,7 +134,7 @@ def list_orders(accountID, other_args: List[str]): return parameters = {} - parameters["state"] = ns_parser.state + parameters["state"] = ns_parser.state.upper() parameters["count"] = ns_parser.count try: @@ -165,6 +165,7 @@ def get_order_book(instrument, other_args): return parameters = {"bucketWidth": "1"} try: + instrument = format_instrument(instrument, "_") request = instruments.InstrumentsOrderBook( instrument=instrument, params=parameters ) @@ -191,6 +192,7 @@ def get_position_book(instrument, other_args): return try: + instrument = format_instrument(instrument, "_") request = instruments.InstrumentsPositionBook(instrument=instrument) response = client.request(request) df = pd.DataFrame.from_dict(response["positionBook"]["buckets"]) @@ -284,7 +286,7 @@ def create_order(accountID, instrument, other_args: List[str]): def cancel_pending_order(accountID, other_args: List[str]): parser = argparse.ArgumentParser( add_help=False, - prog="pending", + prog="cancel", description="Cancel Pending Order ", ) parser.add_argument( @@ -412,6 +414,7 @@ def get_open_trades(accountID, other_args): "initialUnits": "Initial Units", "currentUnits": "Current Units", "price": "Entry Price", + "unrealizedPL": "Unrealized P/L", } ) print(df) @@ -438,7 +441,7 @@ def close_trade(accountID, other_args: List[str]): dest="orderID", action="store", type=str, - required=True, + required=False, help="The Trade ID to close. ", ) parser.add_argument( @@ -446,13 +449,12 @@ def close_trade(accountID, other_args: List[str]): "--units", dest="units", action="store", - type=str, required=False, help="The number of units on the trade to close. If not set it " + "defaults to all units. ", ) if other_args: - if "-" not in other_args[0]: + 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: @@ -460,7 +462,7 @@ def close_trade(accountID, other_args: List[str]): data = {} if ns_parser.units is not None: - data["units"] = (ns_parser.units,) + data["units"] = ns_parser.units try: request = trades.TradeClose(accountID, ns_parser.orderID, data) response = client.request(request) @@ -522,6 +524,7 @@ def show_candles(accountID, instrument, other_args: List[str]): parameters["granularity"] = ns_parser.granularity parameters["count"] = ns_parser.candlecount try: + instrument = format_instrument(instrument, "_") request = instruments.InstrumentsCandles(instrument, params=parameters) response = client.request(request) process_candle_response(response) @@ -635,11 +638,16 @@ def calendar(instrument, other_args: List[str]): 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": response[i]["impact"], + "Impact": impact, "Forecast": forecast, "Market Forecast": market, "Currency": response[i]["currency"], @@ -729,3 +737,12 @@ def book_plot(df, instrument, book_type): if gtff.USE_ION: plt.ion() plt.show() + + +def format_instrument(instrument, char): + if char not in instrument: + instrument_list = list(instrument) + instrument_list.pop(3) + instrument_list.insert(3, char) + instrument = "".join(map(str, instrument_list)) + return instrument From ff970751e0371f20a8a03925041649d5eda38676 Mon Sep 17 00:00:00 2001 From: DJ Date: Tue, 20 Apr 2021 08:42:09 -0600 Subject: [PATCH 47/54] added README --- gamestonk_terminal/forex/README.md | 146 +++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 gamestonk_terminal/forex/README.md diff --git a/gamestonk_terminal/forex/README.md b/gamestonk_terminal/forex/README.md new file mode 100644 index 00000000000..4e00e174a20 --- /dev/null +++ b/gamestonk_terminal/forex/README.md @@ -0,0 +1,146 @@ +# Forex + +This menu utilizes oanda to enable support for trading forex. + +* [summary] (#summary) + * Display a summary of your account +* [calendar] (#calendar) + * Get information about past or upcoming events which may impact the price +* [list] (#list) + * List your order history +* [pending] (#pending) + * Get information about pending orders +* [cancel] (#cancel) + * Cancel a pending order by ID +* [positions] (#positions) + * Get information about your positions +* [trades] (#trades) + * See a list of open trades +* [closetrade] (#closetrade) + * Close a trade by ID +* [load] (#load) + *Specify an instrument to use +* [candles] (#candles) + * Get a candlestick chart for the forex instrument +* [price] (#price) + * Show the current price for the forex instrument +* [order] (#order) + * Place a limit order +* [orderbook] (#orderbook) + * Display the orderbook if Oanda provides one for the forex instrument +* [positionbook] (#positionbook) + * Display the positionbook if Oanda provides one for the forex instrument + +## summary + ```text + usage: summary + ``` + + Displays current balance, net asset value, unrealized P/L, total P/L, count of open trades, how much margin is available, how much margin you've used, the margin call value, the percent of margin until closeout, and the total position value. + +## calendar + +```text +usage: calendar [-d DAYS] +``` + +Display information about past or upcoming events up to 30 days before or from todays date. The information includes the Title of the event, the time the event is scheduled, the rated impact, Oanda's forecast, the market forecast, the currency involved, the region from which the event originates, the actual value (for events that have finished), and the previous value. + +* -d : The number of days to search (7 is the default, 30 is the maximum that information is provided for). Use a positive value to search for upcoming events and a negative value to search for past events. + +## list +```text +usage: list [-s STATE] [-c COUNT] +``` + +Retrieves a list of orders you've made. It includes the ID, the instrument, the number of units, the price, the state of the order, and the type of order. + +* -s : The state of the orders to filter. You can specify: pending, filled, triggered, canceled or all. (default=all) +* -c : The number of orders to retrieve. (default = 20) + +## pending +```text +usage: pending +``` +A shortcut to list the pending orders for your account. It includes the ID, the instrument, the price, the number of units, the time the order was created and the time in force. + +## cancel +```text +usage: cancel [-i ORDERID] +``` + +Cancel a pending order by its order ID. + +* -i : Specify the order ID. You don't need to include this, simply include the order number. + +##positions +```text +usage: positions +``` + +Lists your open positions. The information includes the instrument, the current number of long units, the total profit/loss from long positions for the account, the unrealized profit/loss from long positions, the current number of short units, the total profit/loss from short positions for the account, and the unrealized profit/loss from short positions. + +##trades +```text +usage: trades +``` + +Lists the current open trades for your account. The information includes the ID, the instrument, the initial units opened for the trade, the current number of units open for the trade, it's entry price, and the unrealized profit/loss for the trade. + +##closetrade +```text +usage: closetrade [-i ORDERID] [-u UNITS] +``` + +Closes a trade by ID. Displays the order ID closed, the instrument, the number of units that were closed, the price, and the profit/loss + +* -i : Specify the order ID. You don't need to include this, simply include the order number. +* -u : Specify the number of units to close for that trade. (default=all) + +##load +```text +usage: load [-i INSTRUMENT] +``` + +Specify the instrument to use in the format EUR_USD + +* -i : Specify the instrument. You don't need to include this, simply include the instrument. + +##candles +```text +usage: candles [-g GRANULARITY] [-c CANDLECOUNT] +``` + +Retrieve a candlestick chart for the currently loaded instrument. Not a live chart. + +* -g : Specify the granularity, which is the timeframe each candle represents. The available granularities are Seconds: S5, S10, S15, S30 Minutes: M1, M2, M4, M5, M10, M15, M30 Hours: H1, H2, H3, H4, H6, H8, H12 Day: D, Week: W Month: M. (default=D) +* -c : Specify the number of candles to retrieve. (default=180) + +##price +```text +usage: price +``` + +Gets the current price for the currently loaded instrument + +##order +```text +usage: order -u UNITS -p PRICE +``` + +Places a limit order for the specified number of units at the specified price. Both arguments are required. + +* -u : Specify the number of units for the order. +* -p : Specify the price for the limit order. + +##orderbook +```text +usage: orderbook +``` +Plots the current orderbook for loaded instrument if one is provided by Oanda. Not a live chart. + +##positionbook +```text +usage: positionbook +``` +Plots the current positionbook for the loaded instrument if one is provided by Oanda. Not a live chart. From f59f9257a3cc9787169531e8f7de8e56a68d20fe Mon Sep 17 00:00:00 2001 From: DJ Date: Tue, 20 Apr 2021 08:59:47 -0600 Subject: [PATCH 48/54] Delete gamestonk_terminal/oanda directory --- gamestonk_terminal/oanda/__init.py__ | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 gamestonk_terminal/oanda/__init.py__ diff --git a/gamestonk_terminal/oanda/__init.py__ b/gamestonk_terminal/oanda/__init.py__ deleted file mode 100644 index e69de29bb2d..00000000000 From 2e4c4d1becff10821dd184ee7bf552644baf4058 Mon Sep 17 00:00:00 2001 From: DJ Date: Tue, 20 Apr 2021 09:03:24 -0600 Subject: [PATCH 49/54] added a space --- gamestonk_terminal/forex/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gamestonk_terminal/forex/README.md b/gamestonk_terminal/forex/README.md index 4e00e174a20..53aca19e373 100644 --- a/gamestonk_terminal/forex/README.md +++ b/gamestonk_terminal/forex/README.md @@ -19,7 +19,7 @@ This menu utilizes oanda to enable support for trading forex. * [closetrade] (#closetrade) * Close a trade by ID * [load] (#load) - *Specify an instrument to use + * Specify an instrument to use * [candles] (#candles) * Get a candlestick chart for the forex instrument * [price] (#price) From a88519dd4be7051edd5097788a8fb3d813d2a370 Mon Sep 17 00:00:00 2001 From: DJ Date: Tue, 20 Apr 2021 09:07:11 -0600 Subject: [PATCH 50/54] removed spaces --- gamestonk_terminal/forex/README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/gamestonk_terminal/forex/README.md b/gamestonk_terminal/forex/README.md index 53aca19e373..4e3f928c4f9 100644 --- a/gamestonk_terminal/forex/README.md +++ b/gamestonk_terminal/forex/README.md @@ -2,33 +2,33 @@ This menu utilizes oanda to enable support for trading forex. -* [summary] (#summary) +* [summary](#summary) * Display a summary of your account -* [calendar] (#calendar) +* [calendar](#calendar) * Get information about past or upcoming events which may impact the price -* [list] (#list) +* [list](#list) * List your order history -* [pending] (#pending) +* [pending](#pending) * Get information about pending orders -* [cancel] (#cancel) +* [cancel](#cancel) * Cancel a pending order by ID -* [positions] (#positions) +* [positions](#positions) * Get information about your positions -* [trades] (#trades) +* [trades](#trades) * See a list of open trades -* [closetrade] (#closetrade) +* [closetrade](#closetrade) * Close a trade by ID -* [load] (#load) +* [load](#load) * Specify an instrument to use -* [candles] (#candles) +* [candles](#candles) * Get a candlestick chart for the forex instrument -* [price] (#price) +* [price](#price) * Show the current price for the forex instrument -* [order] (#order) +* [order](#order) * Place a limit order -* [orderbook] (#orderbook) +* [orderbook](#orderbook) * Display the orderbook if Oanda provides one for the forex instrument -* [positionbook] (#positionbook) +* [positionbook](#positionbook) * Display the positionbook if Oanda provides one for the forex instrument ## summary From 23f12c294c11023dbff2f1090bd3e5e6d578739d Mon Sep 17 00:00:00 2001 From: DJ Date: Tue, 20 Apr 2021 09:08:42 -0600 Subject: [PATCH 51/54] Fix README --- gamestonk_terminal/forex/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gamestonk_terminal/forex/README.md b/gamestonk_terminal/forex/README.md index 4e3f928c4f9..079b5fb4d5d 100644 --- a/gamestonk_terminal/forex/README.md +++ b/gamestonk_terminal/forex/README.md @@ -73,21 +73,21 @@ Cancel a pending order by its order ID. * -i : Specify the order ID. You don't need to include this, simply include the order number. -##positions +## positions ```text usage: positions ``` Lists your open positions. The information includes the instrument, the current number of long units, the total profit/loss from long positions for the account, the unrealized profit/loss from long positions, the current number of short units, the total profit/loss from short positions for the account, and the unrealized profit/loss from short positions. -##trades +## trades ```text usage: trades ``` Lists the current open trades for your account. The information includes the ID, the instrument, the initial units opened for the trade, the current number of units open for the trade, it's entry price, and the unrealized profit/loss for the trade. -##closetrade +## closetrade ```text usage: closetrade [-i ORDERID] [-u UNITS] ``` @@ -97,7 +97,7 @@ Closes a trade by ID. Displays the order ID closed, the instrument, the number o * -i : Specify the order ID. You don't need to include this, simply include the order number. * -u : Specify the number of units to close for that trade. (default=all) -##load +## load ```text usage: load [-i INSTRUMENT] ``` @@ -106,7 +106,7 @@ Specify the instrument to use in the format EUR_USD * -i : Specify the instrument. You don't need to include this, simply include the instrument. -##candles +## candles ```text usage: candles [-g GRANULARITY] [-c CANDLECOUNT] ``` @@ -116,14 +116,14 @@ Retrieve a candlestick chart for the currently loaded instrument. Not a live cha * -g : Specify the granularity, which is the timeframe each candle represents. The available granularities are Seconds: S5, S10, S15, S30 Minutes: M1, M2, M4, M5, M10, M15, M30 Hours: H1, H2, H3, H4, H6, H8, H12 Day: D, Week: W Month: M. (default=D) * -c : Specify the number of candles to retrieve. (default=180) -##price +## price ```text usage: price ``` Gets the current price for the currently loaded instrument -##order +## order ```text usage: order -u UNITS -p PRICE ``` @@ -133,13 +133,13 @@ Places a limit order for the specified number of units at the specified price. B * -u : Specify the number of units for the order. * -p : Specify the price for the limit order. -##orderbook +## orderbook ```text usage: orderbook ``` Plots the current orderbook for loaded instrument if one is provided by Oanda. Not a live chart. -##positionbook +## positionbook ```text usage: positionbook ``` From 407d9d02a781991197d3864877ff49a515fbf014 Mon Sep 17 00:00:00 2001 From: DJ Date: Tue, 20 Apr 2021 17:23:38 -0600 Subject: [PATCH 52/54] Requested changes --- README.md | 2 ++ gamestonk_terminal/config_terminal.py | 2 +- gamestonk_terminal/forex/README.md | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7c13cbe50b2..7e6de2c164c 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,8 @@ These are the ones where a key is necessary: * Tradier: https://developer.tradier.com/getting_started * Oanda API: https://developer.oanda.com +When these are obtained, don't forget to update [config_terminal.py](/gamestonk_terminal/config_terminal.py). + Alternatively, you can also set them to the following environment variables: GT_API_KEY_ALPHAVANTAGE, GT_API_KEY_FINANCIALMODELINGPREP, GT_API_KEY_QUANDL, GT_API_REDDIT_CLIENT_ID, GT_API_REDDIT_CLIENT_SECRET, GT_API_REDDIT_USERNAME, GT_API_REDDIT_USER_AGENT, GT_API_REDDIT_PASSWORD, GT_API_TWITTER_KEY, GT_API_TWITTER_SECRET_KEY, GT_API_TWITTER_BEARER_TOKEN, GT_API_POLYGON_KEY, GT_FRED_API_KEY, GT_API_NEWS_TOKEN, GT_OANDA_TOKEN, GT_OANDA_ACCOUNT, GT_TRADIER_TOKEN. Example: diff --git a/gamestonk_terminal/config_terminal.py b/gamestonk_terminal/config_terminal.py index 874e1181edc..cc479934ad9 100644 --- a/gamestonk_terminal/config_terminal.py +++ b/gamestonk_terminal/config_terminal.py @@ -36,7 +36,7 @@ API_NEWS_TOKEN = os.getenv("GT_API_NEWS_TOKEN") or "REPLACE_ME" RH_USERNAME = os.getenv("GT_RH_USERNAME") or "REPLACE_ME" RH_PASSWORD = os.getenv("GT_RH_PASSWORD") or "REPLACE_ME" -# Oanda +# https://developer.oanda.com OANDA_ACCOUNT = os.getenv("GT_OANDA_ACCOUNT") or "REPLACE ME" OANDA_TOKEN = os.getenv("GT_OANDA_TOKEN") or "REPLACE ME" diff --git a/gamestonk_terminal/forex/README.md b/gamestonk_terminal/forex/README.md index 079b5fb4d5d..5c27d28a4fd 100644 --- a/gamestonk_terminal/forex/README.md +++ b/gamestonk_terminal/forex/README.md @@ -29,7 +29,7 @@ This menu utilizes oanda to enable support for trading forex. * [orderbook](#orderbook) * Display the orderbook if Oanda provides one for the forex instrument * [positionbook](#positionbook) - * Display the positionbook if Oanda provides one for the forex instrument + * Display the positionbook if Oanda provides one for the forex instrument ## summary ```text @@ -38,7 +38,7 @@ This menu utilizes oanda to enable support for trading forex. Displays current balance, net asset value, unrealized P/L, total P/L, count of open trades, how much margin is available, how much margin you've used, the margin call value, the percent of margin until closeout, and the total position value. -## calendar +## calendar ```text usage: calendar [-d DAYS] From 4a32b3a8b418059050b696bed83652fabfd7282f Mon Sep 17 00:00:00 2001 From: DJ Date: Wed, 21 Apr 2021 15:07:54 -0600 Subject: [PATCH 53/54] run linter --- terminal.py | 1 - 1 file changed, 1 deletion(-) diff --git a/terminal.py b/terminal.py index 1ad32666dac..368f078bcc3 100644 --- a/terminal.py +++ b/terminal.py @@ -35,7 +35,6 @@ from gamestonk_terminal.portfolio_optimization import po_controller from gamestonk_terminal.forex import fx_controller - # pylint: disable=too-many-statements,too-many-branches def main(): """ From 04330f2e3cc15837c521bcf070230a4538493d42 Mon Sep 17 00:00:00 2001 From: DJ Date: Wed, 21 Apr 2021 15:23:06 -0600 Subject: [PATCH 54/54] run poetry --- poetry.lock | 50 ++++++++++++++++++++++++++----- pyproject.toml | 1 + requirements-full.txt | 68 ++++++++++--------------------------------- requirements.txt | 3 +- 4 files changed, 61 insertions(+), 61 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1c9dc7b56d1..a5b4f1f7f8e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -837,7 +837,7 @@ idna = ">=2.0" [[package]] name = "importlib-metadata" -version = "4.0.0" +version = "4.0.1" description = "Read metadata from Python packages" category = "main" optional = false @@ -1496,6 +1496,14 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "oandapyv20" +version = "0.6.3" +description = "Python wrapper for the OANDA REST-V20 API" +category = "main" +optional = false +python-versions = "*" + [[package]] name = "oauthlib" version = "3.1.0" @@ -2099,7 +2107,7 @@ python-versions = "*" [[package]] name = "pyupgrade" -version = "2.12.0" +version = "2.13.0" description = "A tool to automatically upgrade syntax for newer versions." category = "dev" optional = false @@ -2954,7 +2962,7 @@ prediction = ["fbprophet", "tensorflow", "pmdarima", "transformers", "flair", "t [metadata] lock-version = "1.1" python-versions = "^3.6.8" -content-hash = "fa7c02ebec2d14a1c42f76f8e1328134975893c5f98afa879f2bc672675d6a7f" +content-hash = "2b31cd4c709b83d1a6f1d516036c84dc611cc487ae5d499905658454fa8dba40" [metadata.files] absl-py = [ @@ -3287,30 +3295,51 @@ entrypoints = [ ] ephem = [ {file = "ephem-3.7.7.1-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:0a6d6ed003383f702695094399728052981c567d423b25aa7163b3fd40de3d5a"}, + {file = "ephem-3.7.7.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3cbc4f643329c4f64de63584e636625fa9f11b50209386ed99cbf508abe19488"}, {file = "ephem-3.7.7.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:e73b9997d7b844fabff988d7f66c3bad4e76ec8975ec9c60ec988037bb5c04b1"}, {file = "ephem-3.7.7.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:7619297a1103f6b3dac0c609bfa7c31e17678fb1a02653347ec030487e412031"}, + {file = "ephem-3.7.7.1-cp27-cp27m-win32.whl", hash = "sha256:7756218a8b72ca50ee4f290b80828073412e7d7023389df5ead5d14839175bda"}, + {file = "ephem-3.7.7.1-cp27-cp27m-win_amd64.whl", hash = "sha256:b8f9fb5b4184523b98f6629c6c927aeeba80d61df21f07af2844cdae45e1a294"}, {file = "ephem-3.7.7.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:37fd1abbebb821381d2865064a6fa9484ffdcd2161cf92193da8d42da79aa7db"}, {file = "ephem-3.7.7.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:ff4e1584cc232d8bc4aea405346eeb8a24b0fbe84c6331d4edb5d4456d19f1c3"}, {file = "ephem-3.7.7.1-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c6081a9791e9de6511cddbeb9529bd5550fdb6f9e2a8dc9c2ec85911d67c6d11"}, + {file = "ephem-3.7.7.1-cp35-cp35m-macosx_10_9_x86_64.whl", hash = "sha256:d1d1ac8dc9be47dc6c7ec62800ca6557d03e8be3716c2a10ea0c8f1198c0a701"}, {file = "ephem-3.7.7.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:7f781d73e49caf7989c503a948471f1c8c1f738a450d557413999fb732f4dba3"}, {file = "ephem-3.7.7.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:f0959882bf73c8fe1cb801f42b0989e0ecb9d84b99eb8b8f70d61d16767a1e8a"}, + {file = "ephem-3.7.7.1-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:06029db6b26f1764ed009360de8530f6cd759411232b3001df1b69ba6afa871d"}, + {file = "ephem-3.7.7.1-cp35-cp35m-manylinux2014_s390x.whl", hash = "sha256:562baaeb3a46cf2248f97d2ad80092d747024e5dea62ebe50386a04b8e506c10"}, {file = "ephem-3.7.7.1-cp35-cp35m-win32.whl", hash = "sha256:2bcd953cced045e4232bcb71d60f6ba237f7944c880e83e74cc959449b192dee"}, {file = "ephem-3.7.7.1-cp35-cp35m-win_amd64.whl", hash = "sha256:fd0b922387df1ad8721f2fafd5dca51ce7105238f968e1532a177b781b8cae0e"}, {file = "ephem-3.7.7.1-cp36-cp36m-macosx_10_6_intel.whl", hash = "sha256:e10c2c8afbb9e927d46d9bf3562441da2173ff196790a75ad0e5c5ee920d5922"}, + {file = "ephem-3.7.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:592264e512b5658402e93903343eb26502c6f327df8a71847f3dcaea73908f32"}, {file = "ephem-3.7.7.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:8d586d970c42d62b1fc757a1c7c10679416a43e9e4a77391a05907159ce346f8"}, {file = "ephem-3.7.7.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:1a8ea6a045ff669011f8ecdeffadfc5eaa519fb56ab8dd40508910e9db5ec7b0"}, + {file = "ephem-3.7.7.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c25993d6554820ff11fdd8f2ddb75bf99e70db01327f09700bf8811201e9daef"}, + {file = "ephem-3.7.7.1-cp36-cp36m-manylinux2014_s390x.whl", hash = "sha256:9180d953ff4cbe89fc9ccc78aea73f290c0166b8ddb01e9c837b4639a7492ec8"}, {file = "ephem-3.7.7.1-cp36-cp36m-win32.whl", hash = "sha256:72fdb72c813fc9780726aa732eb2193bbe1663549265ca5a746899215ecfbffd"}, {file = "ephem-3.7.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f5af029f8814aa8617e73b2ddcad2bd1c682ca13c31ee3b6f5825dd5cb43e777"}, {file = "ephem-3.7.7.1-cp37-cp37m-macosx_10_6_intel.whl", hash = "sha256:e61c68f1c7d304a130bb6b88c1fca6600bbc1a1df6ca9a109b6eba4c6cbd3376"}, + {file = "ephem-3.7.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:fdf5f3ed8bd46ce2bf1145b26aec65a2c08ea7c97cf42b6b439af6e7bc6dc94d"}, {file = "ephem-3.7.7.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:31e2a17ca6f8d6bd44832574fea5461938ba034e52bf551e15e1a4c8f8c6c4fb"}, {file = "ephem-3.7.7.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:1ff5abefa230d5ab40846f1cb27d4f5583d46044fecf10e17bfb559bdc766a47"}, + {file = "ephem-3.7.7.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:5ef34e3cfea0683998e867d7b8de642081e8821fafbd5e1d2dcab503c119c4a2"}, + {file = "ephem-3.7.7.1-cp37-cp37m-manylinux2014_s390x.whl", hash = "sha256:e2aca9edb59ee2bb3c8c36f17177966bc762da04af994450c8ad61a20fb6acdd"}, {file = "ephem-3.7.7.1-cp37-cp37m-win32.whl", hash = "sha256:71ea3935c658ee184575fd4cc2aa3e7fd6d6bac4a1ccff9ee5549cee67ff0385"}, {file = "ephem-3.7.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:34fd228105b89fefd20608420861a75a184db1edeaf0056cf2f85d2a2f768aee"}, {file = "ephem-3.7.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:189fcaeacb44dc5a1684ad489542f126d99d4a85e87d2fe73864c902f0bc2c71"}, {file = "ephem-3.7.7.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:a5ddf46ced515e4b81dfd22e6408fd06a329c31c6abb10af48d392641c490b0e"}, {file = "ephem-3.7.7.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:3530bee3eb7458d86d4c399f51f5b73ad7ea0441f0b2fb03de41d623789935af"}, + {file = "ephem-3.7.7.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:77193de077e5fff453756144dee978e5fd6978c5c03ce3f67a09bb237e78f9d4"}, + {file = "ephem-3.7.7.1-cp38-cp38-manylinux2014_s390x.whl", hash = "sha256:095ad42e8df6a5c91e0aeb5a68d76c0800b0283f72fc5f76e6df370ae1fb5537"}, {file = "ephem-3.7.7.1-cp38-cp38-win32.whl", hash = "sha256:33ad1a71b204aff123250b04e0ff2d5c3b8e9c6c814bf2c675d7752ce5896c02"}, {file = "ephem-3.7.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:4bc5140cc7218a53ee6d949110f6ed413b8e3165294100515cd0fb29aa8e3637"}, + {file = "ephem-3.7.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7aa88d2a6d216c2044be2a3b3cb184e9c8a5022f12464220694017ea9d75209d"}, + {file = "ephem-3.7.7.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:1a9822bde93205c67fd3399b26868204b67d9e4442af21a764307e51ea6dc672"}, + {file = "ephem-3.7.7.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:15dd3dbb3864fb853388e39ac3c915c72873507f54ee6b3ede0033b03465d8ba"}, + {file = "ephem-3.7.7.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:5763919cdf38cd0488172711da504c3be003b7c104ccd2516cc496ff05f146ae"}, + {file = "ephem-3.7.7.1-cp39-cp39-manylinux2014_s390x.whl", hash = "sha256:cdea23a7caa1178b322e6e76484d40bd021672446ff51b512f35581e0f1b32b7"}, + {file = "ephem-3.7.7.1-cp39-cp39-win32.whl", hash = "sha256:c3cbc973b334892972445cec3deb99aafd0c57cb4e50679d5f12fc8c51b4e65f"}, + {file = "ephem-3.7.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:1af920c382980fe3566a31f36bd712973e1ca6049cbe07b4624214d5e1f144bc"}, {file = "ephem-3.7.7.1.tar.gz", hash = "sha256:36b51a8dc7cfdeb456dd6b8ab811accab8341b2d562ee3c6f4c86f6d3dbb984e"}, ] fbprophet = [ @@ -3491,8 +3520,8 @@ idna-ssl = [ {file = "idna-ssl-1.1.0.tar.gz", hash = "sha256:a933e3bb13da54383f9e8f35dc4f9cb9eb9b3b78c6b36f311254d6d0d92c6c7c"}, ] importlib-metadata = [ - {file = "importlib_metadata-4.0.0-py3-none-any.whl", hash = "sha256:19192b88d959336bfa6bdaaaef99aeafec179eca19c47c804e555703ee5f07ef"}, - {file = "importlib_metadata-4.0.0.tar.gz", hash = "sha256:2e881981c9748d7282b374b68e759c87745c25427b67ecf0cc67fb6637a1bff9"}, + {file = "importlib_metadata-4.0.1-py3-none-any.whl", hash = "sha256:d7eb1dea6d6a6086f8be21784cc9e3bcfa55872b52309bc5fad53a8ea444465d"}, + {file = "importlib_metadata-4.0.1.tar.gz", hash = "sha256:8c501196e49fb9df5df43833bdb1e4328f64847763ec8a50703148b73784d581"}, ] inflection = [ {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, @@ -3955,6 +3984,9 @@ numpy = [ {file = "numpy-1.19.5-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:a0d53e51a6cb6f0d9082decb7a4cb6dfb33055308c4c44f53103c073f649af73"}, {file = "numpy-1.19.5.zip", hash = "sha256:a76f502430dd98d7546e1ea2250a7360c065a5fdea52b2dffe8ae7180909b6f4"}, ] +oandapyv20 = [ + {file = "oandapyV20-0.6.3.tar.gz", hash = "sha256:173a56b41ab3a19315c2fbea6f9aa3f0c17f64ba84acff014d072c64c1844b28"}, +] oauthlib = [ {file = "oauthlib-3.1.0-py2.py3-none-any.whl", hash = "sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"}, {file = "oauthlib-3.1.0.tar.gz", hash = "sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889"}, @@ -4305,8 +4337,8 @@ pytz = [ {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, ] pyupgrade = [ - {file = "pyupgrade-2.12.0-py2.py3-none-any.whl", hash = "sha256:024fc6be99c2ccdbc1c9354ac0ab8d5b9e5bf69a5477384677b6c9d8f2ce63bf"}, - {file = "pyupgrade-2.12.0.tar.gz", hash = "sha256:eef4d77e44e57dfbae897a9a61543be1f34701dbbe51cc18462210021bd28dcf"}, + {file = "pyupgrade-2.13.0-py2.py3-none-any.whl", hash = "sha256:1cb5a15573fc63824b952bf7dde5b96f9672a1179ec779ac2d159a845da743c8"}, + {file = "pyupgrade-2.13.0.tar.gz", hash = "sha256:dc6b580b7bc82f357acb354bbba03d8d69da51425ecd5993a2651059bca80518"}, ] pywin32 = [ {file = "pywin32-300-cp35-cp35m-win32.whl", hash = "sha256:1c204a81daed2089e55d11eefa4826c05e604d27fe2be40b6bf8db7b6a39da63"}, @@ -4563,6 +4595,7 @@ scikit-learn = [ {file = "scikit_learn-0.24.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:c658432d8a20e95398f6bb95ff9731ce9dfa343fdf21eea7ec6a7edfacd4b4d9"}, {file = "scikit_learn-0.24.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:9dfa564ef27e8e674aa1cc74378416d580ac4ede1136c13dd555a87996e13422"}, {file = "scikit_learn-0.24.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:9c6097b6a9b2bafc5e0f31f659e6ab5e131383209c30c9e978c5b8abdac5ed2a"}, + {file = "scikit_learn-0.24.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:5580eba7345a4d3b097be2f067cc71a306c44bab19e8717a30361f279c929bea"}, {file = "scikit_learn-0.24.1-cp36-cp36m-win32.whl", hash = "sha256:7b04691eb2f41d2c68dbda8d1bd3cb4ef421bdc43aaa56aeb6c762224552dfb6"}, {file = "scikit_learn-0.24.1-cp36-cp36m-win_amd64.whl", hash = "sha256:1adf483e91007a87171d7ce58c34b058eb5dab01b5fee6052f15841778a8ecd8"}, {file = "scikit_learn-0.24.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:ddb52d088889f5596bc4d1de981f2eca106b58243b6679e4782f3ba5096fd645"}, @@ -4570,6 +4603,7 @@ scikit-learn = [ {file = "scikit_learn-0.24.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0567a2d29ad08af98653300c623bd8477b448fe66ced7198bef4ed195925f082"}, {file = "scikit_learn-0.24.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:99349d77f54e11f962d608d94dfda08f0c9e5720d97132233ebdf35be2858b2d"}, {file = "scikit_learn-0.24.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:83b21ff053b1ff1c018a2d24db6dd3ea339b1acfbaa4d9c881731f43748d8b3b"}, + {file = "scikit_learn-0.24.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:abe835a851610f87201819cb315f8d554e1a3e8128912783a31e87264ba5ffb7"}, {file = "scikit_learn-0.24.1-cp37-cp37m-win32.whl", hash = "sha256:c3deb3b19dd9806acf00cf0d400e84562c227723013c33abefbbc3cf906596e9"}, {file = "scikit_learn-0.24.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d54dbaadeb1425b7d6a66bf44bee2bb2b899fe3e8850b8e94cfb9c904dcb46d0"}, {file = "scikit_learn-0.24.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:3c4f07f47c04e81b134424d53c3f5e16dfd7f494e44fd7584ba9ce9de2c5e6c1"}, @@ -4577,6 +4611,7 @@ scikit-learn = [ {file = "scikit_learn-0.24.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:4ddd2b6f7449a5d539ff754fa92d75da22de261fd8fdcfb3596799fadf255101"}, {file = "scikit_learn-0.24.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:826b92bf45b8ad80444814e5f4ac032156dd481e48d7da33d611f8fe96d5f08b"}, {file = "scikit_learn-0.24.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:259ec35201e82e2db1ae2496f229e63f46d7f1695ae68eef9350b00dc74ba52f"}, + {file = "scikit_learn-0.24.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:9599a3f3bf33f73fed0fe06d1dfa4e6081365a58c1c807acb07271be0dce9733"}, {file = "scikit_learn-0.24.1-cp38-cp38-win32.whl", hash = "sha256:8772b99d683be8f67fcc04789032f1b949022a0e6880ee7b75a7ec97dbbb5d0b"}, {file = "scikit_learn-0.24.1-cp38-cp38-win_amd64.whl", hash = "sha256:ed9d65594948678827f4ff0e7ae23344e2f2b4cabbca057ccaed3118fdc392ca"}, {file = "scikit_learn-0.24.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:8aa1b3ac46b80eaa552b637eeadbbce3be5931e4b5002b964698e33a1b589e1e"}, @@ -4584,6 +4619,7 @@ scikit-learn = [ {file = "scikit_learn-0.24.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:087dfede39efb06ab30618f9ab55a0397f29c38d63cd0ab88d12b500b7d65fd7"}, {file = "scikit_learn-0.24.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:895dbf2030aa7337649e36a83a007df3c9811396b4e2fa672a851160f36ce90c"}, {file = "scikit_learn-0.24.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:9a24d1ccec2a34d4cd3f2a1f86409f3f5954cc23d4d2270ba0d03cf018aa4780"}, + {file = "scikit_learn-0.24.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:54be0a60a5a35005ad69c75902e0f5c9f699db4547ead427e97ef881c3242e6f"}, {file = "scikit_learn-0.24.1-cp39-cp39-win32.whl", hash = "sha256:fab31f48282ebf54dd69f6663cd2d9800096bad1bb67bbc9c9ac84eb77b41972"}, {file = "scikit_learn-0.24.1-cp39-cp39-win_amd64.whl", hash = "sha256:4562dcf4793e61c5d0f89836d07bc37521c3a1889da8f651e2c326463c4bd697"}, ] diff --git a/pyproject.toml b/pyproject.toml index a3bbf6762c7..45e9f245261 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,7 @@ tabulate = "^0.8.9" pyportfolioopt = "^1.4.1" selenium = "^3.141.0" python-coinmarketcap = "^0.2" +oandapyV20 = "^0.6.3" [tool.poetry.dev-dependencies] pytest = "^6.2.2" diff --git a/requirements-full.txt b/requirements-full.txt index 659e713d3c7..15e7172d368 100644 --- a/requirements-full.txt +++ b/requirements-full.txt @@ -5,7 +5,6 @@ ansiwrap==0.8.4; python_version >= "3.6" appdirs==1.4.4; python_version >= "3.6" appnope==0.1.2; platform_system == "Darwin" and python_version >= "3.6" and sys_platform == "darwin" argon2-cffi==20.1.0; python_version >= "3.6" -astunparse==1.6.3 async-generator==1.10; python_full_version >= "3.6.1" and python_version >= "3.6" async-timeout==3.0.1; python_full_version >= "3.5.3" and python_version >= "3.6" attrs==20.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" @@ -13,7 +12,6 @@ backcall==0.2.0; python_version >= "3.6" beautifulsoup4==4.9.3; python_version >= "3.5" black==20.8b1; python_version >= "3.6" bleach==3.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -bpemb==0.3.3; python_version >= "3.6" bs4==0.0.1 certifi==2020.12.5; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" cffi==1.14.5; implementation_name == "pypy" and python_version >= "3.6" @@ -26,38 +24,23 @@ cssselect==1.1.0; python_version >= "2.7" and python_full_version < "3.0.0" or p cvxpy==1.1.12; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" cycler==0.10.0; python_version >= "3.6" cython==0.29.17; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "darwin" or python_full_version >= "3.3.0" and python_version >= "3.6" and sys_platform == "darwin" -dataclasses==0.8; python_version >= "3.6" and python_version < "3.7" and python_full_version >= "3.6.2" +dataclasses==0.8; python_version >= "3.6" and python_version < "3.7" datetime==4.3; python_version >= "3.5" decorator==4.4.2; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" and python_version >= "3.6") defusedxml==0.7.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -deprecated==1.2.12; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" detecta==0.0.5; python_version >= "3.6" ecos==2.0.7.post1; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" entrypoints==0.3; python_version >= "3.6" -ephem==3.7.7.1; python_version >= "2.7" and python_version < "4" -fbprophet==0.6 -filelock==3.0.12; python_full_version >= "3.6.0" and python_version >= "3.6" finviz==1.4.1 finvizfinance==0.9.6; python_version >= "3.5" -flair==0.7; python_version >= "3.6" -flatbuffers==1.12 fredapi==0.4.3 -ftfy==6.0.1; python_version >= "3.6" fundamentalanalysis==0.2.7 -future==0.18.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" -gast==0.3.3; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" -gdown==3.12.2; python_version >= "3.6" -gensim==3.8.2; python_version >= "3.6" -google-auth-oauthlib==0.4.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" and python_version >= "3.6" -google-auth==1.29.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6" -google-pasta==0.2.0 -grpcio==1.32.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" h5py==2.10.0 hijri-converter==2.1.1; python_version >= "3.6" holidays==0.10.5.2 idna-ssl==1.1.0; python_version < "3.7" and python_version >= "3.6" idna==2.10; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version < "3.7" and python_version >= "3.6" and python_full_version >= "3.5.0" -importlib-metadata==4.0.0; python_version < "3.8" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" or python_full_version >= "3.2.0" and python_version >= "3.6" and python_version < "3.8") +importlib-metadata==4.0.1; python_version < "3.8" and python_version >= "3.6" inflection==0.5.1; python_version >= "3.5" ipykernel==5.5.3; python_version >= "3.6" ipython-genutils==0.2.0; python_full_version >= "3.6.1" and python_version >= "3.6" @@ -66,6 +49,7 @@ ipywidgets==7.6.3 iso8601==0.1.14 jedi==0.18.0; python_version >= "3.6" jinja2==2.11.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +joblib==1.0.1; python_version >= "3.6" jsonschema==3.2.0; python_version >= "3.6" jupyter-client==6.2.0; python_full_version >= "3.6.1" and python_version >= "3.6" jupyter-console==6.4.0; python_version >= "3.6" @@ -74,17 +58,12 @@ jupyter==1.0.0 jupyterlab-pygments==0.1.2; python_version >= "3.6" jupyterlab-widgets==1.0.0; python_version >= "3.6" kiwisolver==1.3.1; python_version >= "3.6" -konoha==4.6.3; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.6" korean-lunar-calendar==0.2.1 -langdetect==1.0.8; python_version >= "3.6" -lunarcalendar==0.0.9; python_version >= "2.7" and python_version < "4" -lxml==4.6.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" -markdown==3.3.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" and python_version >= "3.6" +lxml==4.6.3; python_version >= "3.5" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.5" markupsafe==1.1.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" matplotlib==3.3.4; python_version >= "3.6" mistune==0.8.4; python_version >= "3.6" more-itertools==8.7.0; python_version >= "3.5" -mpld3==0.3; python_version >= "3.6" mplfinance==0.12.7a12 msgpack==1.0.2 multidict==5.1.0; python_version >= "3.6" @@ -94,36 +73,33 @@ nbclient==0.5.3; python_full_version >= "3.6.1" and python_version >= "3.6" nbconvert==6.0.7; python_version >= "3.6" nbformat==5.1.3; python_full_version >= "3.6.1" and python_version >= "3.6" nest-asyncio==1.5.1; python_full_version >= "3.6.1" and python_version >= "3.6" -networkx==2.5.1; python_version >= "3.6" notebook==6.3.0; python_version >= "3.6" numpy==1.19.5; python_version >= "3.6" +oandapyv20==0.6.3 oauthlib==3.1.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" -opt-einsum==3.3.0; python_version >= "3.5" osqp==0.6.2.post0; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" -overrides==3.1.0; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.6" -packaging==20.9; python_full_version >= "3.6.0" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") +packaging==20.9; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" pandas-ta==0.2.45b pandas==1.1.5; python_full_version >= "3.6.1" pandocfilters==1.4.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" papermill==2.3.3; python_version >= "3.6" parso==0.8.2; python_version >= "3.6" pathspec==0.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" +patsy==0.5.1; python_version >= "3.6" pexpect==4.8.0; sys_platform != "win32" and python_version >= "3.6" pickleshare==0.7.5; python_version >= "3.6" pillow==8.2.0; python_version >= "3.6" plotly==4.14.3 +pmdarima==1.8.0; python_version >= "3.6" praw==7.2.0; python_version >= "3.6" and python_version < "4.0" prawcore==2.0.0; python_version >= "3.6" and python_version < "4.0" prettytable==2.1.0; python_version >= "3.6" prometheus-client==0.10.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" prompt-toolkit==3.0.18; python_full_version >= "3.6.1" -protobuf==3.15.8; python_full_version >= "3.6.0" and python_version >= "3.6" psaw==0.0.12; python_version >= "3" ptyprocess==0.7.0; os_name != "nt" and python_version >= "3.6" and sys_platform != "win32" py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" and implementation_name == "pypy" or implementation_name == "pypy" and python_version >= "3.6" and python_full_version >= "3.4.0" pyally==1.1.2 -pyasn1-modules==0.2.8; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" -pyasn1==0.4.8; python_version >= "3.5" and python_full_version < "3.0.0" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") or python_version >= "3.5" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") and python_full_version >= "3.6.0" pycoingecko==1.4.1 pycparser==2.20; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" pygments==2.8.1; python_version >= "3.6" @@ -131,13 +107,12 @@ pymeeus==0.5.11; python_version >= "3.5" pyobjc-core==7.1; python_version >= "3.6" and sys_platform == "darwin" pyobjc-framework-cocoa==7.1; python_version >= "3.6" and sys_platform == "darwin" pyotp==2.6.0; python_version >= "3" -pyparsing==2.4.7; python_full_version >= "3.6.0" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") +pyparsing==2.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" pyportfolioopt==1.4.1; python_full_version >= "3.6.1" and python_full_version < "4.0.0" pyrsistent==0.14.11 pysocks==1.7.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" -pystan==2.19.1.1 python-coinmarketcap==0.2 -python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "2.7" and python_full_version < "3.0.0" and python_version < "4" or python_version >= "2.7" and python_version < "4" and python_full_version >= "3.3.0") +python-dateutil==2.8.1; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") python-dotenv==0.17.0; python_version >= "3" pytrends==4.7.3 pytz==2021.1; python_full_version >= "3.6.1" and python_version >= "3.5" @@ -151,47 +126,35 @@ qtpy==1.9.0; python_version >= "3.6" quandl==3.6.1; python_version >= "3.5" rapidfuzz==1.4.1; python_version >= "3.5" regex==2020.11.13 -requests-oauthlib==1.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" +requests-oauthlib==1.3.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") retrying==1.3.3 robin-stocks==2.0.3; python_version >= "3" -rsa==4.7.2; python_version >= "3.5" and python_version < "4" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6") -sacremoses==0.0.45; python_full_version >= "3.6.0" and python_version >= "3.6" scikit-learn==0.24.1; python_version >= "3.6" scipy==1.5.4; python_version >= "3.6" screeninfo==0.6.7 scs==2.1.3; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" seaborn==0.11.1; python_version >= "3.6" -segtok==1.5.10; python_version >= "3.6" selenium==3.141.0 send2trash==1.5.0; python_version >= "3.6" -sentencepiece==0.1.91; python_full_version >= "3.6.0" and python_version >= "3.6" -setuptools-git==1.2 -six==1.15.0; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.6.0") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") -smart-open==5.0.0; python_version >= "3.6" +six==1.15.0; python_full_version >= "3.6.1" and python_version >= "3.6" and (python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0") and (python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6") and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6") soupsieve==2.2.1; python_version >= "3.6" -sqlitedict==1.7.0; python_version >= "3.6" statsmodels==0.12.2; python_version >= "3.6" tabulate==0.8.9 tenacity==7.0.0; python_version >= "3.6" -tensorboard-data-server==0.6.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" and python_version >= "3.6" -tensorboard-plugin-wit==1.8.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" -tensorboard==2.5.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.2.0" -tensorflow-estimator==2.4.0 -tensorflow==2.4.1 termcolor==1.1.0 terminado==0.9.4; python_version >= "3.6" testpath==0.4.4; python_version >= "3.6" textwrap3==0.9.2; python_version >= "3.6" +threadpoolctl==2.1.0; python_version >= "3.6" timeseriescrossvalidation @ git+https://github.com/DidierRLopes/TimeSeriesCrossValidation.git@master toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version >= "3.6" tornado==6.1; python_full_version >= "3.6.1" and python_version >= "3.6" -tqdm==4.60.0; python_full_version >= "3.6.0" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6") +tqdm==4.60.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" tradingview-ta==3.2.3; python_version >= "3.5" traitlets==4.3.3; python_full_version >= "3.6.1" and python_version >= "3.6" -transformers==3.5.1; python_full_version >= "3.6.0" typed-ast==1.4.3; python_version >= "3.6" -typing-extensions==3.7.4.3; python_full_version >= "3.6.2" and python_version >= "3.6" and python_version < "3.8" +typing-extensions==3.7.4.3; python_version < "3.8" and python_version >= "3.6" update-checker==0.18.0; python_version >= "3.6" and python_version < "4.0" urllib3==1.26.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.6" user-agent==0.1.9 @@ -199,7 +162,6 @@ wcwidth==0.2.5; python_version >= "3.6" and python_full_version >= "3.6.1" webencodings==0.5.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6" websocket-client==0.58.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.4.0" websockets==8.1; python_full_version >= "3.6.1" -werkzeug==1.0.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" widgetsnbextension==3.5.1 yarl==1.6.3; python_version >= "3.6" yfinance==0.1.59 diff --git a/requirements.txt b/requirements.txt index bf0985ecfa5..15e7172d368 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,7 +40,7 @@ hijri-converter==2.1.1; python_version >= "3.6" holidays==0.10.5.2 idna-ssl==1.1.0; python_version < "3.7" and python_version >= "3.6" idna==2.10; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version < "3.7" and python_version >= "3.6" and python_full_version >= "3.5.0" -importlib-metadata==4.0.0; python_version < "3.8" and python_version >= "3.6" +importlib-metadata==4.0.1; python_version < "3.8" and python_version >= "3.6" inflection==0.5.1; python_version >= "3.5" ipykernel==5.5.3; python_version >= "3.6" ipython-genutils==0.2.0; python_full_version >= "3.6.1" and python_version >= "3.6" @@ -75,6 +75,7 @@ nbformat==5.1.3; python_full_version >= "3.6.1" and python_version >= "3.6" nest-asyncio==1.5.1; python_full_version >= "3.6.1" and python_version >= "3.6" notebook==6.3.0; python_version >= "3.6" numpy==1.19.5; python_version >= "3.6" +oandapyv20==0.6.3 oauthlib==3.1.0; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" osqp==0.6.2.post0; python_full_version >= "3.6.1" and python_full_version < "4.0.0" and python_version >= "3.5" packaging==20.9; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"