diff --git a/gamestonk_terminal/oanda/oanda_controller.py b/gamestonk_terminal/oanda/oanda_controller.py index 4befdbd1c10..8794f2f4656 100644 --- a/gamestonk_terminal/oanda/oanda_controller.py +++ b/gamestonk_terminal/oanda/oanda_controller.py @@ -1,5 +1,5 @@ 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,21 +17,28 @@ class OandaController: "help", "q", "quit", - "hist", "price", "summary", - "list"] + "list", + "orderbook", + "positionbook", + "limitorder", + "cancel", + "positions", + "closetrade", + "trades", + "candles", + "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", - choices=self.CHOICES, + "cmd", + choices=self.CHOICES, ) @staticmethod @@ -39,13 +46,22 @@ 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 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 @@ -58,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""" @@ -72,46 +90,77 @@ 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 limit 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_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 8843d337526..fa3db84bca5 100644 --- a/gamestonk_terminal/oanda/oanda_functions.py +++ b/gamestonk_terminal/oanda/oanda_functions.py @@ -1,24 +1,49 @@ 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 +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, 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,13 +72,34 @@ 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, } - request = orders.OrderList(accountID, parameters) response = client.request(request) - for i in range(0, int(count), 1): + 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(ns_parser.count), 1): try: order_id = response["orders"][i]["id"] instrument = response["orders"][i]["instrument"] @@ -66,12 +112,299 @@ 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, 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, + ) + 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": { + "price": ns_parser.price, + "instrument": ns_parser.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): + 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 + + +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}\n") + 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 + + +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) + + +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:]) diff --git a/terminal.py b/terminal.py index 35638648e2d..706ec39a2e1 100644 --- a/terminal.py +++ b/terminal.py @@ -33,6 +33,8 @@ 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 # warnings.simplefilter("always")