Fixed strange bug when trying to get the history of AZN.L as it was in GBp, not GBP. Also, some refactoring.
The issue was that with some shares, there is a currency with lowercase letters with means they are just a fraction of the all-uppercase equivalent (e.g. with AZN.L: GBp is 1/1000 of GBP). Now this is fixed, and newly discovered lowercase exceptions can be added in the init function by adding it to the currency_exceptions dictionary.
This commit is contained in:
parent
1d731eec10
commit
4a1d24c562
@ -56,7 +56,7 @@ You should refer to Yahoo!'s terms of use (here, here, and here) for details on
|
||||
## 2. Usage
|
||||
|
||||
### 2.1 If used as a CLI:
|
||||
TODO;
|
||||
TODO; also mention the strange but working GBp thing
|
||||
|
||||
### 2.2 If used as a Module in Python (via `import`):
|
||||
TODO;
|
||||
|
@ -15,15 +15,17 @@ class SimpleStockData:
|
||||
:param to_currency:
|
||||
currency to convert rates to (e.g. EUR)
|
||||
"""
|
||||
self.currency_exceptions = {"GBp": 0.001} # dict where currencies with lowercase letters and how they convert to
|
||||
# their uppercase equivalent (e.g. GBp is 0.001/1% of one GBP)
|
||||
|
||||
self.ticker_list = ticker_list
|
||||
self.to_currency = to_currency.upper() # make it uppercase
|
||||
self._to_currency = to_currency.upper() # make it uppercase
|
||||
self._period_start = period_start
|
||||
self._period_end = period_end
|
||||
self._ohcl = ohcl.capitalize()
|
||||
|
||||
self._exchange_df = None # Mapping: time mapped to conversion factor, to get the right converted value per date
|
||||
self._create_exchange_dataframe() # initialize self.exchange_df attribute
|
||||
self._able_to_convert = self._create_exchange_dataframe() # initialize self.exchange_df attribute and define if
|
||||
# convertible now
|
||||
|
||||
def _get_history(self, idx, interval="1d"):
|
||||
"""
|
||||
@ -49,39 +51,82 @@ class SimpleStockData:
|
||||
"""
|
||||
|
||||
# check if a to_currency is even given
|
||||
if self.to_currency == "":
|
||||
if self._to_currency == "":
|
||||
return False
|
||||
|
||||
# create the list of currencies based on all the stocks of the class
|
||||
_from_currency_list = []
|
||||
_shares_currency_list = []
|
||||
for i in range(len(self.ticker_list)): # to get all indexes; this adds an entry for each currency
|
||||
add_currency = f"{self.get_info(i, 'currency')}{self.to_currency}=X" # Format: "fffttt=X" f=from, t=to
|
||||
add_currency = add_currency.upper() # make everything uppercase
|
||||
new_currency = f"{self.get_info(i, 'currency')}{self._to_currency}=X" # Format: "fffttt=X" f=from, t=to
|
||||
new_currency = new_currency.upper() # make everything uppercase
|
||||
# for the case that FROM and TO are equal, just don't download the data (as conversion factor's 1)
|
||||
if add_currency == f"{self.to_currency}{self.to_currency}=X":
|
||||
if new_currency == f"{self._to_currency}{self._to_currency}=X":
|
||||
pass
|
||||
elif add_currency not in _from_currency_list: # add a new item if not already there
|
||||
_from_currency_list.append(add_currency)
|
||||
elif new_currency not in _shares_currency_list: # add a new item if not already there
|
||||
_shares_currency_list.append(new_currency)
|
||||
|
||||
# now the real process begins
|
||||
tickers = yf.Tickers(" ".join(_from_currency_list)) # create a new Ticker instance with all wanted currencies
|
||||
|
||||
# create a new Tickers instance with all wanted currencies
|
||||
tickers = yf.Tickers(" ".join(_shares_currency_list))
|
||||
|
||||
exchange_rates = [] # temporary variable where all exchange rates are stored in (as objects of pd.Series)
|
||||
for er_name in tickers.tickers: # get all the history of each currency conversion factors
|
||||
# for simplicity: using the conversion factor of closing (.Close at the end)
|
||||
exchange_rates.append((tickers.tickers[er_name].history(start=self._period_start, end=self._period_end)[self._ohcl], er_name))
|
||||
# using the conversion factor at wanted time (given as ohcl at object init; example: "Close", "High", etc.)
|
||||
exchange_rates.append(
|
||||
(tickers.tickers[er_name].history(start=self._period_start, end=self._period_end)[self._ohcl], er_name))
|
||||
# now exchange_rates contains tuples of the form (ticker, er_name) where er_name is the name of the
|
||||
# currency ticker, used only internal in this method (_from_currency_list variable). The index is now taken
|
||||
# to set the right names for every row in the dataframe
|
||||
# currency ticker, used only internal in this method. The index is now taken to set the right names for
|
||||
# every row in the dataframe
|
||||
|
||||
# now, the rates are taken from the exchange_rates list and are all wrapped up in a beautiful DataFrame
|
||||
# now, the rates are taken from the exchange_rates list and are all wrapped up in a beautiful pandas DataFrame
|
||||
self._exchange_df = pd.DataFrame()
|
||||
for exchange_rate, er_name in exchange_rates:
|
||||
self._exchange_df[er_name] = exchange_rate
|
||||
self._exchange_df[f"{self.to_currency}{self.to_currency}=X"] = 1.0 # for FROM and TO being equal: set factor to 1
|
||||
self._exchange_df[
|
||||
f"{self._to_currency}{self._to_currency}=X"] = 1.0 # for FROM and TO being equal: set factor to 1
|
||||
|
||||
# now the currency exceptions (as GBp isn't the same as GBP, see comment at definition of self.currency_exceptions
|
||||
for currency_exception in self.currency_exceptions:
|
||||
self._exchange_df[f"{currency_exception}{currency_exception.upper()}"] = self.currency_exceptions[currency_exception]
|
||||
|
||||
return True
|
||||
|
||||
def _get_history_convert_result(self, result, ticker_currency):
|
||||
"""
|
||||
Helper method for the get_history method. Converts the share prices in a given result DataFrame from the
|
||||
currency specified by ticker_currency to the class-wide self.to_currency and stores it in new columns in the df
|
||||
:param result:
|
||||
the result containing the unconverted share prices
|
||||
:param ticker_currency:
|
||||
the currency from which convert to the self._to_currency
|
||||
:return: pandas.DataFrame (with extra columns for the converted value)
|
||||
"""
|
||||
|
||||
exceptive_conversion = ticker_currency in self.currency_exceptions
|
||||
if exceptive_conversion:
|
||||
exceptive_conversion_factor = self.currency_exceptions[ticker_currency] # the additional conversion factor (usually 0.01)
|
||||
else:
|
||||
exceptive_conversion_factor = 1 # 1:1, don't change values
|
||||
|
||||
ex_rate_name = f"{ticker_currency.upper()}{self._to_currency}=X"
|
||||
result["ex_rate_name"] = ex_rate_name
|
||||
result["exceptive_conversion"] = exceptive_conversion
|
||||
if exceptive_conversion:
|
||||
result["exceptive_conversion_factor"] = exceptive_conversion_factor
|
||||
ex_rate_series = self._exchange_df[ex_rate_name]
|
||||
|
||||
# now there's a result dataframe with ticker, currency, rate name etc. as column names
|
||||
# to add only matching ex rates per day (sometimes there are more days with exchange rates recorded than
|
||||
# share prices), the result df has to be transposed so that the following function df.append can select
|
||||
# by columns.
|
||||
# TODO: implement the bug fix when not the exact same timestamps and amount of data are given in both the series and the df
|
||||
|
||||
result["ex_rate"] = ex_rate_series.to_list() # TODO: won't work with mismatching data (e.g. different exchange gaps over christmas)
|
||||
result[f"{self._ohcl} in {self._to_currency}"] = (result[self._ohcl] * exceptive_conversion_factor) * result["ex_rate"]
|
||||
|
||||
return result
|
||||
|
||||
def get_info(self, idx, key=""):
|
||||
"""
|
||||
:param idx:
|
||||
@ -112,29 +157,9 @@ class SimpleStockData:
|
||||
|
||||
result = self._get_history(idx, interval)[self._ohcl].to_frame()
|
||||
result["Ticker Index"] = idx
|
||||
ticker_currency = self.get_info(idx, "currency").upper() # upper it as sometimes it doesn't fit
|
||||
|
||||
if convert:
|
||||
exrate_name = f"{ticker_currency}{self.to_currency}=X"
|
||||
result["ex_rate_name"] = exrate_name
|
||||
ex_rate_series = self._exchange_df[exrate_name]
|
||||
|
||||
# now there's a result dataframe with ticker, currency, rate name etc. as column names
|
||||
# to add only matching ex rates per day (sometimes there are more days with exchange rates recorded than
|
||||
# share prices), the result df has to be transposed so that the following function df.append can select
|
||||
# by columns. TODO: implement the bug fix when not the exact same timestamps and amount of data are given in both the series and the df
|
||||
"""
|
||||
result = result.T
|
||||
|
||||
ex_rate_df = ex_rate_df.to_list()
|
||||
print(ex_rate_df)
|
||||
result.append(ex_rate_df[result.columns], ignore_index=True) # magic - see above :)
|
||||
result = result.T # transpose back
|
||||
result[f"{self._ohcl} in {self.to_currency}"] = result[self._ohcl] / result["ex_rate"]
|
||||
"""
|
||||
|
||||
result["ex_rate"] = ex_rate_series.to_list()
|
||||
result[f"{self._ohcl} in {self.to_currency}"] = result[self._ohcl] / result["ex_rate"]
|
||||
ticker_currency = self.get_info(idx, "currency")
|
||||
|
||||
if convert and self._able_to_convert:
|
||||
result = self._get_history_convert_result(result, ticker_currency)
|
||||
|
||||
return result
|
||||
|
Loading…
Reference in New Issue
Block a user