mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 04:31:13 +00:00
Redo some forex menu with basic commands (#903)
* Make oanda menu and add some alpha vantage forex * ignore csv for codespell * Added load and candle plot for forex * Add hugo stuff for forex commands * fix merge error Co-authored-by: James Maslek <james@Jamess-MacBook-Pro.local>
This commit is contained in:
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
- run: pip install types-pytz types-requests types-termcolor types-tabulate types-PyYAML types-python-dateutil
|
||||
- run: bandit -r . || true
|
||||
- run: black --check .
|
||||
- run: codespell --ignore-words-list=pres,hsi,hist,mape,ba,ser,coo --quiet-level=2 --skip=".git" --skip="*.yaml" --skip="*.ipynb" --skip="*.json" --skip="*.js" --skip="*.css" --skip="*.scss" --skip="*.html" --skip="*.ini" --skip="*.lock"
|
||||
- run: codespell --ignore-words-list=pres,hsi,hist,mape,ba,ser,coo --quiet-level=2 --skip=".git" --skip="*.yaml" --skip="*.ipynb" --skip="*.json" --skip="*.js" --skip="*.css" --skip="*.scss" --skip="*.html" --skip="*.ini" --skip="*.lock" --skip="*.csv"
|
||||
- run: flake8 . --count --ignore=E203,W503 --max-line-length=122 --show-source --statistics
|
||||
- run: mypy --ignore-missing-imports --exclude '/setup\.py$' .
|
||||
- run: shopt -s globstar && pyupgrade --py36-plus **/*.py
|
||||
|
||||
2
.pre-commit-config.yaml
vendored
2
.pre-commit-config.yaml
vendored
@@ -21,7 +21,7 @@ repos:
|
||||
hooks:
|
||||
- id: codespell
|
||||
entry: codespell
|
||||
args: [ '--ignore-words-list=pres,coo,navagation,ser,buil,operatio,mape,ba,hist,yeld,ether,hsi', '--quiet-level=2', '--skip=".git,*.html,*.yaml,*.json,./tests"' ]
|
||||
args: [ '--ignore-words-list=pres,coo,navagation,ser,buil,operatio,mape,ba,hist,yeld,ether,hsi', '--quiet-level=2', '--skip=".git,*.html,*.yaml,*.json,./tests,*.csv"' ]
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: 'v0.812'
|
||||
hooks:
|
||||
|
||||
160
gamestonk_terminal/forex/av_forex_currencies.csv
vendored
Normal file
160
gamestonk_terminal/forex/av_forex_currencies.csv
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
currency code,currency name
|
||||
AED,United Arab Emirates Dirham
|
||||
AFN,Afghan Afghani
|
||||
ALL,Albanian Lek
|
||||
AMD,Armenian Dram
|
||||
ANG,Netherlands Antillean Guilder
|
||||
AOA,Angolan Kwanza
|
||||
ARS,Argentine Peso
|
||||
AUD,Australian Dollar
|
||||
AWG,Aruban Florin
|
||||
AZN,Azerbaijani Manat
|
||||
BAM,Bosnia-Herzegovina Convertible Mark
|
||||
BBD,Barbadian Dollar
|
||||
BDT,Bangladeshi Taka
|
||||
BGN,Bulgarian Lev
|
||||
BHD,Bahraini Dinar
|
||||
BIF,Burundian Franc
|
||||
BMD,Bermudan Dollar
|
||||
BND,Brunei Dollar
|
||||
BOB,Bolivian Boliviano
|
||||
BRL,Brazilian Real
|
||||
BSD,Bahamian Dollar
|
||||
BTN,Bhutanese Ngultrum
|
||||
BWP,Botswanan Pula
|
||||
BZD,Belize Dollar
|
||||
CAD,Canadian Dollar
|
||||
CDF,Congolese Franc
|
||||
CHF,Swiss Franc
|
||||
CLF,Chilean Unit of Account UF
|
||||
CLP,Chilean Peso
|
||||
CNH,Chinese Yuan Offshore
|
||||
CNY,Chinese Yuan
|
||||
COP,Colombian Peso
|
||||
CUP,Cuban Peso
|
||||
CVE,Cape Verdean Escudo
|
||||
CZK,Czech Republic Koruna
|
||||
DJF,Djiboutian Franc
|
||||
DKK,Danish Krone
|
||||
DOP,Dominican Peso
|
||||
DZD,Algerian Dinar
|
||||
EGP,Egyptian Pound
|
||||
ERN,Eritrean Nakfa
|
||||
ETB,Ethiopian Birr
|
||||
EUR,Euro
|
||||
FJD,Fijian Dollar
|
||||
FKP,Falkland Islands Pound
|
||||
GBP,British Pound Sterling
|
||||
GEL,Georgian Lari
|
||||
GHS,Ghanaian Cedi
|
||||
GIP,Gibraltar Pound
|
||||
GMD,Gambian Dalasi
|
||||
GNF,Guinean Franc
|
||||
GTQ,Guatemalan Quetzal
|
||||
GYD,Guyanaese Dollar
|
||||
HKD,Hong Kong Dollar
|
||||
HNL,Honduran Lempira
|
||||
HRK,Croatian Kuna
|
||||
HTG,Haitian Gourde
|
||||
HUF,Hungarian Forint
|
||||
ICP,Internet Computer
|
||||
IDR,Indonesian Rupiah
|
||||
ILS,Israeli New Sheqel
|
||||
INR,Indian Rupee
|
||||
IQD,Iraqi Dinar
|
||||
IRR,Iranian Rial
|
||||
ISK,Icelandic Krona
|
||||
JEP,Jersey Pound
|
||||
JMD,Jamaican Dollar
|
||||
JOD,Jordanian Dinar
|
||||
JPY,Japanese Yen
|
||||
KES,Kenyan Shilling
|
||||
KGS,Kyrgystani Som
|
||||
KHR,Cambodian Riel
|
||||
KMF,Comorian Franc
|
||||
KPW,North Korean Won
|
||||
KRW,South Korean Won
|
||||
KWD,Kuwaiti Dinar
|
||||
KYD,Cayman Islands Dollar
|
||||
KZT,Kazakhstani Tenge
|
||||
LAK,Laotian Kip
|
||||
LBP,Lebanese Pound
|
||||
LKR,Sri Lankan Rupee
|
||||
LRD,Liberian Dollar
|
||||
LSL,Lesotho Loti
|
||||
LYD,Libyan Dinar
|
||||
MAD,Moroccan Dirham
|
||||
MDL,Moldovan Leu
|
||||
MGA,Malagasy Ariary
|
||||
MKD,Macedonian Denar
|
||||
MMK,Myanma Kyat
|
||||
MNT,Mongolian Tugrik
|
||||
MOP,Macanese Pataca
|
||||
MRO,Mauritanian Ouguiya (pre-2018)
|
||||
MRU,Mauritanian Ouguiya
|
||||
MUR,Mauritian Rupee
|
||||
MVR,Maldivian Rufiyaa
|
||||
MWK,Malawian Kwacha
|
||||
MXN,Mexican Peso
|
||||
MYR,Malaysian Ringgit
|
||||
MZN,Mozambican Metical
|
||||
NAD,Namibian Dollar
|
||||
NGN,Nigerian Naira
|
||||
NOK,Norwegian Krone
|
||||
NPR,Nepalese Rupee
|
||||
NZD,New Zealand Dollar
|
||||
OMR,Omani Rial
|
||||
PAB,Panamanian Balboa
|
||||
PEN,Peruvian Nuevo Sol
|
||||
PGK,Papua New Guinean Kina
|
||||
PHP,Philippine Peso
|
||||
PKR,Pakistani Rupee
|
||||
PLN,Polish Zloty
|
||||
PYG,Paraguayan Guarani
|
||||
QAR,Qatari Rial
|
||||
RON,Romanian Leu
|
||||
RSD,Serbian Dinar
|
||||
RUB,Russian Ruble
|
||||
RUR,Old Russian Ruble
|
||||
RWF,Rwandan Franc
|
||||
SAR,Saudi Riyal
|
||||
SBDf,Solomon Islands Dollar
|
||||
SCR,Seychellois Rupee
|
||||
SDG,Sudanese Pound
|
||||
SDR,Special Drawing Rights
|
||||
SEK,Swedish Krona
|
||||
SGD,Singapore Dollar
|
||||
SHIB,Shiba Inu
|
||||
SHP,Saint Helena Pound
|
||||
SLL,Sierra Leonean Leone
|
||||
SOS,Somali Shilling
|
||||
SRD,Surinamese Dollar
|
||||
SYP,Syrian Pound
|
||||
SZL,Swazi Lilangeni
|
||||
THB,Thai Baht
|
||||
TJS,Tajikistani Somoni
|
||||
TMT,Turkmenistani Manat
|
||||
TND,Tunisian Dinar
|
||||
TOP,Tongan Pa'anga
|
||||
TRY,Turkish Lira
|
||||
TTD,Trinidad and Tobago Dollar
|
||||
TWD,New Taiwan Dollar
|
||||
TZS,Tanzanian Shilling
|
||||
UAH,Ukrainian Hryvnia
|
||||
UGX,Ugandan Shilling
|
||||
USD,United States Dollar
|
||||
UYU,Uruguayan Peso
|
||||
UZS,Uzbekistan Som
|
||||
VND,Vietnamese Dong
|
||||
VUV,Vanuatu Vatu
|
||||
WBTC,Wrapped Bitcoin
|
||||
WST,Samoan Tala
|
||||
XAF,CFA Franc BEAC
|
||||
XCD,East Caribbean Dollar
|
||||
XDR,Special Drawing Rights
|
||||
XOF,CFA Franc BCEAO
|
||||
XPF,CFP Franc
|
||||
YER,Yemeni Rial
|
||||
ZAR,South African Rand
|
||||
ZMW,Zambian Kwacha
|
||||
ZWL,Zimbabwean Dollar
|
||||
|
114
gamestonk_terminal/forex/av_model.py
Normal file
114
gamestonk_terminal/forex/av_model.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""AlphaVantage FX Model"""
|
||||
|
||||
import argparse
|
||||
from typing import Dict
|
||||
import os
|
||||
import pandas as pd
|
||||
import requests
|
||||
from gamestonk_terminal import config_terminal as cfg
|
||||
|
||||
|
||||
def check_valid_forex_currency(fx_symbol: str) -> str:
|
||||
"""Check if given symbol is supported on alphavantage
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fx_symbol : str
|
||||
Symbol to check
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Currency symbol
|
||||
|
||||
Raises
|
||||
------
|
||||
argparse.ArgumentTypeError
|
||||
Symbol not valid on alphavantage
|
||||
"""
|
||||
path = os.path.join(os.path.dirname(__file__), "av_forex_currencies.csv")
|
||||
if fx_symbol.upper() in list(pd.read_csv(path)["currency code"]):
|
||||
return fx_symbol.upper()
|
||||
|
||||
raise argparse.ArgumentTypeError(
|
||||
f"{fx_symbol.upper()} not found in alphavantage supported currency codes. "
|
||||
)
|
||||
|
||||
|
||||
def get_quote(to_symbol: str, from_symbol: str) -> Dict:
|
||||
"""Get current exchange rate quote from alpha vantage
|
||||
|
||||
Parameters
|
||||
----------
|
||||
to_symbol : str
|
||||
To forex symbol
|
||||
from_symbol : str
|
||||
From forex symbol
|
||||
|
||||
Returns
|
||||
-------
|
||||
Dict
|
||||
Dictionary of exchange rate
|
||||
"""
|
||||
url = "https://www.alphavantage.co/query?function=CURRENCY_EXCHANGE_RAT"
|
||||
url += f"E&from_currency={from_symbol}&to_currency={to_symbol}&apikey={cfg.API_KEY_ALPHAVANTAGE}"
|
||||
r = requests.get(url)
|
||||
if r.status_code != 200:
|
||||
return {}
|
||||
return r.json()
|
||||
|
||||
|
||||
def get_historical(
|
||||
to_symbol: str,
|
||||
from_symbol: str,
|
||||
resolution: str = "d",
|
||||
interval: int = 5,
|
||||
start_date: str = "",
|
||||
) -> pd.DataFrame:
|
||||
"""Get historical forex data
|
||||
|
||||
Parameters
|
||||
----------
|
||||
to_symbol : str
|
||||
To forex symbol
|
||||
from_symbol : str
|
||||
From forex symbol
|
||||
resolution : str, optional
|
||||
Resolution of data. Can be "i", "d", "w", "m" for intraday, daily, weekly or monthly
|
||||
interval : int, optional
|
||||
Interval for intraday data
|
||||
start_date : str, optional
|
||||
Start date for data.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pd.DataFrame
|
||||
Historical data for forex pair
|
||||
"""
|
||||
d_res = {"i": "FX_INTRADAY", "d": "FX_DAILY", "w": "FX_WEEKLY", "m": "FX_MONTHLY"}
|
||||
|
||||
url = f"https://www.alphavantage.co/query?function={d_res[resolution]}&from_symbol={from_symbol}"
|
||||
url += f"&to_symbol={to_symbol}&outputsize=full&apikey={cfg.API_KEY_ALPHAVANTAGE}"
|
||||
if resolution == "i":
|
||||
url += f"&interval={interval}min"
|
||||
|
||||
r = requests.get(url)
|
||||
if r.status_code != 200:
|
||||
return pd.DataFrame()
|
||||
|
||||
key = list(r.json().keys())[1]
|
||||
|
||||
df = pd.DataFrame.from_dict(r.json()[key], orient="index")
|
||||
if start_date and resolution != "i":
|
||||
df = df[df.index > start_date]
|
||||
|
||||
df = df.rename(
|
||||
columns={
|
||||
"1. open": "Open",
|
||||
"2. high": "High",
|
||||
"3. low": "Low",
|
||||
"4. close": "Close",
|
||||
}
|
||||
)
|
||||
df.index = pd.DatetimeIndex(df.index)
|
||||
return df.astype(float)
|
||||
80
gamestonk_terminal/forex/av_view.py
Normal file
80
gamestonk_terminal/forex/av_view.py
Normal file
@@ -0,0 +1,80 @@
|
||||
"""AlphaVantage Forex View"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import pandas as pd
|
||||
from tabulate import tabulate
|
||||
import mplfinance as mpf
|
||||
import matplotlib.pyplot as plt
|
||||
from gamestonk_terminal.forex import av_model
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
|
||||
|
||||
def display_quote(to_symbol: str, from_symbol: str):
|
||||
"""Display current forex pair exchange rate
|
||||
|
||||
Parameters
|
||||
----------
|
||||
to_symbol : str
|
||||
To symbol
|
||||
from_symbol : str
|
||||
From forex symbol
|
||||
"""
|
||||
quote = av_model.get_quote(to_symbol, from_symbol)
|
||||
|
||||
if not quote:
|
||||
print("Quote not pulled from AlphaVantage. Check API key.")
|
||||
return
|
||||
|
||||
df = pd.DataFrame.from_dict(quote)
|
||||
df.index = df.index.to_series().apply(lambda x: x[3:]).values
|
||||
df = df.iloc[[0, 2, 5, 4, 7, 8]]
|
||||
if gtff.USE_TABULATE_DF:
|
||||
print(tabulate(df, tablefmt="fancy_grid"))
|
||||
else:
|
||||
print(df.to_string())
|
||||
print("")
|
||||
|
||||
|
||||
def display_candle(data: pd.DataFrame, to_symbol: str, from_symbol: str):
|
||||
"""Show candle plot for fx data
|
||||
|
||||
Parameters
|
||||
----------
|
||||
data : pd.DataFrame
|
||||
Loaded fx historical data
|
||||
to_symbol : str
|
||||
To forex symbol
|
||||
from_symbol : str
|
||||
From forex symbol
|
||||
"""
|
||||
mc = mpf.make_marketcolors(
|
||||
up="green",
|
||||
down="red",
|
||||
edge="black",
|
||||
wick="black",
|
||||
volume="in",
|
||||
ohlc="i",
|
||||
)
|
||||
|
||||
s = mpf.make_mpf_style(marketcolors=mc, gridstyle=":", y_on_right=True)
|
||||
mpf.plot(
|
||||
data,
|
||||
type="candle",
|
||||
mav=(20, 50),
|
||||
volume=False,
|
||||
title=f"\n{to_symbol}/{from_symbol}",
|
||||
xrotation=10,
|
||||
style=s,
|
||||
figratio=(10, 7),
|
||||
figscale=1.10,
|
||||
figsize=(plot_autoscale()),
|
||||
update_width_config=dict(
|
||||
candle_linewidth=1.0,
|
||||
candle_width=0.8,
|
||||
),
|
||||
)
|
||||
if gtff.USE_ION:
|
||||
plt.ion()
|
||||
plt.show()
|
||||
print("")
|
||||
@@ -1,15 +1,15 @@
|
||||
import argparse
|
||||
import os
|
||||
from datetime import timedelta, datetime
|
||||
from typing import List
|
||||
|
||||
# import pandas as pd
|
||||
import pandas as pd
|
||||
from prompt_toolkit.completion import NestedCompleter
|
||||
|
||||
from gamestonk_terminal import config_terminal as cfg
|
||||
from colorama import Style
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.forex import fx_view
|
||||
from gamestonk_terminal.forex.oanda import oanda_controller
|
||||
from gamestonk_terminal.forex import av_view, av_model
|
||||
|
||||
# from gamestonk_terminal.forex.behavioural_analysis import ba_controller
|
||||
|
||||
# from gamestonk_terminal.forex.exploratory_data_analysis import eda_controller
|
||||
from gamestonk_terminal.helper_funcs import (
|
||||
@@ -17,14 +17,12 @@ from gamestonk_terminal.helper_funcs import (
|
||||
MENU_GO_BACK,
|
||||
MENU_QUIT,
|
||||
MENU_RESET,
|
||||
parse_known_args_and_warn,
|
||||
try_except,
|
||||
valid_date,
|
||||
)
|
||||
from gamestonk_terminal.menu import session
|
||||
|
||||
# from gamestonk_terminal.stocks.due_diligence import news_view, reddit_view
|
||||
|
||||
|
||||
account = cfg.OANDA_ACCOUNT
|
||||
|
||||
|
||||
class ForexController:
|
||||
"""Forex Controller class"""
|
||||
@@ -38,32 +36,11 @@ class ForexController:
|
||||
"reset",
|
||||
]
|
||||
|
||||
CHOICES_COMMANDS = [
|
||||
"price",
|
||||
"summary",
|
||||
"list",
|
||||
"orderbook",
|
||||
"positionbook",
|
||||
"order",
|
||||
"load",
|
||||
"cancel",
|
||||
"positions",
|
||||
"closetrade",
|
||||
"trades",
|
||||
"candles",
|
||||
"pending",
|
||||
"calendar",
|
||||
# "news",
|
||||
# "reddit",
|
||||
]
|
||||
|
||||
# CHOICES_MENUS = [
|
||||
# "eda",
|
||||
# "ba",
|
||||
# ]
|
||||
CHOICES_COMMANDS = ["select", "load", "quote", "candle"]
|
||||
CHOICES_MENUS = ["oanda"]
|
||||
|
||||
CHOICES += CHOICES_COMMANDS
|
||||
# CHOICES += CHOICES_MENUS
|
||||
CHOICES += CHOICES_MENUS
|
||||
|
||||
def __init__(self):
|
||||
"""Construct Data"""
|
||||
@@ -72,44 +49,37 @@ class ForexController:
|
||||
"cmd",
|
||||
choices=self.CHOICES,
|
||||
)
|
||||
self.instrument = None
|
||||
self.from_symbol = "USD"
|
||||
self.to_symbol = ""
|
||||
self.data = pd.DataFrame()
|
||||
|
||||
def print_help(self):
|
||||
"""Print help"""
|
||||
print("\nForex Mode:")
|
||||
print(" cls clear screen")
|
||||
print(" ?/help show this menu again")
|
||||
print(" q quit this menu and goes back to main menu")
|
||||
print(" quit quit to abandon program")
|
||||
print(" reset reset terminal and reload configs")
|
||||
print("")
|
||||
print(" summary shows account summary")
|
||||
print(" calendar show calendar")
|
||||
print(" list list order history")
|
||||
print(" pending get information on pending orders")
|
||||
print(" cancel cancel a pending order by ID -i order ID")
|
||||
print(" positions get open positions")
|
||||
print(" trades list open trades")
|
||||
print(" closetrade close a trade by id")
|
||||
print("")
|
||||
print(f"Loaded instrument: {self.instrument if self.instrument else ''}")
|
||||
print("")
|
||||
print(" load load an instrument to use")
|
||||
if self.instrument:
|
||||
print(" candles show candles")
|
||||
print(" price shows price for selected instrument")
|
||||
print(" order place limit order -u # of units -p price")
|
||||
print(" orderbook print orderbook")
|
||||
print(" positionbook print positionbook")
|
||||
print(" news print news [News API]")
|
||||
print(
|
||||
" reddit search reddit for posts about the loaded instrument"
|
||||
)
|
||||
# print("")
|
||||
# print(
|
||||
# "> ba behavioural analysis, from: reddit, stocktwits, twitter, google"
|
||||
# )
|
||||
print("")
|
||||
dim_bool = self.from_symbol and self.to_symbol
|
||||
help_str = f"""
|
||||
>>> FOREX <<<
|
||||
|
||||
What would you like to do?
|
||||
cls clear screen
|
||||
?/help show this menu again
|
||||
q quit this menu and goes back to main menu
|
||||
quit quit to abandon program
|
||||
reset reset terminal and reload configs
|
||||
select select fx pair
|
||||
|
||||
To: {None or self.to_symbol}
|
||||
From: {None or self.from_symbol}
|
||||
{Style.DIM if not dim_bool else ""}
|
||||
AlphaVantage (API Key required):
|
||||
quote get last quote
|
||||
load get historical data
|
||||
candle show candle plot for loaded data
|
||||
{Style.RESET_ALL}
|
||||
Brokerages:
|
||||
> oanda access oanda menu
|
||||
|
||||
"""
|
||||
print(help_str)
|
||||
|
||||
def switch(self, an_input: str):
|
||||
"""Process and dispatch input
|
||||
@@ -159,59 +129,147 @@ class ForexController:
|
||||
"""Process Reset command - reset the program"""
|
||||
return MENU_RESET
|
||||
|
||||
def call_price(self, other_args):
|
||||
"""Process Price Command"""
|
||||
fx_view.get_fx_price(account, self.instrument, other_args)
|
||||
@try_except
|
||||
def call_select(self, other_args: List[str]):
|
||||
"""Process select command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="select",
|
||||
description="Select Forex pair in the form of TO -f FROM",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--to",
|
||||
help="To currency",
|
||||
type=av_model.check_valid_forex_currency,
|
||||
dest="to_symbol",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--from",
|
||||
help="From currency",
|
||||
type=av_model.check_valid_forex_currency,
|
||||
dest="from_symbol",
|
||||
default=None,
|
||||
)
|
||||
|
||||
def call_load(self, other_args):
|
||||
self.instrument = fx_view.load(other_args)
|
||||
if (
|
||||
other_args
|
||||
and "-f" not in other_args[0]
|
||||
and "--from" not in other_args[0]
|
||||
and "-t" not in other_args
|
||||
and "-h" not in other_args
|
||||
):
|
||||
other_args.insert(0, "-t")
|
||||
|
||||
def call_summary(self, other_args):
|
||||
"""Process account summary command"""
|
||||
fx_view.get_account_summary(account, other_args)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
self.to_symbol = ns_parser.to_symbol
|
||||
if ns_parser.from_symbol:
|
||||
self.from_symbol = ns_parser.from_symbol
|
||||
print("")
|
||||
|
||||
def call_orderbook(self, other_args):
|
||||
"""Process Oanda Order Book"""
|
||||
fx_view.get_order_book(self.instrument, other_args)
|
||||
@try_except
|
||||
def call_load(self, other_args: List[str]):
|
||||
"""Process select command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="load",
|
||||
description="Load historical exchange rate data.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--resolution",
|
||||
choices=["i", "d", "w", "m"],
|
||||
default="d",
|
||||
help="Resolution of data. Can be intraday, daily, weekly or monthly",
|
||||
dest="resolution",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--interval",
|
||||
choices=[1, 5, 15, 30, 60],
|
||||
default="5",
|
||||
help="Interval of intraday data. Can be 1, 5, 15, 30 or 60.",
|
||||
dest="interval",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--start_date",
|
||||
default=(datetime.now() - timedelta(days=366)),
|
||||
type=valid_date,
|
||||
help="Start date of data.",
|
||||
dest="start_date",
|
||||
)
|
||||
|
||||
def call_positionbook(self, other_args):
|
||||
"""Process Oanda Position Book"""
|
||||
fx_view.get_position_book(self.instrument, other_args)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
|
||||
def call_list(self, other_args: List[str]):
|
||||
"""Process list orders command"""
|
||||
fx_view.list_orders(account, other_args)
|
||||
if not self.to_symbol or not self.from_symbol:
|
||||
print(
|
||||
"Make sure both a to symbol and a from symbol are supplied using <select> \n"
|
||||
)
|
||||
return
|
||||
|
||||
def call_order(self, other_args: List[str]):
|
||||
"""Place limit order"""
|
||||
fx_view.create_order(account, self.instrument, other_args)
|
||||
self.data = av_model.get_historical(
|
||||
to_symbol=self.to_symbol,
|
||||
from_symbol=self.from_symbol,
|
||||
resolution=ns_parser.resolution,
|
||||
interval=ns_parser.interval,
|
||||
start_date=ns_parser.start_date.strftime("%Y-%m-%d"),
|
||||
)
|
||||
print("")
|
||||
|
||||
def call_cancel(self, other_args: List[str]):
|
||||
"""Cancel pending order by ID"""
|
||||
fx_view.cancel_pending_order(account, other_args)
|
||||
@try_except
|
||||
def call_candle(self, other_args: List[str]):
|
||||
"""Process quote command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="candle",
|
||||
description="Show candle for loaded fx data",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
if self.data.empty:
|
||||
print("No forex historical data loaded. Load first using <load>.")
|
||||
return
|
||||
|
||||
def call_positions(self, other_args):
|
||||
"""Get Open Positions"""
|
||||
fx_view.get_open_positions(account, other_args)
|
||||
av_view.display_candle(self.data, self.to_symbol, self.from_symbol)
|
||||
|
||||
def call_pending(self, other_args):
|
||||
"""See up to 25 pending orders"""
|
||||
fx_view.get_pending_orders(account, other_args)
|
||||
@try_except
|
||||
def call_quote(self, other_args: List[str]):
|
||||
"""Process quote command"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="quote",
|
||||
description="Get current exchange rate quote",
|
||||
)
|
||||
|
||||
def call_closetrade(self, other_args: List[str]):
|
||||
"""Close a trade by id"""
|
||||
fx_view.close_trade(account, other_args)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
|
||||
def call_candles(self, other_args: List[str]):
|
||||
fx_view.show_candles(account, self.instrument, other_args)
|
||||
if not self.to_symbol or not self.from_symbol:
|
||||
print(
|
||||
"Make sure both a to symbol and a from symbol are supplied using <select> \n"
|
||||
)
|
||||
return
|
||||
|
||||
def call_trades(self, other_args):
|
||||
"""List open trades"""
|
||||
fx_view.get_open_trades(account, other_args)
|
||||
av_view.display_quote(self.to_symbol, self.from_symbol)
|
||||
|
||||
def call_calendar(self, other_args: List[str]):
|
||||
"""Call calendar"""
|
||||
fx_view.calendar(self.instrument, other_args)
|
||||
# pylint: disable=inconsistent-return-statements
|
||||
def call_oanda(self, _):
|
||||
ret = oanda_controller.menu()
|
||||
if ret:
|
||||
return True
|
||||
self.print_help()
|
||||
|
||||
# TODO: Add news and reddit commands back
|
||||
|
||||
|
||||
221
gamestonk_terminal/forex/oanda/oanda_controller.py
Normal file
221
gamestonk_terminal/forex/oanda/oanda_controller.py
Normal file
@@ -0,0 +1,221 @@
|
||||
"""Oanda Controller"""
|
||||
__docformat__ = "numpy"
|
||||
import argparse
|
||||
import os
|
||||
from typing import List
|
||||
|
||||
from prompt_toolkit.completion import NestedCompleter
|
||||
|
||||
from gamestonk_terminal import config_terminal as cfg
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.forex.oanda import oanda_view
|
||||
|
||||
# from gamestonk_terminal.forex.exploratory_data_analysis import eda_controller
|
||||
from gamestonk_terminal.helper_funcs import (
|
||||
get_flair,
|
||||
MENU_GO_BACK,
|
||||
MENU_QUIT,
|
||||
MENU_RESET,
|
||||
)
|
||||
from gamestonk_terminal.menu import session
|
||||
|
||||
|
||||
account = cfg.OANDA_ACCOUNT
|
||||
|
||||
|
||||
class OandaController:
|
||||
|
||||
CHOICES = [
|
||||
"cls",
|
||||
"?",
|
||||
"help",
|
||||
"q",
|
||||
"quit",
|
||||
"reset",
|
||||
]
|
||||
|
||||
CHOICES_COMMANDS = [
|
||||
"price",
|
||||
"summary",
|
||||
"list",
|
||||
"orderbook",
|
||||
"positionbook",
|
||||
"order",
|
||||
"load",
|
||||
"cancel",
|
||||
"positions",
|
||||
"closetrade",
|
||||
"trades",
|
||||
"candles",
|
||||
"pending",
|
||||
"calendar",
|
||||
# "news",
|
||||
# "reddit",
|
||||
]
|
||||
CHOICES += CHOICES_COMMANDS
|
||||
|
||||
def __init__(self):
|
||||
"""Construct Data"""
|
||||
self.fx_parser = argparse.ArgumentParser(add_help=False, prog="oanda")
|
||||
self.fx_parser.add_argument(
|
||||
"cmd",
|
||||
choices=self.CHOICES,
|
||||
)
|
||||
self.instrument = None
|
||||
|
||||
def print_help(self):
|
||||
"""Print help"""
|
||||
print(" ?/help show this menu again")
|
||||
print(" q quit this menu and goes back to main menu")
|
||||
print(" quit quit to abandon program")
|
||||
print(" reset reset terminal and reload configs")
|
||||
print("")
|
||||
print(" summary shows account summary")
|
||||
print(" calendar show calendar")
|
||||
print(" list list order history")
|
||||
print(" pending get information on pending orders")
|
||||
print(" cancel cancel a pending order by ID -i order ID")
|
||||
print(" positions get open positions")
|
||||
print(" trades list open trades")
|
||||
print(" closetrade close a trade by id")
|
||||
print("")
|
||||
print(f"Loaded instrument: {self.instrument if self.instrument else ''}")
|
||||
print("")
|
||||
print(" load load an instrument to use")
|
||||
if self.instrument:
|
||||
print(" candles show candles")
|
||||
print(" price shows price for selected instrument")
|
||||
print(" order place limit order -u # of units -p price")
|
||||
print(" orderbook print orderbook")
|
||||
print(" positionbook print positionbook")
|
||||
print("")
|
||||
|
||||
def switch(self, an_input: str):
|
||||
"""Process and dispatch input
|
||||
|
||||
Returns
|
||||
-------
|
||||
MENU_GO_BACK, MENU_QUIT, MENU_RESET
|
||||
MENU_GO_BACK - Show main context menu again
|
||||
MENU_QUIT - Quit terminal
|
||||
MENU_RESET - Reset terminal and go back to same previous menu
|
||||
"""
|
||||
|
||||
# Empty command
|
||||
if not an_input:
|
||||
print("")
|
||||
return None
|
||||
|
||||
(known_args, other_args) = self.fx_parser.parse_known_args(an_input.split())
|
||||
|
||||
# Help menu again
|
||||
if known_args.cmd == "?":
|
||||
self.print_help()
|
||||
return None
|
||||
|
||||
# Clear screen
|
||||
if known_args.cmd == "cls":
|
||||
os.system("cls||clear")
|
||||
return None
|
||||
|
||||
return getattr(
|
||||
self, "call_" + known_args.cmd, lambda: "command not recognized!"
|
||||
)(other_args)
|
||||
|
||||
def call_help(self, _):
|
||||
"""Process Help Command"""
|
||||
self.print_help()
|
||||
|
||||
def call_q(self, _):
|
||||
"""Process Q command - quit the menu"""
|
||||
return MENU_GO_BACK
|
||||
|
||||
def call_quit(self, _):
|
||||
"""Process Quit command - exit the program"""
|
||||
return MENU_QUIT
|
||||
|
||||
def call_reset(self, _):
|
||||
"""Process Reset command - reset the program"""
|
||||
return MENU_RESET
|
||||
|
||||
def call_price(self, other_args):
|
||||
"""Process Price Command"""
|
||||
oanda_view.get_fx_price(account, self.instrument, other_args)
|
||||
|
||||
def call_load(self, other_args):
|
||||
self.instrument = oanda_view.load(other_args)
|
||||
|
||||
def call_summary(self, other_args):
|
||||
"""Process account summary command"""
|
||||
oanda_view.get_account_summary(account, other_args)
|
||||
|
||||
def call_orderbook(self, other_args):
|
||||
"""Process Oanda Order Book"""
|
||||
oanda_view.get_order_book(self.instrument, other_args)
|
||||
|
||||
def call_positionbook(self, other_args):
|
||||
"""Process Oanda Position Book"""
|
||||
oanda_view.get_position_book(self.instrument, other_args)
|
||||
|
||||
def call_list(self, other_args: List[str]):
|
||||
"""Process list orders command"""
|
||||
oanda_view.list_orders(account, other_args)
|
||||
|
||||
def call_order(self, other_args: List[str]):
|
||||
"""Place limit order"""
|
||||
oanda_view.create_order(account, self.instrument, other_args)
|
||||
|
||||
def call_cancel(self, other_args: List[str]):
|
||||
"""Cancel pending order by ID"""
|
||||
oanda_view.cancel_pending_order(account, other_args)
|
||||
|
||||
def call_positions(self, other_args):
|
||||
"""Get Open Positions"""
|
||||
oanda_view.get_open_positions(account, other_args)
|
||||
|
||||
def call_pending(self, other_args):
|
||||
"""See up to 25 pending orders"""
|
||||
oanda_view.get_pending_orders(account, other_args)
|
||||
|
||||
def call_closetrade(self, other_args: List[str]):
|
||||
"""Close a trade by id"""
|
||||
oanda_view.close_trade(account, other_args)
|
||||
|
||||
def call_candles(self, other_args: List[str]):
|
||||
oanda_view.show_candles(account, self.instrument, other_args)
|
||||
|
||||
def call_trades(self, other_args):
|
||||
"""List open trades"""
|
||||
oanda_view.get_open_trades(account, other_args)
|
||||
|
||||
def call_calendar(self, other_args: List[str]):
|
||||
"""Call calendar"""
|
||||
oanda_view.calendar(self.instrument, other_args)
|
||||
|
||||
|
||||
def menu():
|
||||
"""Forex Menu"""
|
||||
oanda_controller = OandaController()
|
||||
oanda_controller.call_help(None)
|
||||
while True:
|
||||
if session and gtff.USE_PROMPT_TOOLKIT:
|
||||
completer = NestedCompleter.from_nested_dict(
|
||||
{c: None for c in oanda_controller.CHOICES}
|
||||
)
|
||||
|
||||
an_input = session.prompt(
|
||||
f"{get_flair()} (forex)(oanda)> ",
|
||||
completer=completer,
|
||||
)
|
||||
else:
|
||||
an_input = input(f"{get_flair()} (forex)(oanda)> ")
|
||||
|
||||
try:
|
||||
process_input = oanda_controller.switch(an_input)
|
||||
|
||||
if process_input is not None:
|
||||
return process_input
|
||||
|
||||
except SystemExit:
|
||||
print("The command selected doesn't exit\n")
|
||||
continue
|
||||
2
gamestonk_terminal/forex/oanda/oanda_model.py
Normal file
2
gamestonk_terminal/forex/oanda/oanda_model.py
Normal file
@@ -0,0 +1,2 @@
|
||||
"""Oanda Model"""
|
||||
__docformat__ = "numpy"
|
||||
917
gamestonk_terminal/forex/oanda/oanda_view.py
Normal file
917
gamestonk_terminal/forex/oanda/oanda_view.py
Normal file
@@ -0,0 +1,917 @@
|
||||
"""Oanda View"""
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.ticker as mticker
|
||||
import mplfinance as mpf
|
||||
import pandas as pd
|
||||
import pandas_ta as ta
|
||||
import seaborn as sns
|
||||
from oandapyV20 import API
|
||||
from oandapyV20.endpoints import (
|
||||
accounts,
|
||||
forexlabs,
|
||||
instruments,
|
||||
orders,
|
||||
positions,
|
||||
pricing,
|
||||
trades,
|
||||
)
|
||||
from oandapyV20.exceptions import V20Error
|
||||
|
||||
from gamestonk_terminal import config_plot as cfgPlot
|
||||
from gamestonk_terminal import config_terminal as cfg
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.helper_funcs import (
|
||||
check_non_negative,
|
||||
parse_known_args_and_warn,
|
||||
plot_autoscale,
|
||||
)
|
||||
|
||||
client = API(access_token=cfg.OANDA_TOKEN, environment=cfg.OANDA_ACCOUNT_TYPE)
|
||||
account = cfg.OANDA_ACCOUNT
|
||||
|
||||
|
||||
def get_fx_price(accountID, instrument, other_args: List[str]):
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="price",
|
||||
description="Get price for selected instrument.",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
|
||||
try:
|
||||
parameters = {"instruments": instrument}
|
||||
request = pricing.PricingInfo(accountID=accountID, params=parameters)
|
||||
response = client.request(request)
|
||||
bid = response["prices"][0]["bids"][0]["price"]
|
||||
ask = response["prices"][0]["asks"][0]["price"]
|
||||
print(instrument + " Bid: " + bid)
|
||||
print(instrument + " Ask: " + ask)
|
||||
print("")
|
||||
|
||||
except V20Error as e:
|
||||
# pylint: disable=W0123
|
||||
d_error = eval(e.msg)
|
||||
print(d_error["errorMessage"], "\n")
|
||||
|
||||
|
||||
def get_account_summary(accountID, other_args: List[str]):
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="summary",
|
||||
description="Print some information about your account.",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
|
||||
try:
|
||||
request = accounts.AccountSummary(accountID=accountID)
|
||||
response = client.request(request)
|
||||
|
||||
df_summary = pd.DataFrame(
|
||||
[
|
||||
{"Type": "Balance", "Value": response["account"]["balance"]},
|
||||
{"Type": "NAV", "Value": response["account"]["NAV"]},
|
||||
{
|
||||
"Type": "Unrealized P/L",
|
||||
"Value": response["account"]["unrealizedPL"],
|
||||
},
|
||||
{"Type": "Total P/L", "Value": response["account"]["pl"]},
|
||||
{
|
||||
"Type": "Open Trade Count",
|
||||
"Value": response["account"]["openTradeCount"],
|
||||
},
|
||||
{
|
||||
"Type": "Margin Available",
|
||||
"Value": response["account"]["marginAvailable"],
|
||||
},
|
||||
{"Type": "Margin Used", "Value": response["account"]["marginUsed"]},
|
||||
{
|
||||
"Type": "Margin Closeout",
|
||||
"Value": response["account"]["marginCloseoutNAV"],
|
||||
},
|
||||
{
|
||||
"Type": "Margin Closeout Percent",
|
||||
"Value": response["account"]["marginCloseoutPercent"],
|
||||
},
|
||||
{
|
||||
"Type": "Margin Closeout Position Value",
|
||||
"Value": response["account"]["marginCloseoutPositionValue"],
|
||||
},
|
||||
]
|
||||
)
|
||||
|
||||
print(df_summary.to_string(index=False, header=False))
|
||||
|
||||
print("")
|
||||
except V20Error as e:
|
||||
# pylint: disable=W0123
|
||||
d_error = eval(e.msg)
|
||||
print(d_error["errorMessage"], "\n")
|
||||
|
||||
|
||||
def list_orders(accountID, other_args: List[str]):
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="list",
|
||||
description="List order history",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--state",
|
||||
dest="state",
|
||||
action="store",
|
||||
default="ALL",
|
||||
required=False,
|
||||
help="Select state for order list. ",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--count",
|
||||
dest="count",
|
||||
action="store",
|
||||
default=20,
|
||||
required=False,
|
||||
help="the number of orders to retrieve ",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
|
||||
parameters = {}
|
||||
parameters["state"] = ns_parser.state.upper()
|
||||
parameters["count"] = ns_parser.count
|
||||
|
||||
try:
|
||||
request = orders.OrderList(accountID, parameters)
|
||||
response = client.request(request)
|
||||
|
||||
df = pd.DataFrame.from_dict(response["orders"])
|
||||
df = df[["id", "instrument", "units", "price", "state", "type"]]
|
||||
print(df)
|
||||
print("")
|
||||
|
||||
except KeyError:
|
||||
print("No orders were found\n")
|
||||
except V20Error as e:
|
||||
# pylint: disable=W0123
|
||||
d_error = eval(e.msg)
|
||||
print(d_error["errorMessage"], "\n")
|
||||
|
||||
|
||||
def get_order_book(instrument, other_args):
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="orderbook",
|
||||
description="Plot the orderbook for the instrument if Oanda provides one.",
|
||||
)
|
||||
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
parameters = {"bucketWidth": "1"}
|
||||
try:
|
||||
instrument = format_instrument(instrument, "_")
|
||||
request = instruments.InstrumentsOrderBook(
|
||||
instrument=instrument, params=parameters
|
||||
)
|
||||
response = client.request(request)
|
||||
df = pd.DataFrame.from_dict(response["orderBook"]["buckets"])
|
||||
pd.set_option("display.max_rows", None)
|
||||
df = df.take(range(527, 727, 1))
|
||||
book_plot(df, instrument, "Order Book")
|
||||
print("")
|
||||
|
||||
except V20Error as e:
|
||||
# pylint: disable=W0123
|
||||
d_error = eval(e.msg)
|
||||
print(d_error["errorMessage"], "\n")
|
||||
|
||||
|
||||
def get_position_book(instrument, other_args):
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="positionbook",
|
||||
description="Plot the positionbook for the instrument if Oanda provides one.",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
|
||||
try:
|
||||
instrument = format_instrument(instrument, "_")
|
||||
request = instruments.InstrumentsPositionBook(instrument=instrument)
|
||||
response = client.request(request)
|
||||
df = pd.DataFrame.from_dict(response["positionBook"]["buckets"])
|
||||
pd.set_option("display.max_rows", None)
|
||||
df = df.take(range(219, 415, 1))
|
||||
book_plot(df, instrument, "Position Book")
|
||||
print("")
|
||||
|
||||
except V20Error as e:
|
||||
# pylint: disable=W0123
|
||||
d_error = eval(e.msg)
|
||||
print(d_error["errorMessage"], "\n")
|
||||
|
||||
|
||||
def create_order(accountID, instrument, other_args: List[str]):
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="order",
|
||||
description="Create order",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-u",
|
||||
"--unit",
|
||||
dest="units",
|
||||
action="store",
|
||||
type=int,
|
||||
default=0,
|
||||
required=True,
|
||||
help="The number of units to place in the order request. Positive for "
|
||||
+ "a long position and negative for a short position ",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--price",
|
||||
dest="price",
|
||||
action="store",
|
||||
type=check_non_negative,
|
||||
required=True,
|
||||
help="The price to set for the limit order. ",
|
||||
)
|
||||
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
if "JPY" in instrument or "THB" in instrument or "HUF" in instrument:
|
||||
ns_parser.price = round(ns_parser.price, 3)
|
||||
else:
|
||||
ns_parser.price = round(ns_parser.price, 5)
|
||||
|
||||
data = {
|
||||
"order": {
|
||||
"price": ns_parser.price,
|
||||
"instrument": instrument,
|
||||
"units": ns_parser.units,
|
||||
"type": "LIMIT",
|
||||
"timeInForce": "GTC",
|
||||
"positionFill": "DEFAULT",
|
||||
}
|
||||
}
|
||||
try:
|
||||
request = orders.OrderCreate(accountID, data)
|
||||
response = client.request(request)
|
||||
order_data = []
|
||||
order_data.append(
|
||||
{
|
||||
"Order ID": response["orderCreateTransaction"]["id"],
|
||||
"Instrument": response["orderCreateTransaction"]["instrument"],
|
||||
"Price": response["orderCreateTransaction"]["price"],
|
||||
"Units": response["orderCreateTransaction"]["units"],
|
||||
}
|
||||
)
|
||||
|
||||
df = pd.DataFrame.from_dict(order_data)
|
||||
print(df.to_string(index=False))
|
||||
print("")
|
||||
|
||||
except V20Error as e:
|
||||
# pylint: disable=W0123
|
||||
d_error = eval(e.msg)
|
||||
print(d_error["errorMessage"], "\n")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def cancel_pending_order(accountID, other_args: List[str]):
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="cancel",
|
||||
description="Cancel Pending Order ",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--id",
|
||||
dest="orderID",
|
||||
action="store",
|
||||
type=str,
|
||||
help="The pending order ID to cancel ",
|
||||
)
|
||||
if other_args:
|
||||
if "-" not in other_args[0]:
|
||||
other_args.insert(0, "-i")
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
try:
|
||||
request = orders.OrderCancel(accountID, ns_parser.orderID)
|
||||
response = client.request(request)
|
||||
order_id = response["orderCancelTransaction"]["orderID"]
|
||||
print(f"Order {order_id} canceled.")
|
||||
print("")
|
||||
except V20Error as e:
|
||||
# pylint: disable=W0123
|
||||
d_error = eval(e.msg)
|
||||
print(d_error["errorMessage"], "\n")
|
||||
|
||||
|
||||
def get_pending_orders(accountID, other_args):
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="pending",
|
||||
description="Gets information about pending orders.",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
|
||||
try:
|
||||
request = orders.OrdersPending(accountID)
|
||||
response = client.request(request)
|
||||
pending_data = []
|
||||
for i in range(len(response["orders"])):
|
||||
pending_data.append(
|
||||
{
|
||||
"Order ID": response["orders"][i]["id"],
|
||||
"Instrument": response["orders"][i]["instrument"],
|
||||
"Price": response["orders"][i]["price"],
|
||||
"Units": response["orders"][i]["units"],
|
||||
"Time Created": response["orders"][i]["createTime"][:10]
|
||||
+ " "
|
||||
+ response["orders"][i]["createTime"][11:19],
|
||||
"Time In Force": response["orders"][i]["timeInForce"],
|
||||
}
|
||||
)
|
||||
|
||||
df = pd.DataFrame.from_dict(pending_data)
|
||||
if not pending_data:
|
||||
print("No pending orders")
|
||||
else:
|
||||
print(df.to_string(index=False))
|
||||
print("")
|
||||
|
||||
except V20Error as e:
|
||||
# pylint: disable=W0123
|
||||
d_error = eval(e.msg)
|
||||
print(d_error["errorMessage"], "\n")
|
||||
|
||||
|
||||
def get_open_positions(accountID, other_args):
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="positions",
|
||||
description="Gets information about open positions.",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
try:
|
||||
request = positions.OpenPositions(accountID)
|
||||
response = client.request(request)
|
||||
position_data = []
|
||||
for i in range(len(response["positions"])):
|
||||
position_data.append(
|
||||
{
|
||||
"Instrument": response["positions"][i]["instrument"],
|
||||
"Long Units": response["positions"][i]["long"]["units"],
|
||||
"Total Long P/L": response["positions"][i]["long"]["units"],
|
||||
"Unrealized Long P/L": response["positions"][i]["long"][
|
||||
"unrealizedPL"
|
||||
],
|
||||
"Short Units": response["positions"][i]["short"]["units"],
|
||||
"Total Short P/L": response["positions"][i]["short"]["pl"],
|
||||
"Short Unrealized P/L": response["positions"][i]["short"][
|
||||
"unrealizedPL"
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
df = pd.DataFrame.from_dict(position_data)
|
||||
print(df.to_string(index=False))
|
||||
print("")
|
||||
|
||||
except V20Error as e:
|
||||
# pylint: disable=W0123
|
||||
d_error = eval(e.msg)
|
||||
print(d_error["errorMessage"], "\n")
|
||||
|
||||
|
||||
def get_open_trades(accountID, other_args):
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="trades",
|
||||
description="Gets information about open trades.",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
|
||||
try:
|
||||
request = trades.OpenTrades(accountID)
|
||||
response = client.request(request)
|
||||
try:
|
||||
df = pd.DataFrame.from_dict(response["trades"])
|
||||
df = df[
|
||||
[
|
||||
"id",
|
||||
"instrument",
|
||||
"initialUnits",
|
||||
"currentUnits",
|
||||
"price",
|
||||
"unrealizedPL",
|
||||
]
|
||||
]
|
||||
df = df.rename(
|
||||
columns={
|
||||
"id": "ID",
|
||||
"instrument": "Instrument",
|
||||
"initialUnits": "Initial Units",
|
||||
"currentUnits": "Current Units",
|
||||
"price": "Entry Price",
|
||||
"unrealizedPL": "Unrealized P/L",
|
||||
}
|
||||
)
|
||||
|
||||
print(df.to_string(index=False))
|
||||
print("")
|
||||
|
||||
except KeyError:
|
||||
print("No trades were found")
|
||||
print("")
|
||||
except V20Error as e:
|
||||
msg_length = len(e.msg)
|
||||
msg = e.msg[17 : msg_length - 2]
|
||||
print(msg)
|
||||
print("")
|
||||
|
||||
|
||||
def close_trade(accountID, other_args: List[str]):
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="closetrade",
|
||||
description="Close a trade by id.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--id",
|
||||
dest="orderID",
|
||||
action="store",
|
||||
type=str,
|
||||
required=False,
|
||||
help="The Trade ID to close. ",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-u",
|
||||
"--units",
|
||||
dest="units",
|
||||
action="store",
|
||||
required=False,
|
||||
help="The number of units on the trade to close. If not set it "
|
||||
+ "defaults to all units. ",
|
||||
)
|
||||
if other_args:
|
||||
if "-i" not in other_args[0]:
|
||||
other_args.insert(0, "-i")
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
|
||||
data = {}
|
||||
if ns_parser.units is not None:
|
||||
data["units"] = ns_parser.units
|
||||
try:
|
||||
request = trades.TradeClose(accountID, ns_parser.orderID, data)
|
||||
response = client.request(request)
|
||||
|
||||
close_data = []
|
||||
close_data.append(
|
||||
{
|
||||
"OrderID": response["orderCreateTransaction"]["tradeClose"]["tradeID"],
|
||||
"Instrument": response["orderFillTransaction"]["instrument"],
|
||||
"Units": response["orderCreateTransaction"]["units"],
|
||||
"Price": response["orderFillTransaction"]["price"],
|
||||
"P/L": response["orderFillTransaction"]["pl"],
|
||||
}
|
||||
)
|
||||
df = pd.DataFrame.from_dict(close_data)
|
||||
print(df.to_string(index=False))
|
||||
print("")
|
||||
|
||||
except V20Error as e:
|
||||
# pylint: disable=W0123
|
||||
d_error = eval(e.msg)
|
||||
print(d_error["errorMessage"], "\n")
|
||||
except Exception as e:
|
||||
print(e, "\n")
|
||||
|
||||
|
||||
def show_candles(accountID, instrument, other_args: List[str]):
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="candles",
|
||||
description="Display Candle Data",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-g",
|
||||
"--granularity",
|
||||
dest="granularity",
|
||||
action="store",
|
||||
type=str,
|
||||
default="D",
|
||||
required=False,
|
||||
help="The timeframe to get for the candle chart (Seconds: S5, S10, S15, S30 "
|
||||
+ "Minutes: M1, M2, M4, M5, M10, M15, M30 Hours: H1, H2, H3, H4, H6, H8, H12 "
|
||||
+ "Day (default): D, Week: W Month: M",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--count",
|
||||
dest="candlecount",
|
||||
action="store",
|
||||
default=180,
|
||||
type=int,
|
||||
required=False,
|
||||
help="The number of candles to retrieve. Default:180 ",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-a",
|
||||
"--ad",
|
||||
dest="ad",
|
||||
action="store_true",
|
||||
help="Adds ad (Accumulation/Distribution Indicator) to the chart",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-b",
|
||||
"--bbands",
|
||||
dest="bbands",
|
||||
action="store_true",
|
||||
help="Adds Bollinger Bands to the chart",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-C",
|
||||
"--cci",
|
||||
dest="cci",
|
||||
action="store_true",
|
||||
help="Adds cci (Commodity Channel Index) to the chart",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--ema",
|
||||
dest="ema",
|
||||
action="store_true",
|
||||
help="Adds ema (Exponential Moving Average) to the chart",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--obv",
|
||||
dest="obv",
|
||||
action="store_true",
|
||||
help="Adds obv (On Balance Volume) to the chart",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--rsi",
|
||||
dest="rsi",
|
||||
action="store_true",
|
||||
help="Adds rsi (Relative Strength Index) to the chart",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--sma",
|
||||
dest="sma",
|
||||
action="store_true",
|
||||
help="Adds sma (Simple Moving Average) to the chart",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--vwap",
|
||||
dest="vwap",
|
||||
action="store_true",
|
||||
help="Adds vwap (Volume Weighted Average Price) to the chart",
|
||||
)
|
||||
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
|
||||
parameters = {}
|
||||
parameters["granularity"] = ns_parser.granularity.upper()
|
||||
parameters["count"] = ns_parser.candlecount
|
||||
try:
|
||||
instrument = format_instrument(instrument, "_")
|
||||
df = get_candles_dataframe(accountID, instrument, parameters)
|
||||
|
||||
plots_to_add, legends, subplot_legends = add_plots(df, ns_parser)
|
||||
|
||||
if gtff.USE_ION:
|
||||
plt.ion()
|
||||
|
||||
# pylint: disable=W0612
|
||||
fig, ax = mpf.plot(
|
||||
df,
|
||||
type="candle",
|
||||
style="charles",
|
||||
volume=True,
|
||||
title=f"{instrument} {ns_parser.granularity}",
|
||||
returnfig=True,
|
||||
addplot=plots_to_add,
|
||||
)
|
||||
|
||||
ax[0].set_title(f"{instrument} {ns_parser.granularity}")
|
||||
ax[0].legend(legends)
|
||||
# pylint: disable=C0200
|
||||
for i in range(0, len(subplot_legends), 2):
|
||||
ax[subplot_legends[i]].legend(subplot_legends[i + 1])
|
||||
|
||||
print("")
|
||||
except V20Error as e:
|
||||
# pylint: disable=W0123
|
||||
d_error = eval(e.msg)
|
||||
print(d_error["errorMessage"], "\n")
|
||||
except TypeError as e:
|
||||
print(e)
|
||||
|
||||
|
||||
def add_plots(df, ns_parser):
|
||||
panel_number = 2
|
||||
plots_to_add = []
|
||||
legends = []
|
||||
subplot_legends = []
|
||||
|
||||
if ns_parser.ad:
|
||||
ad = ta.ad(df["High"], df["Low"], df["Close"], df["Volume"])
|
||||
ad_plot = mpf.make_addplot(ad, panel=panel_number)
|
||||
plots_to_add.append(ad_plot)
|
||||
subplot_legends.extend([panel_number * 2, ["AD"]])
|
||||
panel_number += 1
|
||||
|
||||
if ns_parser.bbands:
|
||||
bbands = ta.bbands(df["Close"])
|
||||
bbands = bbands.drop("BBB_5_2.0", axis=1)
|
||||
bbands_plot = mpf.make_addplot(bbands, panel=0)
|
||||
plots_to_add.append(bbands_plot)
|
||||
legends.extend(["Lower BBand", "Middle BBand", "Upper BBand"])
|
||||
|
||||
if ns_parser.cci:
|
||||
cci = ta.cci(df["High"], df["Low"], df["Close"])
|
||||
cci_plot = mpf.make_addplot(cci, panel=panel_number)
|
||||
plots_to_add.append(cci_plot)
|
||||
subplot_legends.extend([panel_number * 2, ["CCI"]])
|
||||
panel_number += 1
|
||||
|
||||
if ns_parser.ema:
|
||||
ema = ta.ema(df["Close"])
|
||||
ema_plot = mpf.make_addplot(ema, panel=0)
|
||||
plots_to_add.append(ema_plot)
|
||||
legends.append("10 EMA")
|
||||
|
||||
if ns_parser.rsi:
|
||||
rsi = ta.rsi(df["Close"])
|
||||
rsi_plot = mpf.make_addplot(rsi, panel=panel_number)
|
||||
plots_to_add.append(rsi_plot)
|
||||
subplot_legends.extend([panel_number * 2, ["RSI"]])
|
||||
panel_number += 1
|
||||
|
||||
if ns_parser.obv:
|
||||
obv = ta.obv(df["Close"], df["Volume"])
|
||||
obv_plot = mpf.make_addplot(obv, panel=panel_number)
|
||||
plots_to_add.append(obv_plot)
|
||||
subplot_legends.extend([panel_number * 2, ["OBV"]])
|
||||
panel_number += 1
|
||||
|
||||
if ns_parser.sma:
|
||||
sma_length = [20, 50]
|
||||
for length in sma_length:
|
||||
sma = ta.sma(df["Close"], length=length)
|
||||
sma_plot = mpf.make_addplot(sma, panel=0)
|
||||
plots_to_add.append(sma_plot)
|
||||
legends.append(f"{length} SMA")
|
||||
|
||||
if ns_parser.vwap:
|
||||
vwap = ta.vwap(df["High"], df["Low"], df["Close"], df["Volume"])
|
||||
vwap_plot = mpf.make_addplot(vwap, panel=0)
|
||||
plots_to_add.append(vwap_plot)
|
||||
legends.append("vwap")
|
||||
|
||||
return plots_to_add, legends, subplot_legends
|
||||
|
||||
|
||||
def calendar(instrument, other_args: List[str]):
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="calendar",
|
||||
description="Show Calendar Data",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--days",
|
||||
dest="days",
|
||||
action="store",
|
||||
type=int,
|
||||
default=7,
|
||||
required=False,
|
||||
help="The number of days to search for, up to 30 forward or backward "
|
||||
+ "use negative numbers to search back. ",
|
||||
)
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
|
||||
parameters = {"instrument": instrument, "period": str(ns_parser.days * 86400 * -1)}
|
||||
try:
|
||||
request = forexlabs.Calendar(params=parameters)
|
||||
response = client.request(request)
|
||||
|
||||
l_data = []
|
||||
# pylint: disable=C0200
|
||||
for i in range(len(response)):
|
||||
if "forecast" in response[i]:
|
||||
forecast = response[i]["forecast"]
|
||||
if response[i]["unit"] != "Index":
|
||||
forecast += response[i]["unit"]
|
||||
else:
|
||||
forecast = ""
|
||||
|
||||
if "market" in response[i]:
|
||||
market = response[i]["market"]
|
||||
if response[i]["unit"] != "Index":
|
||||
market += response[i]["unit"]
|
||||
else:
|
||||
market = ""
|
||||
|
||||
if "actual" in response[i]:
|
||||
actual = response[i]["actual"]
|
||||
if response[i]["unit"] != "Index":
|
||||
actual += response[i]["unit"]
|
||||
else:
|
||||
actual = ""
|
||||
|
||||
if "previous" in response[i]:
|
||||
previous = response[i]["previous"]
|
||||
if response[i]["unit"] != "Index":
|
||||
previous += response[i]["unit"]
|
||||
else:
|
||||
previous = ""
|
||||
|
||||
if "impact" in response[i]:
|
||||
impact = response[i]["impact"]
|
||||
else:
|
||||
impact = ""
|
||||
|
||||
l_data.append(
|
||||
{
|
||||
"Title": response[i]["title"],
|
||||
"Time": datetime.fromtimestamp(response[i]["timestamp"]),
|
||||
"Impact": impact,
|
||||
"Forecast": forecast,
|
||||
"Market Forecast": market,
|
||||
"Currency": response[i]["currency"],
|
||||
"Region": response[i]["region"],
|
||||
"Actual": actual,
|
||||
"Previous": previous,
|
||||
}
|
||||
)
|
||||
|
||||
print(pd.DataFrame(l_data).to_string(index=False))
|
||||
print("")
|
||||
|
||||
except V20Error as e:
|
||||
# pylint: disable=W0123
|
||||
d_error = eval(e.msg)
|
||||
print(d_error["message"], "\n")
|
||||
|
||||
|
||||
# pylint: disable=W0613
|
||||
# pylint: disable=R1710
|
||||
def get_candles_dataframe(accountID, instrument, parameters):
|
||||
if not parameters:
|
||||
parameters = {
|
||||
"granularity": "D",
|
||||
"count": "1500",
|
||||
}
|
||||
try:
|
||||
request = instruments.InstrumentsCandles(instrument, params=parameters)
|
||||
response = client.request(request)
|
||||
candles_data = []
|
||||
for i in range(len(response["candles"])):
|
||||
candles_data.append(
|
||||
{
|
||||
"Date": response["candles"][i]["time"][:10]
|
||||
+ " "
|
||||
+ response["candles"][i]["time"][11:19],
|
||||
"Open": float(response["candles"][i]["mid"]["o"]),
|
||||
"High": float(response["candles"][i]["mid"]["h"]),
|
||||
"Low": float(response["candles"][i]["mid"]["l"]),
|
||||
"Close": float(response["candles"][i]["mid"]["c"]),
|
||||
"Volume": response["candles"][i]["volume"],
|
||||
}
|
||||
)
|
||||
df = pd.DataFrame(candles_data)
|
||||
df.set_index("Date", inplace=True)
|
||||
df.index = pd.to_datetime(df.index)
|
||||
return df
|
||||
except V20Error as e:
|
||||
# pylint: disable=W0123
|
||||
d_error = eval(e.msg)
|
||||
print(d_error["errorMessage"], "\n")
|
||||
|
||||
|
||||
# pylint: disable=R1710
|
||||
def load(other_args: List[str]):
|
||||
"""Load a forex instrument to use"""
|
||||
parser = argparse.ArgumentParser(
|
||||
add_help=False,
|
||||
prog="load",
|
||||
description="Forex using oanda",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-i",
|
||||
"--instrument",
|
||||
required=True,
|
||||
type=str,
|
||||
dest="instrument",
|
||||
help="Forex pair to use. ",
|
||||
)
|
||||
|
||||
try:
|
||||
if other_args:
|
||||
if "-" not in other_args[0]:
|
||||
other_args.insert(0, "-i")
|
||||
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if not ns_parser:
|
||||
return
|
||||
|
||||
print("")
|
||||
return ns_parser.instrument.upper()
|
||||
|
||||
except Exception as e:
|
||||
print(e, "\n")
|
||||
return None
|
||||
|
||||
|
||||
def book_plot(df, instrument, book_type):
|
||||
_, ax = plt.subplots(figsize=plot_autoscale(), dpi=cfgPlot.PLOT_DPI)
|
||||
df = df.apply(pd.to_numeric)
|
||||
df["shortCountPercent"] = df["shortCountPercent"] * -1
|
||||
axis_origin = max(
|
||||
abs(max(df["longCountPercent"])), abs(max(df["shortCountPercent"]))
|
||||
)
|
||||
ax.set_xlim(-axis_origin, +axis_origin)
|
||||
|
||||
sns.set_style(style="darkgrid")
|
||||
|
||||
sns.barplot(
|
||||
x="longCountPercent",
|
||||
y="price",
|
||||
data=df,
|
||||
label="Count Percent",
|
||||
color="green",
|
||||
orient="h",
|
||||
)
|
||||
|
||||
sns.barplot(
|
||||
x="shortCountPercent",
|
||||
y="price",
|
||||
data=df,
|
||||
label="Prices",
|
||||
color="red",
|
||||
orient="h",
|
||||
)
|
||||
|
||||
ax.invert_yaxis()
|
||||
plt.title(f"{instrument} {book_type}")
|
||||
plt.xlabel("Count Percent")
|
||||
plt.ylabel("Price")
|
||||
sns.despine(left=True, bottom=True)
|
||||
ax.yaxis.set_major_locator(mticker.MultipleLocator(5))
|
||||
if gtff.USE_ION:
|
||||
plt.ion()
|
||||
plt.show()
|
||||
|
||||
|
||||
# pylint: disable=R1710
|
||||
def format_instrument(instrument, char):
|
||||
try:
|
||||
if char not in instrument:
|
||||
instrument_list = list(instrument)
|
||||
instrument_list.pop(3)
|
||||
if char == " ":
|
||||
instrument = "".join(map(str, instrument_list))
|
||||
else:
|
||||
instrument_list.insert(3, char)
|
||||
instrument = "".join(map(str, instrument_list))
|
||||
return instrument
|
||||
except TypeError:
|
||||
print("Please load an instrument")
|
||||
print("")
|
||||
10
website/content/forex/candle/_index.md
vendored
Normal file
10
website/content/forex/candle/_index.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
```
|
||||
usage: candle [-h]
|
||||
```
|
||||
|
||||
Show candle for loaded fx data
|
||||
|
||||
```
|
||||
optional arguments:
|
||||
-h, --help show this help message (default: False)
|
||||
```
|
||||
16
website/content/forex/load/_index.md
vendored
Normal file
16
website/content/forex/load/_index.md
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
```
|
||||
usage: load [-r {i,d,w,m}] [-i {1,5,15,30,60}] [-s START_DATE] [-h]
|
||||
```
|
||||
|
||||
Load historical exchange rate data.
|
||||
|
||||
```
|
||||
optional arguments:
|
||||
-r {i,d,w,m}, --resolution {i,d,w,m}
|
||||
Resolution of data. Can be intraday, daily, weekly or monthly (default: d)
|
||||
-i {1,5,15,30,60}, --interval {1,5,15,30,60}
|
||||
Interval of intraday data. Can be 1, 5, 15, 30 or 60. (default: 5)
|
||||
-s START_DATE, --start_date START_DATE
|
||||
Start date of data. (default: 2020-11-01 16:41:01.349343)
|
||||
-h, --help show this help message (default: False)
|
||||
```
|
||||
10
website/content/forex/quote/_index.md
vendored
Normal file
10
website/content/forex/quote/_index.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
```
|
||||
usage: quote [-h]
|
||||
```
|
||||
|
||||
Get current exchange rate quote
|
||||
|
||||
```
|
||||
optional arguments:
|
||||
-h, --help show this help message (default: False)
|
||||
```
|
||||
14
website/content/forex/select/_index.md
vendored
Normal file
14
website/content/forex/select/_index.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
```
|
||||
usage: select [-t TO_SYMBOL] [-f FROM_SYMBOL] [-h]
|
||||
```
|
||||
|
||||
Select Forex pair in the form of TO -f FROM
|
||||
|
||||
```
|
||||
optional arguments:
|
||||
-t TO_SYMBOL, --to TO_SYMBOL
|
||||
To currency (default: None)
|
||||
-f FROM_SYMBOL, --from FROM_SYMBOL
|
||||
From currency (default: None)
|
||||
-h, --help show this help message (default: False)
|
||||
```
|
||||
7
website/data/menu/main.yml
vendored
7
website/data/menu/main.yml
vendored
@@ -878,5 +878,12 @@ main:
|
||||
ref: "/portfolio/portfolio_optimization/ef"
|
||||
- name: forex
|
||||
ref: "/forex"
|
||||
sub:
|
||||
- name: select
|
||||
ref: "/forex/select"
|
||||
- name: load
|
||||
ref: "/forex/load"
|
||||
- name: candle
|
||||
ref: "/forex/candle"
|
||||
- name: resources
|
||||
ref: "/resources"
|
||||
|
||||
Reference in New Issue
Block a user