I'm looking to improve my OOP skillset and have wrote a script to pull stock data and run some simple statistics. I am able to run and call each function within the Evaluation class individually (commented below), but run into issues when trying to loop through a list of tickers and append the statistics to the initial dataframe.
import datetime as d
import pandas as pd
import pandas_datareader.data as web
import numpy as np
start = d.datetime(2019, 1, 1)
end = d.datetime(2020, 4, 17)
class Security(object):
def _init__(self, ticker, data_platform, start_date, end_date):
self.ticker = ticker
self.data_platform = data_platform
self.start_date = start_date
self.end_date = end_date
def fetch_stock_data(self, ticker, data_platform, start_date, end_date):
df = web.DataReader(ticker, data_platform, start_date, end_date)
return df
class Evaluation(Security):
def __init__(self, ticker, data_platform, start_date, end_date):
self.df = Security.fetch_stock_data(
self, ticker, data_platform, start_date, end_date)
def simple_moving_average(self, period):
df = self.df
df['SMA-{}'.format(period)] = df['Adj Close'].rolling(period).mean()
return df['SMA-{}'.format(period)]
def exp_moving_average(self, period):
df = self.df
df['EMA_{}'.format(period)] = df['Adj Close'].ewm(span=period).mean()
return df['EMA_{}'.format(period)]
def rsi(self, period):
df = self.df
delta = df['Adj Close'].diff()
up = delta * 0
down = up.copy()
up[delta > 0] = delta[delta > 0]
down[delta < 0] = -delta[delta < 0]
up[up.index[period - 1]] = np.mean(up[:period])
up = up.drop(up.index[:(period - 1)])
down[down.index[period - 1]] = np.mean(down[:period])
down = down.drop(down.index[:(period - 1)])
rs = up.ewm(span=period - 1).mean() / down.ewm(span=period - 1).mean()
rsi_calc = 100 - 100 / (1 + rs)
df['rsi'] = rsi_calc
return df['rsi']
# pypl = Evaluation('PYPL', 'yahoo', start, end)
# print(csgs.df)
# print(csgs.simple_moving_average(50))
# print(csgs.exp_moving_average(26))
# print(csgs.rsi(14))
tickers = ['PYPL', 'TSLA']
for i in tickers:
df = Evaluation(i, 'yahoo', start, end)
df['SMA'] = df.simple_moving_average(50)
df['EMA'] = df.exp_moving_average(26)
df['rsi'] = df.rsi(14)
print(df)
I'm receiving a TypeError, which I believe to be related to referencing the Evaluation class.
TypeError: 'Evaluation' object does not support item assignment
1 Answer 1
You are confusing object methods with dataframe methods. In your example, df
is an Evaluation
object, not a dataframe.
>>>e = Evaluation()
>>>type(e)
__main__.Evaluation
>>>type(e.df)
pandas.core.frame.DataFrame
The line df['SMA'] = df.simple_moving_average(50)
fails because you can't add a column to an object. You need to use df.df['SMA'] = df.simple_moving_average(50)
.
As NomadMonad pointed out, it is confusing to use df as the variable name for an Evaluation object, so it would be better to give it a different name. However, eval
is a built-in function in python, so it would be better to use e
.
Also, you should change the class designs for several reasons
In python 3 there is no need to inherit from
Object
The
__init__
method ofSecurity
has only one leading underscore instead of two.You don't want
Evaluation
to inherit fromSecurity
. Instead, pass aSecurity
object in the__init__
method ofEvaluation
.You don't want to call a method that scrapes a website when you instantiate an object. The call to pandas_datareader should be a separate method.
You don't need to pass parameters to methods if those values are set in the
__init__
method. You can access them withself
.You don't need modify the underlying dataframe in your Evaluation methods. Instead return the value produced by the method.
import datetime
import pandas as pd
import numpy as np
import pandas_datareader.data as web
class Security:
def _init__(self, ticker, data_platform, start_date, end_date):
self.ticker = ticker
self.data_platform = data_platform
self.start_date = start_date
self.end_date = end_date
self.df = None
def fetch_stock_data(self):
self.df = web.DataReader(self.ticker, self.data_platform, self.start_date, self.end_date)
class Evaluation:
def __init__(self, security):
self.security = security
def simple_moving_average(self, period):
df = self.security.df
return df['Adj Close'].rolling(period).mean()
def exp_moving_average(self, period):
df = self.security.df
return df['Adj Close'].ewm(span=period).mean()
def rsi(self, period):
df = self.security.df
delta = df['Adj Close'].diff()
up = delta * 0
down = up.copy()
up[delta > 0] = delta[delta > 0]
down[delta < 0] = -delta[delta < 0]
up[up.index[period - 1]] = np.mean(up[:period])
up = up.drop(up.index[:(period - 1)])
down[down.index[period - 1]] = np.mean(down[:period])
down = down.drop(down.index[:(period - 1)])
rs = up.ewm(span=period - 1).mean() / down.ewm(span=period - 1).mean()
return 100 - 100 / (1 + rs)
start = datetime.datetime(2019, 1, 1)
end = datetime.datetime(2019, 4, 17)
s = Security(ticker, 'yahoo', start, end)
e = Evaluation(security=s)
df
attribute not adf
eval = Evaluation(i, 'yahoo', start, end)
theneval.df['foo'] = data