diff --git a/datasist/timeseries.py b/datasist/timeseries.py index 9cfc3cb4..98eda733 100644 --- a/datasist/timeseries.py +++ b/datasist/timeseries.py @@ -6,8 +6,11 @@ import pandas as pd import numpy as np import matplotlib.pyplot as plt +import random +import yfinance as yf +from plotly import graph_objs as go import seaborn as sns -from IPython.display import display +from IPython.display import display, Image from .structdata import get_cat_feats, get_num_feats, get_date_cols @@ -226,7 +229,7 @@ def get_period_of_day(date_col=None): raise ValueError("date_cols: Expect a date columns, got 'None'") - if date_col.dtype != np.int: + if date_col.dtype != np.int64: date_col_hr = pd.to_datetime(date_col).dt.hour return date_col_hr.map(_map_hours) @@ -388,4 +391,168 @@ def _map_hours(x): elif x in [13,14,15,16]: return 'afternoon' else: - return 'evening' \ No newline at end of file + return 'evening' + + +def get_crypto_visuals(coin, period="5d", interval="15m", MA=False, days=[7,25,99], boll=False, boll_sma=25, save_fig=False, img_format="png"): + + + + + ''' + + Utilizing Plotly and yfinance to make a Stock Market Coin-USD timeseries plot using Candlesticks + + + + Parameters + ----------------- + + coin: str + + - Symbol of Crypto coin i.e 'BTC', 'ETH', 'DOGE', 'BNB', 'SXP' + - Must be currently listed on https://finance.yahoo.com/cryptocurrencies/ + + + period: str, Default "5d" + + - Answers the question - How far back from realtime should data be sourced? + - Valid periods: '1d','5d','1mo','3mo','6mo','1y','2y','5y','10y','ytd','max' + + + interval: str, Default "15m" + + - References intra-period intervals of data + - Valid intervals: '1m','2m','5m','15m','30m','60m','90m','1h','1d','5d','1wk','1mo','3mo' + - Intraday data cannot extend last 60 days + + + MA: bool, Default False + + - References the plotting of the Moving Average (MA) data + + + + days: list, tuple, 1D array, Pandas Series, Set, Default = [7, 25, 99] + + - Must be an iterable containing integer values only + - The integers represent, day subsets for the Moving Average data + + + boll: bool, Default False + + - References the plotting of the bollinger band + - Important: • When both MA and boll are True, MA takes preference + • The above algorithm is in place to avoid a rowdy chart + + boll_sma: Integer, Default 25 + + - Indicates the SMA value used as a reference point for plotting the bollinger band + + + save_fig: bool, Default False + + - Saves the plot to the current working directory + + + img_format: str, Default "png" + - The desired image format:'png', 'jpg' or 'jpeg', 'webp', 'svg', and 'pdf' + + + + + Returns + ----------------- + None + + + Examples of valid use + ----------------- + >>> get_crypto_visuals("BTC") + >>> get_crypto_visuals("BTC", MA=True) + >>> get_crypto_visuals("BTC", period="10d", interval="5m", MA=True, days=[10,30,100], save_fig=True, img_format='jpeg') + >>> get_crypto_visuals("BTC", period="10d", interval="5m", MA=False, days=[10,30,100], boll=True) + + ''' + + #Error Handling + if coin is None: raise ValueError("coin: Symbol of Crypto coin i.e 'BTC', 'ETH', 'DOGE', 'BNB', 'SXP'\nMust be currently listed on https://finance.yahoo.com/cryptocurrencies/") + if not isinstance(period, str): raise TypeError('period: This parameter should be a string') + if not isinstance(interval, str): raise TypeError('interval: This parameter should be a string') + if not isinstance(img_format, str): raise TypeError('img_format: This parameter should be a string') + if not isinstance(coin, str): raise TypeError('coin: This parameter should be a string') + if not isinstance(MA, bool): raise TypeError('MA: This parameter should be boolean (True or False)') + if not isinstance(boll, bool): raise TypeError('boll: This parameter should be boolean (True or False)') + if not isinstance(save_fig, bool): raise TypeError('save_fig: This parameter should be boolean (True or False)') + if not isinstance(boll_sma, int): raise TypeError('boll_sma: This parameter should be an Integer') + if (not hasattr(days, '__iter__')) or isinstance(days, str): raise TypeError("days: This parameter should be iterable, strings aren't also allowed") + + + + + + coin = coin.upper().strip() + data = yf.download(tickers=f'{coin}-USD',period = period, interval = interval) + header = f"{coin}-USD Price Analysis" + + + #Generate Moving Average + if MA==True: + for i in days: data[f'MA{i}'] = data['Close'].rolling(i).mean() + + + #Generate Bollinger + def get_bollinger_band(prices, rate=boll_sma): + sma = prices.rolling(rate).mean() + std = prices.rolling(rate).std() + bollinger_up = sma + std * 2 + bollinger_down = sma - std * 2 + return sma, bollinger_up, bollinger_down + + if boll: + if not (MA or (boll and MA)): sma, u,d = get_bollinger_band(data['Close']) + + #declare figure + fig = go.Figure() + + + #Candlestick + fig.add_trace(go.Candlestick(x=data.index, + open=data['Open'], + high=data['High'], + low=data['Low'], + close=data['Close'], + name = 'Market data')) + #Randomly pick from preferred colors + col_lst = ['pink','darkgray','orange','darkblue','darkcyan','darkgoldenrod','darkgray','darkgrey','darkkhaki','darkmagenta','darkorange','darkorchid','darksalmon','darkslateblue','darkslategray','darkslategrey','darkturquoise','darkviolet','deeppink'] + try: col_sam = random.sample(col_lst, len(days)) + except ValueError: col_sam = np.random.choice(col_lst, len(days)) + + + #Add Moving average on the chart + co = 0 + for col in data.columns: + if col.startswith('MA'): + fig.add_trace(go.Scatter(x=data.index, y= data[col],line={'color':col_sam[co],'width':1.5}, name = col)) + co+=1 + + #Add Bollinger on the chart + if boll: + if not (MA or (boll and MA)): + fig.add_trace(go.Scatter(x=data.index, y= u,line={'color':'darkmagenta','width':1.5}, name = "BOLL (Up)")) + fig.add_trace(go.Scatter(x=data.index, y=sma, line={'color':'orange','width':1.5}, name="BOLL (Mid)")) + fig.add_trace(go.Scatter(x=data.index, y= d,line={'color':'darkblue','width':1.5}, name = "BOLL (Down)")) + + + #Updating axis and graph + fig.update_xaxes(title=f'Datetime', rangeslider_visible =True) + + fig.update_yaxes(title='USD ($)') + fig.update_layout({"title": {"text": header}}) + + #Show + fig.show() + + + #Save + if save_fig: fig.write_image("fig_timeplot_{}-usd.{}".format(coin.lower(), img_format.lower()), engine='kaleido') \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index f0783faf..b47e188e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,6 +20,7 @@ jupyter==1.0.0 jupyter-client==6.1.3 jupyter-console==6.1.0 jupyter-core==4.6.3 +kaleido==0.2.1 kiwisolver==1.2.0 MarkupSafe==1.1.1 matplotlib==3.2.2 @@ -36,8 +37,9 @@ pandocfilters==1.4.2 parso==0.7.0 pexpect==4.8.0 pickleshare==0.7.5 +plotly==0.5.0 pluggy==0.13.1 -ppscore==0.0.3 +ppscore==1.2.0 prometheus-client==0.8.0 prompt-toolkit==3.0.5 ptyprocess==0.6.0 @@ -45,7 +47,7 @@ py==1.10.0 Pygments==2.7.4 pyparsing==2.4.7 pyrsistent==0.16.0 -pytest==5.4.3 +pytest==6.2.4 python-dateutil==2.8.1 pytz==2020.1 pyzmq==19.0.1 @@ -66,4 +68,5 @@ traitlets==4.3.3 wcwidth==0.2.5 webencodings==0.5.1 widgetsnbextension==3.5.1 +yfinance==0.1.63 zipp==3.1.0