mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-11-02 07:23:25 +00:00
* Began the fax * Bumped ruff * Fixed a BUNCH of ruff errors * Fixed a lot of ruff errors * Simplify if statements * Fixed black * Updated some stuff * Updated some stuff * A lot more ruff fixes * Added a fix * Added a fix * Fixed mypy * Fixed some stuff * Reverted one thing * Small fix * Small fix * Small fix * Added fix * Added fix * Update requirements
302 lines
8.4 KiB
Python
302 lines
8.4 KiB
Python
"""Forex helper."""
|
|
import argparse
|
|
import logging
|
|
import os
|
|
import re
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, Iterable, List, Optional
|
|
|
|
import pandas as pd
|
|
import yfinance as yf
|
|
|
|
from openbb_terminal.core.plots.plotly_ta.ta_class import PlotlyTA
|
|
from openbb_terminal.decorators import log_start_end
|
|
from openbb_terminal.forex import av_model, polygon_model
|
|
from openbb_terminal.rich_config import console
|
|
from openbb_terminal.stocks import stocks_helper
|
|
|
|
CANDLE_SORT = [
|
|
"adjclose",
|
|
"open",
|
|
"close",
|
|
"high",
|
|
"low",
|
|
"volume",
|
|
"logret",
|
|
]
|
|
|
|
FOREX_SOURCES: Dict = {
|
|
"YahooFinance": "YahooFinance",
|
|
"AlphaVantage": "AlphaAdvantage",
|
|
"Polygon": "Polygon",
|
|
"Oanda": "Oanda",
|
|
}
|
|
|
|
SOURCES_INTERVALS: Dict = {
|
|
"YahooFinance": [
|
|
"1min",
|
|
"5min",
|
|
"15min",
|
|
"30min",
|
|
"60min",
|
|
"90min",
|
|
"1hour",
|
|
"1day",
|
|
"5day",
|
|
"1week",
|
|
"1month",
|
|
"3month",
|
|
],
|
|
"AlphaVantage": ["1min", "5min", "15min", "30min", "60min"],
|
|
}
|
|
|
|
INTERVAL_MAPS: Dict = {
|
|
"YahooFinance": {
|
|
"1min": "1m",
|
|
"2min": "2m",
|
|
"5min": "5m",
|
|
"15min": "15m",
|
|
"30min": "30m",
|
|
"60min": "60m",
|
|
"90min": "90m",
|
|
"1hour": "60m",
|
|
"1day": "1d",
|
|
"5day": "5d",
|
|
"1week": "1wk",
|
|
"1month": "1mo",
|
|
"3month": "3mo",
|
|
},
|
|
"AlphaVantage": {
|
|
"1min": 1,
|
|
"5min": 5,
|
|
"15min": 15,
|
|
"30min": 30,
|
|
"60min": 60,
|
|
"1day": 1,
|
|
},
|
|
}
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@log_start_end(log=logger)
|
|
def load(
|
|
to_symbol: str,
|
|
from_symbol: str,
|
|
resolution: str = "d",
|
|
interval: str = "1day",
|
|
start_date: Optional[str] = None,
|
|
end_date: Optional[str] = None,
|
|
source: str = "YahooFinance",
|
|
verbose: bool = False,
|
|
) -> pd.DataFrame:
|
|
"""Load forex for two given symbols.
|
|
|
|
Parameters
|
|
----------
|
|
to_symbol : str
|
|
The from currency symbol. Ex: USD, EUR, GBP, YEN
|
|
from_symbol : str
|
|
The from currency symbol. Ex: USD, EUR, GBP, YEN
|
|
resolution : str, optional
|
|
The resolution for the data, by default "d"
|
|
interval : str, optional
|
|
What interval to get data for, by default "1day"
|
|
start_date : Optional[str], optional
|
|
When to begin loading in data, by default last_year.strftime("%Y-%m-%d")
|
|
end_date : Optional[str], optional
|
|
When to end loading in data, by default None
|
|
source : str, optional
|
|
Where to get data from, by default "YahooFinance"
|
|
verbose : bool, optional
|
|
Display verbose information on what was the pair that was loaded, by default True
|
|
|
|
Returns
|
|
-------
|
|
pd.DataFrame
|
|
The loaded data
|
|
|
|
Examples
|
|
--------
|
|
>>> from openbb_terminal.sdk import openbb
|
|
>>> openbb.forex.load(from_symbol="EUR", to_symbol="USD", start_date="2020-11-30", end_date="2022-12-01")
|
|
"""
|
|
|
|
if start_date is None:
|
|
start_date = (datetime.now() - timedelta(days=365)).strftime("%Y-%m-%d")
|
|
if end_date is None:
|
|
end_date = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")
|
|
else:
|
|
end_date = (
|
|
datetime.strptime(end_date, "%Y-%m-%d") + timedelta(days=1)
|
|
).strftime("%Y-%m-%d")
|
|
|
|
if source in ["YahooFinance", "AlphaVantage"]:
|
|
interval_map = INTERVAL_MAPS[source]
|
|
|
|
if interval not in interval_map and resolution != "d":
|
|
if verbose:
|
|
console.print(
|
|
f"Interval not supported by {FOREX_SOURCES[source]}."
|
|
" Need to be one of the following options",
|
|
list(interval_map.keys()),
|
|
)
|
|
return pd.DataFrame()
|
|
|
|
# Check interval in multiple ways
|
|
if interval in interval_map:
|
|
clean_interval = interval_map[interval]
|
|
elif interval in interval_map.values():
|
|
clean_interval = interval
|
|
else:
|
|
console.print(f"[red]'{interval}' is an invalid interval[/red]\n")
|
|
return pd.DataFrame()
|
|
|
|
if source == "AlphaVantage":
|
|
if "min" in interval:
|
|
resolution = "i"
|
|
df = av_model.get_historical(
|
|
to_symbol=to_symbol,
|
|
from_symbol=from_symbol,
|
|
resolution=resolution,
|
|
interval=clean_interval,
|
|
start_date=start_date,
|
|
end_date=end_date,
|
|
)
|
|
df.index.name = "date"
|
|
df.name = f"{from_symbol}/{to_symbol}"
|
|
return df
|
|
|
|
if source == "YahooFinance":
|
|
df = yf.download(
|
|
f"{from_symbol}{to_symbol}=X",
|
|
start=datetime.strptime(start_date, "%Y-%m-%d"),
|
|
end=datetime.strptime(end_date, "%Y-%m-%d"),
|
|
interval=clean_interval,
|
|
progress=verbose,
|
|
)
|
|
df.index = pd.to_datetime(df.index).tz_localize(None)
|
|
df.index.name = "date"
|
|
df.name = f"{from_symbol}/{to_symbol}"
|
|
return df
|
|
|
|
if source == "Polygon":
|
|
# Interval for polygon gets broken into multiplier and timeframe
|
|
temp = re.split(r"(\d+)", interval)
|
|
multiplier = int(temp[1])
|
|
timeframe = temp[2]
|
|
if timeframe == "min":
|
|
timeframe = "minute"
|
|
df = polygon_model.get_historical(
|
|
f"{from_symbol}{to_symbol}",
|
|
multiplier=multiplier,
|
|
timespan=timeframe,
|
|
start_date=start_date,
|
|
end_date=end_date,
|
|
)
|
|
df.index.name = "date"
|
|
df.name = f"{from_symbol}/{to_symbol}"
|
|
return df
|
|
|
|
console.print(f"Source {source} not supported")
|
|
return pd.DataFrame()
|
|
|
|
|
|
@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 = "",
|
|
ma: Optional[Iterable[int]] = None,
|
|
external_axes: bool = False,
|
|
add_trend: bool = False,
|
|
yscale: str = "linear",
|
|
):
|
|
"""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
|
|
ma : Optional[Iterable[int]]
|
|
Moving averages
|
|
external_axes : bool, optional
|
|
Whether to return the figure object or not, by default False
|
|
"""
|
|
# We check if there's Volume data to avoid errors and empty subplots
|
|
if not (has_volume := False) and "Volume" in data.columns:
|
|
has_volume = bool(data["Volume"].sum() > 0)
|
|
|
|
if add_trend and (data.index[1] - data.index[0]).total_seconds() >= 86400:
|
|
if "date_id" not in data.columns:
|
|
data = stocks_helper.process_candle(data)
|
|
data = stocks_helper.find_trendline(data, "OC_High", "high")
|
|
data = stocks_helper.find_trendline(data, "OC_Low", "low")
|
|
|
|
indicators = {}
|
|
if ma:
|
|
indicators = dict(rma=dict(length=ma))
|
|
|
|
data.name = f"{from_symbol}/{to_symbol}"
|
|
fig = PlotlyTA.plot(data, indicators, volume=has_volume)
|
|
if add_trend:
|
|
fig.add_trend(data, secondary_y=False)
|
|
|
|
fig.update_yaxes(type=yscale, row=1, col=1, nticks=20, secondary_y=False)
|
|
|
|
return fig.show(external=external_axes)
|
|
|
|
|
|
@log_start_end(log=logger)
|
|
def parse_forex_symbol(input_symbol):
|
|
"""Parse potential forex symbols."""
|
|
for potential_split in ["-", "/"]:
|
|
if potential_split in input_symbol:
|
|
symbol = input_symbol.replace(potential_split, "")
|
|
return symbol
|
|
if len(input_symbol) != 6:
|
|
raise argparse.ArgumentTypeError("Input symbol should be 6 characters.\n ")
|
|
return input_symbol.upper()
|