mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 04:31:13 +00:00
Add YahooFinance to forex load (#1544)
* Load yf forex * Fix linting * Adding forex dictionary * Fix tests failing * Fix tests failing * Remove inheritance to oanda model * Adding -s to --source * Addressed comments * Fix tests * Fix oanda menu entry * Add av api limit error handling * Fix start date being in different formats across sources * Silence "too much data" warning * Add gst script to test currency sources Co-authored-by: Theodore Aptekarev <aptekarev@gmail.com> Co-authored-by: jmaslek <jmaslek11@gmail.com>
This commit is contained in:
@@ -18,7 +18,7 @@ logger = logging.getLogger(__name__)
|
||||
@log_start_end(log=logger)
|
||||
def get_currency_list() -> List:
|
||||
"""Load AV currency codes from a local file."""
|
||||
path = os.path.join(os.path.dirname(__file__), "av_forex_currencies.csv")
|
||||
path = os.path.join(os.path.dirname(__file__), "data/av_forex_currencies.csv")
|
||||
return list(pd.read_csv(path)["currency code"])
|
||||
|
||||
|
||||
@@ -137,6 +137,8 @@ def get_historical(
|
||||
# If the returned data was unsuccessful
|
||||
if "Error Message" in r.json():
|
||||
console.print(r.json()["Error Message"])
|
||||
elif "Note" in r.json():
|
||||
console.print(r.json()["Note"])
|
||||
else:
|
||||
# check if json is empty
|
||||
if not r.json():
|
||||
|
||||
@@ -2,17 +2,13 @@
|
||||
__docformat__ = "numpy"
|
||||
|
||||
import logging
|
||||
from typing import Optional, List
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import mplfinance as mpf
|
||||
import pandas as pd
|
||||
|
||||
from gamestonk_terminal.config_terminal import theme
|
||||
from gamestonk_terminal.decorators import check_api_key
|
||||
from gamestonk_terminal.decorators import log_start_end
|
||||
from gamestonk_terminal.forex import av_model
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale, print_rich_table
|
||||
from gamestonk_terminal.helper_funcs import print_rich_table
|
||||
from gamestonk_terminal.rich_config import console
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -32,7 +28,15 @@ def display_quote(to_symbol: str, from_symbol: str):
|
||||
"""
|
||||
quote = av_model.get_quote(to_symbol, from_symbol)
|
||||
|
||||
if not quote:
|
||||
if not bool(quote) or "Note" in quote:
|
||||
if "Note" in quote:
|
||||
console.print(quote["Note"])
|
||||
console.print(
|
||||
"\n[red]"
|
||||
+ "No historical data loaded.\n"
|
||||
+ "Make sure 'av' supports the requested pair and you aren't hitting your API call limits."
|
||||
+ "[/red]\n"
|
||||
)
|
||||
return
|
||||
|
||||
df = pd.DataFrame.from_dict(quote)
|
||||
@@ -44,63 +48,3 @@ def display_quote(to_symbol: str, from_symbol: str):
|
||||
title=f"[bold]{from_symbol}/{to_symbol} Quote [/bold]",
|
||||
)
|
||||
console.print("")
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def display_candle(
|
||||
data: pd.DataFrame,
|
||||
to_symbol: str,
|
||||
from_symbol: str,
|
||||
external_axes: Optional[List[plt.Axes]] = None,
|
||||
):
|
||||
"""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
|
||||
external_axes: Optional[List[plt.Axes]]
|
||||
External axes (1 axis are expected in the list), by default None
|
||||
"""
|
||||
candle_chart_kwargs = {
|
||||
"type": "candle",
|
||||
"style": theme.mpf_style,
|
||||
"mav": (20, 50),
|
||||
"volume": False,
|
||||
"xrotation": theme.xticks_rotation,
|
||||
"scale_padding": {"left": 0.3, "right": 1, "top": 0.8, "bottom": 0.8},
|
||||
"update_width_config": {
|
||||
"candle_linewidth": 0.6,
|
||||
"candle_width": 0.8,
|
||||
"volume_linewidth": 0.8,
|
||||
"volume_width": 0.8,
|
||||
},
|
||||
"warn_too_much_data": 10000,
|
||||
}
|
||||
# This plot has 2 axes
|
||||
if not external_axes:
|
||||
candle_chart_kwargs["returnfig"] = True
|
||||
candle_chart_kwargs["figratio"] = (10, 7)
|
||||
candle_chart_kwargs["figscale"] = 1.10
|
||||
candle_chart_kwargs["figsize"] = plot_autoscale()
|
||||
fig, ax = mpf.plot(data, **candle_chart_kwargs)
|
||||
fig.suptitle(
|
||||
f"{from_symbol}/{to_symbol}",
|
||||
x=0.055,
|
||||
y=0.965,
|
||||
horizontalalignment="left",
|
||||
)
|
||||
theme.visualize_output(force_tight_layout=False)
|
||||
ax[0].legend()
|
||||
else:
|
||||
if len(external_axes) != 1:
|
||||
logger.error("Expected list of 1 axis items.")
|
||||
console.print("[red]Expected list of 1 axis items./n[/red]")
|
||||
return
|
||||
(ax1,) = external_axes
|
||||
candle_chart_kwargs["ax"] = ax1
|
||||
mpf.plot(data, **candle_chart_kwargs)
|
||||
|
||||
20418
gamestonk_terminal/forex/data/yahoofinance_forex.json
vendored
Normal file
20418
gamestonk_terminal/forex/data/yahoofinance_forex.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,7 @@ from gamestonk_terminal.helper_classes import ModelsNamespace as _models
|
||||
# pylint: disable=unused-import
|
||||
|
||||
# Context menus
|
||||
from .av_view import display_candle as candle
|
||||
from .forex_helper import display_candle as candle
|
||||
from .av_view import display_quote as quote
|
||||
from .technical_analysis import ta_api as ta
|
||||
from .oanda import oanda_api as oanda
|
||||
|
||||
@@ -12,11 +12,13 @@ from prompt_toolkit.completion import NestedCompleter
|
||||
|
||||
from gamestonk_terminal import feature_flags as gtff
|
||||
from gamestonk_terminal.decorators import log_start_end
|
||||
from gamestonk_terminal.forex import av_model, av_view
|
||||
from gamestonk_terminal.forex import av_view, forex_helper
|
||||
from gamestonk_terminal.forex.forex_helper import FOREX_SOURCES, SOURCES_INTERVALS
|
||||
from gamestonk_terminal.helper_funcs import parse_known_args_and_warn, valid_date
|
||||
from gamestonk_terminal.menu import session
|
||||
from gamestonk_terminal.parent_classes import BaseController
|
||||
from gamestonk_terminal.rich_config import console
|
||||
from gamestonk_terminal.decorators import check_api_key
|
||||
|
||||
# pylint: disable=R1710,import-outside-toplevel
|
||||
|
||||
@@ -37,12 +39,14 @@ class ForexController(BaseController):
|
||||
|
||||
self.from_symbol = "USD"
|
||||
self.to_symbol = ""
|
||||
self.source = "yf"
|
||||
self.data = pd.DataFrame()
|
||||
|
||||
if session and gtff.USE_PROMPT_TOOLKIT:
|
||||
choices: dict = {c: {} for c in self.controller_choices}
|
||||
choices["to"] = {c: None for c in av_model.CURRENCY_LIST}
|
||||
choices["from"] = {c: None for c in av_model.CURRENCY_LIST}
|
||||
choices["to"] = {c: None for c in forex_helper.YF_CURRENCY_LIST}
|
||||
choices["from"] = {c: None for c in forex_helper.YF_CURRENCY_LIST}
|
||||
choices["load"]["--source"] = {c: None for c in FOREX_SOURCES}
|
||||
self.completer = NestedCompleter.from_nested_dict(choices)
|
||||
|
||||
def print_help(self):
|
||||
@@ -53,17 +57,16 @@ class ForexController(BaseController):
|
||||
from select the "from" currency in a forex pair
|
||||
to select the "to" currency in a forex pair[/cmds]
|
||||
|
||||
[param]From: [/param]{None or self.from_symbol}
|
||||
[param]To: [/param]{None or self.to_symbol}[cmds]
|
||||
{has_symbols_start}
|
||||
[src][AlphaVantage][/src][cmds]
|
||||
quote get last quote
|
||||
[param]From: [/param]{None or self.from_symbol}
|
||||
[param]To: [/param]{None or self.to_symbol}
|
||||
[param]Source: [/param]{None or FOREX_SOURCES[self.source]}[cmds]{has_symbols_start}
|
||||
[cmds]
|
||||
quote get last quote [src][AlphaVantage][/src]
|
||||
load get historical data
|
||||
candle show candle plot for loaded data[/cmds]
|
||||
[menu]
|
||||
> ta technical analysis for loaded coin, e.g.: ema, macd, rsi, adx, bbands, obv
|
||||
[/menu]
|
||||
{has_symbols_end}
|
||||
[/menu]{has_symbols_end}
|
||||
[info]Forex brokerages:[/info][menu]
|
||||
> oanda Oanda menu[/menu][/cmds]
|
||||
"""
|
||||
@@ -90,7 +93,7 @@ class ForexController(BaseController):
|
||||
"-n",
|
||||
"--name",
|
||||
help="To currency",
|
||||
type=av_model.check_valid_forex_currency,
|
||||
type=forex_helper.check_valid_yf_forex_currency,
|
||||
dest="to_symbol",
|
||||
)
|
||||
|
||||
@@ -107,7 +110,9 @@ class ForexController(BaseController):
|
||||
self.to_symbol = ns_parser.to_symbol
|
||||
|
||||
console.print(
|
||||
f"\nSelected pair\nFrom: {self.from_symbol}\nTo: {self.to_symbol}\n\n"
|
||||
f"\nSelected pair\nFrom: {self.from_symbol}\n"
|
||||
f"To: {self.to_symbol}\n"
|
||||
f"Source: {FOREX_SOURCES[self.source]}\n\n"
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -123,7 +128,7 @@ class ForexController(BaseController):
|
||||
"-n",
|
||||
"--name",
|
||||
help="From currency",
|
||||
type=av_model.check_valid_forex_currency,
|
||||
type=forex_helper.check_valid_yf_forex_currency,
|
||||
dest="from_symbol",
|
||||
)
|
||||
|
||||
@@ -139,7 +144,9 @@ class ForexController(BaseController):
|
||||
if ns_parser:
|
||||
self.from_symbol = ns_parser.from_symbol
|
||||
console.print(
|
||||
f"\nSelected pair\nFrom: {self.from_symbol}\nTo: {self.to_symbol}\n\n"
|
||||
f"\nSelected pair\nFrom: {self.from_symbol}\n"
|
||||
f"To: {self.to_symbol}\n"
|
||||
f"Source: {FOREX_SOURCES[self.source]}\n\n"
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -149,8 +156,20 @@ class ForexController(BaseController):
|
||||
add_help=False,
|
||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||
prog="load",
|
||||
description="Load historical exchange rate data.",
|
||||
description="Load historical exchange rate data."
|
||||
"Available data sources are Alpha Advantage and YahooFinance"
|
||||
"By default main source used for analysis is YahooFinance (yf). To change it use --source av",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--source",
|
||||
help="Source of historical data",
|
||||
dest="source",
|
||||
choices=("yf", "av"),
|
||||
default="yf",
|
||||
required=False,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--resolution",
|
||||
@@ -162,32 +181,54 @@ class ForexController(BaseController):
|
||||
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.",
|
||||
choices=SOURCES_INTERVALS["yf"],
|
||||
default="5min",
|
||||
help="""Interval of intraday data. Options:
|
||||
[YahooFinance] 1min, 2min, 5min, 15min, 30min, 60min, 90min, 1hour, 1day, 5day, 1week, 1month, 3month.
|
||||
[AlphaAdvantage] 1min, 5min, 15min, 30min, 60min""",
|
||||
dest="interval",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--start_date",
|
||||
default=(datetime.now() - timedelta(days=366)),
|
||||
default=(datetime.now() - timedelta(days=59)),
|
||||
type=valid_date,
|
||||
help="Start date of data.",
|
||||
dest="start_date",
|
||||
)
|
||||
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
|
||||
if ns_parser:
|
||||
|
||||
if self.to_symbol and self.from_symbol:
|
||||
self.data = av_model.get_historical(
|
||||
|
||||
self.data = forex_helper.load(
|
||||
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"),
|
||||
source=ns_parser.source,
|
||||
)
|
||||
self.data.index.name = "date"
|
||||
|
||||
if self.data.empty:
|
||||
console.print(
|
||||
"\n[red]"
|
||||
+ "No historical data loaded.\n"
|
||||
+ f"Make sure you have appropriate access for the '{ns_parser.source}' data source "
|
||||
+ f"and that '{ns_parser.source}' supports the requested range."
|
||||
+ "[/red]\n"
|
||||
)
|
||||
else:
|
||||
self.data.index.name = "date"
|
||||
|
||||
self.source = ns_parser.source
|
||||
|
||||
console.print(
|
||||
f"Loaded historic data from {self.from_symbol} to {self.to_symbol}"
|
||||
f"\nSelected pair\nFrom: {self.from_symbol}\n"
|
||||
f"To: {self.to_symbol}\n"
|
||||
f"Source: {FOREX_SOURCES[self.source]}\n\n"
|
||||
)
|
||||
else:
|
||||
logger.error(
|
||||
@@ -209,7 +250,7 @@ class ForexController(BaseController):
|
||||
ns_parser = parse_known_args_and_warn(parser, other_args)
|
||||
if ns_parser:
|
||||
if not self.data.empty:
|
||||
av_view.display_candle(self.data, self.to_symbol, self.from_symbol)
|
||||
forex_helper.display_candle(self.data, self.to_symbol, self.from_symbol)
|
||||
else:
|
||||
logger.error(
|
||||
"No forex historical data loaded. Load first using <load>."
|
||||
@@ -236,16 +277,24 @@ class ForexController(BaseController):
|
||||
"Make sure both a 'to' symbol and a 'from' symbol are selected."
|
||||
)
|
||||
console.print(
|
||||
'[red]Make sure both a "to" symbol and a "from" symbol are selected.[/red]\n'
|
||||
"[red]Make sure both a 'to' symbol and a 'from' symbol are selected.[/red]\n"
|
||||
)
|
||||
|
||||
# MENUS
|
||||
@log_start_end(log=logger)
|
||||
@check_api_key(["OANDA_ACCOUNT_TYPE", "OANDA_ACCOUNT", "OANDA_TOKEN"])
|
||||
def call_oanda(self, _):
|
||||
"""Enter Oanda menu."""
|
||||
from gamestonk_terminal.forex.oanda.oanda_controller import OandaController
|
||||
|
||||
self.queue = self.load_class(OandaController, self.queue)
|
||||
if self.to_symbol and self.from_symbol:
|
||||
|
||||
self.queue = self.load_class(
|
||||
OandaController,
|
||||
queue=self.queue,
|
||||
)
|
||||
else:
|
||||
console.print("No currency pair data is loaded. Use 'load' to load data.\n")
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def call_ta(self, _):
|
||||
@@ -259,6 +308,7 @@ class ForexController(BaseController):
|
||||
self.queue = self.load_class(
|
||||
TechnicalAnalysisController,
|
||||
ticker=f"{self.from_symbol}/{self.to_symbol}",
|
||||
source=self.source,
|
||||
data=self.data,
|
||||
start=self.data.index[0],
|
||||
interval="",
|
||||
|
||||
201
gamestonk_terminal/forex/forex_helper.py
Normal file
201
gamestonk_terminal/forex/forex_helper.py
Normal file
@@ -0,0 +1,201 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Dict
|
||||
import os
|
||||
import argparse
|
||||
import logging
|
||||
|
||||
import yfinance as yf
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import mplfinance as mpf
|
||||
|
||||
from gamestonk_terminal.forex import av_model
|
||||
from gamestonk_terminal.rich_config import console
|
||||
from gamestonk_terminal.decorators import log_start_end
|
||||
from gamestonk_terminal.helper_funcs import plot_autoscale
|
||||
from gamestonk_terminal.config_terminal import theme
|
||||
|
||||
|
||||
FOREX_SOURCES: Dict = {
|
||||
"yf": "YahooFinance",
|
||||
"av": "AlphaAdvantage",
|
||||
}
|
||||
|
||||
SOURCES_INTERVALS: Dict = {
|
||||
"yf": [
|
||||
"1min",
|
||||
"2min",
|
||||
"5min",
|
||||
"15min",
|
||||
"30min",
|
||||
"60min",
|
||||
"90min",
|
||||
"1hour",
|
||||
"1day",
|
||||
"5day",
|
||||
"1week",
|
||||
"1month",
|
||||
"3month",
|
||||
],
|
||||
"av": ["1min", "5min", "15min", "30min", "60min"],
|
||||
}
|
||||
|
||||
INTERVAL_MAPS: Dict = {
|
||||
"yf": {
|
||||
"1min": "1m",
|
||||
"2min": "2m",
|
||||
"5min": "5m",
|
||||
"15min": "15m",
|
||||
"30min": "30m",
|
||||
"60min": "60m",
|
||||
"90min": "90m",
|
||||
"1hour": "1h",
|
||||
"1day": "1d",
|
||||
"5day": "5d",
|
||||
"1week": "1wk",
|
||||
"1month": "1mo",
|
||||
"3month": "3mo",
|
||||
},
|
||||
"av": {"1min": 1, "5min": 5, "15min": 15, "30min": 30, "60min": 60},
|
||||
}
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def load(
|
||||
to_symbol: str,
|
||||
from_symbol: str,
|
||||
resolution: str,
|
||||
interval: str,
|
||||
start_date: str,
|
||||
source: str = "yf",
|
||||
):
|
||||
interval_map = INTERVAL_MAPS[source]
|
||||
|
||||
if interval not in interval_map.keys():
|
||||
console.print(
|
||||
f"Interval not supported by {FOREX_SOURCES[source]}. Need to be one of the following options",
|
||||
interval_map.keys(),
|
||||
)
|
||||
return pd.DataFrame()
|
||||
|
||||
if source == "av":
|
||||
|
||||
df = av_model.get_historical(
|
||||
to_symbol=to_symbol,
|
||||
from_symbol=from_symbol,
|
||||
resolution=resolution,
|
||||
interval=interval_map[interval],
|
||||
start_date=start_date,
|
||||
)
|
||||
|
||||
if source == "yf":
|
||||
|
||||
df = yf.download(
|
||||
f"{from_symbol}{to_symbol}=X",
|
||||
end=datetime.now(),
|
||||
start=datetime.strptime(start_date, "%Y-%m-%d"),
|
||||
interval=interval_map[interval],
|
||||
progress=False,
|
||||
)
|
||||
|
||||
return df
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def get_yf_currency_list() -> List:
|
||||
"""Load YF list of forex pair a local file."""
|
||||
path = os.path.join(os.path.dirname(__file__), "data/yahoofinance_forex.json")
|
||||
|
||||
return sorted(list(set(pd.read_json(path)["from_symbol"])))
|
||||
|
||||
|
||||
YF_CURRENCY_LIST = get_yf_currency_list()
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def check_valid_yf_forex_currency(fx_symbol: str) -> str:
|
||||
"""Check if given symbol is supported on Yahoo Finance
|
||||
|
||||
Parameters
|
||||
----------
|
||||
fx_symbol : str
|
||||
Symbol to check
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
Currency symbol
|
||||
|
||||
Raises
|
||||
------
|
||||
argparse.ArgumentTypeError
|
||||
Symbol not valid on YahooFinance
|
||||
"""
|
||||
if fx_symbol.upper() in get_yf_currency_list():
|
||||
return fx_symbol.upper()
|
||||
|
||||
raise argparse.ArgumentTypeError(
|
||||
f"{fx_symbol.upper()} not found in YahooFinance supported currency codes. "
|
||||
)
|
||||
|
||||
|
||||
@log_start_end(log=logger)
|
||||
def display_candle(
|
||||
data: pd.DataFrame,
|
||||
to_symbol: str,
|
||||
from_symbol: str,
|
||||
external_axes: Optional[List[plt.Axes]] = None,
|
||||
):
|
||||
"""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
|
||||
external_axes: Optional[List[plt.Axes]]
|
||||
External axes (1 axis are expected in the list), by default None
|
||||
"""
|
||||
candle_chart_kwargs = {
|
||||
"type": "candle",
|
||||
"style": theme.mpf_style,
|
||||
"mav": (20, 50),
|
||||
"volume": False,
|
||||
"xrotation": theme.xticks_rotation,
|
||||
"scale_padding": {"left": 0.3, "right": 1, "top": 0.8, "bottom": 0.8},
|
||||
"update_width_config": {
|
||||
"candle_linewidth": 0.6,
|
||||
"candle_width": 0.8,
|
||||
"volume_linewidth": 0.8,
|
||||
"volume_width": 0.8,
|
||||
},
|
||||
"warn_too_much_data": 20000,
|
||||
}
|
||||
# This plot has 2 axes
|
||||
if not external_axes:
|
||||
candle_chart_kwargs["returnfig"] = True
|
||||
candle_chart_kwargs["figratio"] = (10, 7)
|
||||
candle_chart_kwargs["figscale"] = 1.10
|
||||
candle_chart_kwargs["figsize"] = plot_autoscale()
|
||||
fig, ax = mpf.plot(data, **candle_chart_kwargs)
|
||||
fig.suptitle(
|
||||
f"{from_symbol}/{to_symbol}",
|
||||
x=0.055,
|
||||
y=0.965,
|
||||
horizontalalignment="left",
|
||||
)
|
||||
theme.visualize_output(force_tight_layout=False)
|
||||
ax[0].legend()
|
||||
else:
|
||||
if len(external_axes) != 1:
|
||||
logger.error("Expected list of 1 axis items.")
|
||||
console.print("[red]Expected list of 1 axis items./n[/red]")
|
||||
return
|
||||
(ax1,) = external_axes
|
||||
candle_chart_kwargs["ax"] = ax1
|
||||
mpf.plot(data, **candle_chart_kwargs)
|
||||
@@ -10,7 +10,8 @@ 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.decorators import log_start_end
|
||||
from gamestonk_terminal.forex import av_model
|
||||
from gamestonk_terminal.forex import av_model, forex_helper
|
||||
from gamestonk_terminal.forex.forex_helper import FOREX_SOURCES
|
||||
from gamestonk_terminal.forex.oanda import oanda_view
|
||||
from gamestonk_terminal.helper_funcs import (
|
||||
check_non_negative_float,
|
||||
@@ -55,6 +56,7 @@ class OandaController(BaseController):
|
||||
|
||||
self.from_symbol = ""
|
||||
self.to_symbol = ""
|
||||
self.source = "Oanda"
|
||||
self.instrument: Union[str, None] = None
|
||||
|
||||
if session and gtff.USE_PROMPT_TOOLKIT:
|
||||
@@ -64,8 +66,8 @@ class OandaController(BaseController):
|
||||
# In Oanda they have their own list of available instruments. It would be
|
||||
# Great to fetch these lists and store them locally like it's done for
|
||||
# other currency codes (see ./av_forex_currencies.csv and how it's handled).
|
||||
choices["to"] = {c: None for c in av_model.CURRENCY_LIST}
|
||||
choices["from"] = {c: None for c in av_model.CURRENCY_LIST}
|
||||
choices["to"] = {c: None for c in forex_helper.YF_CURRENCY_LIST}
|
||||
choices["from"] = {c: None for c in forex_helper.YF_CURRENCY_LIST}
|
||||
self.completer = NestedCompleter.from_nested_dict(choices)
|
||||
|
||||
def print_help(self):
|
||||
@@ -87,6 +89,9 @@ class OandaController(BaseController):
|
||||
to select the "to" currency in a forex pair[/cmds]
|
||||
|
||||
[param]Loaded instrument: [/param]{self.instrument if self.instrument else ""}[cmds]
|
||||
[param]From: [/param]{None or self.from_symbol}
|
||||
[param]To: [/param]{None or self.to_symbol}
|
||||
[param]Source: [/param]{None or FOREX_SOURCES[self.source]}[cmds]
|
||||
|
||||
{has_instrument_start}
|
||||
candles show candles
|
||||
@@ -128,7 +133,9 @@ class OandaController(BaseController):
|
||||
self.instrument = f"{self.from_symbol}_{self.to_symbol}"
|
||||
|
||||
console.print(
|
||||
f"\nSelected pair\nFrom: {self.from_symbol}\nTo: {self.to_symbol}\n\n"
|
||||
f"\nSelected pair\nFrom: {self.from_symbol}\n"
|
||||
f"To: {self.to_symbol}\n"
|
||||
f"Source: {FOREX_SOURCES[self.source]}\n\n"
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
@@ -163,7 +170,9 @@ class OandaController(BaseController):
|
||||
self.instrument = f"{self.from_symbol}_{self.to_symbol}"
|
||||
|
||||
console.print(
|
||||
f"\nSelected pair\nFrom: {self.from_symbol}\nTo: {self.to_symbol}\n\n"
|
||||
f"\nSelected pair\nFrom: {self.from_symbol}\n"
|
||||
f"To: {self.to_symbol}\n"
|
||||
f"Source: {FOREX_SOURCES[self.source]}\n\n"
|
||||
)
|
||||
|
||||
@log_start_end(log=logger)
|
||||
|
||||
@@ -27,6 +27,7 @@ from gamestonk_terminal.helper_funcs import (
|
||||
parse_known_args_and_warn,
|
||||
valid_date,
|
||||
)
|
||||
from gamestonk_terminal.forex.forex_helper import FOREX_SOURCES
|
||||
from gamestonk_terminal.menu import session
|
||||
from gamestonk_terminal.parent_classes import StockBaseController
|
||||
from gamestonk_terminal.rich_config import console
|
||||
@@ -59,6 +60,7 @@ class TechnicalAnalysisController(StockBaseController):
|
||||
def __init__(
|
||||
self,
|
||||
ticker: str,
|
||||
source: str,
|
||||
start: datetime,
|
||||
interval: str,
|
||||
data: pd.DataFrame,
|
||||
@@ -68,6 +70,7 @@ class TechnicalAnalysisController(StockBaseController):
|
||||
super().__init__(queue)
|
||||
|
||||
self.ticker = ticker
|
||||
self.source = source
|
||||
self.start = start
|
||||
self.interval = interval
|
||||
self.data = data
|
||||
@@ -82,6 +85,7 @@ class TechnicalAnalysisController(StockBaseController):
|
||||
currency_str = f" {self.ticker} (from {self.start.strftime('%Y-%m-%d')})"
|
||||
help_text = f"""[cmds]
|
||||
[param]Currency Pair Loaded: [/param]{currency_str}
|
||||
[param]Source: [/param]{FOREX_SOURCES[self.source]}
|
||||
|
||||
[info]Overlap:[/info]
|
||||
ema exponential moving average
|
||||
|
||||
12
scripts/test_forex_load.gst
vendored
Normal file
12
scripts/test_forex_load.gst
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
forex
|
||||
to gbp
|
||||
load
|
||||
load --source av
|
||||
load --source av -s 2021-01-02
|
||||
load --source yf -s 2021-01-02
|
||||
load -s 2022-01-20
|
||||
load -s 2022-02-20
|
||||
load -i 1week -s 2021-01-20
|
||||
load -r w --source av
|
||||
load -r w --source av -s 2021-01-20
|
||||
exit
|
||||
@@ -52,6 +52,7 @@ def test_menu_with_queue(expected, mocker, queue):
|
||||
result_menu = ta_controller.TechnicalAnalysisController(
|
||||
ticker="MOCK_TICKER",
|
||||
start=datetime.strptime("2021-12-01", "%Y-%m-%d"),
|
||||
source="yf",
|
||||
interval="MOCK_INTERVAL",
|
||||
data=MOCK_STOCK_DF,
|
||||
queue=queue,
|
||||
@@ -95,6 +96,7 @@ def test_menu_without_queue_completion(mocker):
|
||||
|
||||
result_menu = ta_controller.TechnicalAnalysisController(
|
||||
ticker="MOCK_TICKER",
|
||||
source="yf",
|
||||
start=datetime.strptime("2021-12-01", "%Y-%m-%d"),
|
||||
interval="MOCK_INTERVAL",
|
||||
data=MOCK_STOCK_DF,
|
||||
@@ -145,6 +147,7 @@ def test_menu_without_queue_sys_exit(mock_input, mocker):
|
||||
|
||||
result_menu = ta_controller.TechnicalAnalysisController(
|
||||
ticker="MOCK_TICKER",
|
||||
source="yf",
|
||||
start=datetime.strptime("2021-12-01", "%Y-%m-%d"),
|
||||
interval="MOCK_INTERVAL",
|
||||
data=MOCK_STOCK_DF,
|
||||
@@ -165,6 +168,7 @@ def test_menu_without_queue_sys_exit(mock_input, mocker):
|
||||
def test_print_help(start):
|
||||
controller = ta_controller.TechnicalAnalysisController(
|
||||
ticker="MOCK_TICKER",
|
||||
source="yf",
|
||||
start=start,
|
||||
interval="MOCK_INTERVAL",
|
||||
data=MOCK_STOCK_DF,
|
||||
@@ -206,6 +210,7 @@ def test_print_help(start):
|
||||
def test_switch(an_input, expected_queue):
|
||||
controller = ta_controller.TechnicalAnalysisController(
|
||||
ticker="MOCK_FROM/MOCK_TO",
|
||||
source="yf",
|
||||
start=datetime.strptime("2021-12-01", "%Y-%m-%d"),
|
||||
interval="MOCK_INTERVAL",
|
||||
data=MOCK_STOCK_DF,
|
||||
@@ -221,6 +226,7 @@ def test_call_cls(mocker):
|
||||
mocker.patch("os.system")
|
||||
controller = ta_controller.TechnicalAnalysisController(
|
||||
ticker="MOCK_TICKER/MOCK_TICKER",
|
||||
source="yf",
|
||||
start=datetime.strptime("2021-12-01", "%Y-%m-%d"),
|
||||
interval="MOCK_INTERVAL",
|
||||
data=MOCK_STOCK_DF,
|
||||
@@ -280,6 +286,7 @@ def test_call_cls(mocker):
|
||||
def test_call_func_expect_queue(expected_queue, func, queue):
|
||||
controller = ta_controller.TechnicalAnalysisController(
|
||||
ticker="MOCK_FROM/MOCK_TO",
|
||||
source="yf",
|
||||
start=datetime.strptime("2021-12-01", "%Y-%m-%d"),
|
||||
interval="MOCK_INTERVAL",
|
||||
data=MOCK_STOCK_DF,
|
||||
@@ -469,6 +476,7 @@ def test_call_func(
|
||||
|
||||
controller = ta_controller.TechnicalAnalysisController(
|
||||
ticker="MOCK_FROM/MOCK_TO",
|
||||
source="yf",
|
||||
start=datetime.strptime("2021-12-01", "%Y-%m-%d"),
|
||||
interval="MOCK_INTERVAL",
|
||||
data=MOCK_STOCK_DF,
|
||||
@@ -484,6 +492,7 @@ def test_call_func(
|
||||
else:
|
||||
controller = ta_controller.TechnicalAnalysisController(
|
||||
ticker="MOCK_FROM/MOCK_TO",
|
||||
source="yf",
|
||||
start=datetime.strptime("2021-12-01", "%Y-%m-%d"),
|
||||
interval="MOCK_INTERVAL",
|
||||
data=MOCK_STOCK_DF,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
Currency Pair Loaded: MOCK_TICKER (from 2021-12-01)
|
||||
Source: YahooFinance
|
||||
|
||||
Overlap:
|
||||
ema exponential moving average
|
||||
|
||||
@@ -248,6 +248,7 @@ def test_call_func_expect_queue(expected_queue, func, queue):
|
||||
[TechnicalAnalysisController],
|
||||
dict(
|
||||
ticker="MOCK_TICKER/MOCK_TICKER",
|
||||
source="yf",
|
||||
data=NON_EMPTY_DF,
|
||||
start=pd.Timestamp("2020-11-30 00:00:00"),
|
||||
interval="",
|
||||
@@ -290,6 +291,7 @@ def test_call_func(
|
||||
else:
|
||||
controller = forex_controller.ForexController(queue=None)
|
||||
controller.stock = EMPTY_DF
|
||||
controller.source = "yf"
|
||||
controller.to_symbol = "MOCK_TICKER"
|
||||
controller.from_symbol = "MOCK_TICKER"
|
||||
controller.interval = "1440min"
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
from select the "from" currency in a forex pair
|
||||
to select the "to" currency in a forex pair
|
||||
|
||||
From: USD
|
||||
To:
|
||||
[dim]
|
||||
[AlphaVantage]
|
||||
quote get last quote
|
||||
From: USD
|
||||
To:
|
||||
Source: YahooFinance[dim]
|
||||
|
||||
quote get last quote [AlphaVantage]
|
||||
load get historical data
|
||||
candle show candle plot for loaded data
|
||||
|
||||
> ta technical analysis for loaded coin, e.g.: ema, macd, rsi, adx, bbands, obv
|
||||
|
||||
[/dim]
|
||||
Forex brokerages:
|
||||
> oanda Oanda menu
|
||||
|
||||
Reference in New Issue
Block a user