def bollinger_bands(dataframe, period=21, stdv=2, field='close', colum_prefix="bb") -> DataFrame: from pyti.bollinger_bands import lower_bollinger_band, middle_bollinger_band, upper_bollinger_band dataframe["{}_lower".format(colum_prefix)] = lower_bollinger_band(dataframe[field], period, stdv) dataframe["{}_middle".format(colum_prefix)] = middle_bollinger_band(dataframe[field], period, stdv) dataframe["{}_upper".format(colum_prefix)] = upper_bollinger_band(dataframe[field], period, stdv) return dataframe
def bollinger(symbol): URL = "https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=" + symbol + "&apikey=" + config.alphaadvantage_api_key_id r = requests.get(url = URL) data = r.json() days = data["Time Series (Daily)"] closingPrices = [] for day in days: closingPrices.append(float(days[day]['4. close'])) cleanedData = [] period = 2 upperbands = upper_bollinger_band(closingPrices, 20) lowerBands = lower_bollinger_band(closingPrices, 20) ub = [] lb = [] for upper in upperbands: if(math.isnan(upper) == False ): ub.append(upper) for lower in lowerBands: if(math.isnan(lower) == False ): lb.append(lower) # if current price is greater than upper bound sell if(closingPrices[0] >= ub[0]): return (1) # if current price is lower than lower bound buy if(closingPrices[0] <= lb[0]): return 1 # if within 2% of upperband band then sell if( (1 - (closingPrices[0] / ub[0]))*100 <= 2 ): return 0 # if within 2% of lower band buy if( (1 - (lb[0] / closingPrices[0]))*100 <= 2 ): return 0 # if inside middle range look for trajectory up or down if(closingPrices[0] > closingPrices[2]): return 1 else: return 0
def get_analysis(self, data, bollinger_function): if bollinger_function == 'upper': return bollinger_bands.upper_bollinger_band( data, self.params['period']) if bollinger_function == 'middle': return bollinger_bands.middle_bollinger_band( data, self.params['period']) if bollinger_function == 'lower': return bollinger_bands.lower_bollinger_band( data, self.params['period']) if bollinger_function == 'bandwidth': return bollinger_bands.bandwidth(data, self.params['period']) if bollinger_function == 'range': return bollinger_bands.bb_range(data, self.params['period']) if bollinger_function == 'percent_bandwidth': return bollinger_bands.percent_bandwidth(data, self.params['period']) if bollinger_function == 'percent_b': return bollinger_bands.percent_b(data, self.params['period'])
def Update(): print(str(dt.datetime.now()) + " " + timeframe + " Bar Closed - Running Update Function...") # Calculate Indicators iBBUpper = bb.upper_bollinger_band(pricedata['bidclose'], bb_periods, bb_standard_deviations) iBBMiddle = bb.middle_bollinger_band(pricedata['bidclose'], bb_periods, bb_standard_deviations) iBBLower = bb.lower_bollinger_band(pricedata['bidclose'], bb_periods, bb_standard_deviations) iADX = adx(pricedata['bidclose'], pricedata['bidhigh'], pricedata['bidlow'], adx_periods) # Declare simplified variable names for most recent close candle close_price = pricedata['bidclose'][len(pricedata)-1] BBUpper = iBBUpper[len(iBBUpper)-1] BBMiddle = iBBMiddle[len(iBBMiddle)-1] BBLower = iBBLower[len(iBBLower)-1] ADX = iADX[len(iADX)-1] # Print Price/Indicators print("Close Price: " + str(close_price)) print("Upper BB: " + str(BBUpper)) print("Middle BB: " + str(BBMiddle)) print("Lower BB: " + str(BBLower)) print("ADX: " + str(ADX)) # TRADING LOGIC # Change Any Existing Trades' Limits to Middle Bollinger Band if countOpenTrades()>0: openpositions = con.get_open_positions(kind='list') for position in openpositions: if position['currency'] == symbol: print("Changing Limit for tradeID: " + position['tradeId']) try: editlimit = con.change_trade_stop_limit(trade_id=position['tradeId'], is_stop=False, rate=BBMiddle, is_in_pips=False) except: print(" Error Changing Limit.") else: print(" Limit Changed Successfully.") # Entry Logic if ADX < adx_trade_below: if countOpenTrades("B") == 0 and close_price < BBLower: print(" BUY SIGNAL!") print(" Opening Buy Trade...") stop = pricedata['askclose'][len(pricedata)-1] - (BBMiddle - pricedata['askclose'][len(pricedata)-1]) limit = BBMiddle enter("B", stop, limit) if countOpenTrades("S") == 0 and close_price > BBUpper: print(" SELL SIGNAL!") print(" Opening Sell Trade...") stop = pricedata['bidclose'][len(pricedata)-1] + (pricedata['bidclose'][len(pricedata)-1] - BBMiddle) limit = BBMiddle enter("S", stop, limit) # Exit Logic if countOpenTrades("B") > 0 and close_price > BBMiddle: print(" Closing Buy Trade(s)...") exit("B") if countOpenTrades("S") > 0 and close_price < BBMiddle: print(" Closing Sell Trade(s)...") exit("S") print(str(dt.datetime.now()) + " " + timeframe + " Update Function Completed.\n")
def test_upper_bollinger_bands_invalid_period(self): period = 128 with self.assertRaises(Exception) as cm: bollinger_bands.upper_bollinger_band(self.data, period) expected = "Error: data_len < period" self.assertEqual(str(cm.exception), expected)
def test_upper_bollinger_bands_period_6(self): period = 6 upper_bb = bollinger_bands.upper_bollinger_band(self.data, period) np.testing.assert_array_equal(upper_bb, self.upper_bb_period_6_expected)
def calc_feature(data, feature, args): #helper function for creating logs def replace_zero_with_min(series): return series.replace(0, series.loc[series > 0].min()) import numpy as np import pandas as pd from pyti import bollinger_bands as bbands if feature == "SMA": if len(args) != 2: raise ValueError("SMA requires 2 args; period and column name") return data[args[1]].rolling(int(args[0])).mean() elif feature == "BBANDS": if len(args) != 4: raise ValueError( "BBANDS requires 4 args; period, std dev, data_type (1=upper, 2=lower, 3=middle, 4=range) and column name" ) if (int(args[2]) == 1): return bbands.upper_bollinger_band(data[args[3]], int(args[0]), float(args[1])) elif (int(args[2]) == 2): return bbands.lower_bollinger_band(data[args[3]], int(args[0]), float(args[1])) elif (int(args[2]) == 3): return bbands.middle_bollinger_band(data[args[3]], int(args[0]), float(args[1])) elif (int(args[2]) == 4): return bbands.bb_range(data[args[3]], int(args[0]), float(args[1])) else: raise ValueError( "BBANDS data_type must be one of (1=upper, 2=lower, 3=middle, 4=range)" ) elif feature == "ATR": if len(args) != 4: raise ValueError( "ATR requires 4 args; period and close column, high column, low column" ) range_table = pd.DataFrame() range_table["A"] = data[args[2]] - data[args[3]] range_table["B"] = abs(data[args[2]] - data[args[1]].shift(1)) range_table["C"] = abs(data[args[3]] - data[args[1]].shift(1)) range_table["max"] = range_table[['A', 'B', 'C']].max(axis=1) return range_table["max"].rolling(int(args[0])).mean() elif feature == "VOLATILITY_LOG_MA": #calculates relative volatility of 12 periods compared to 200 the #smooths with a moving average if len(args) != 3: raise ValueError( "ATR requires 3 args; period, high column name, low column name" ) data["volatility_12"] = (data[args[1]] - data[args[2]]).rolling(12).std() data["volatility_200"] = (data[args[1]] - data[args[2]]).rolling(200).std() data["volatility"] = data["volatility_12"] / data["volatility_200"] data["volatility_log"] = np.log( replace_zero_with_min(data["volatility"])) return data["volatility_log"].rolling(int(args[0])).mean() elif feature == "VOLUME_LOG_MA": if len(args) != 2: raise ValueError( "VOLUME_LOG_MA requires 2 args; period, column name") data["volume_log"] = np.log(replace_zero_with_min( data[args[1]])) / np.log( replace_zero_with_min(data[args[1]].rolling(200).mean())) return data["volume_log"].rolling(int(args[0])).mean()
def add_features(asset_data, features=None, save_path=None, verbose=True): """ Adds features to the bar data. If no features are passed then all features are added. Args: asset_data ((str, pd.DataFrame) or (str, pd.DataFrame)[]): a single or list of tuples (asset name, bar data) containing the bar data features (str[]): a list of features to include, all features if this is None save_path (str): Path to save the bar data with features. A placeholder {ASSET} that will be substituted with the asset name verbose (bool): True if progress printing to console is desired Returns: (str, pd.DataFrame): a single tuple of (asset name, dataframe) of the bar data if a single asset was passed or an array of (asset name, dataframes) if an array of assets was passed """ #helper function for creating logs def replace_zero_with_min(series): return series.replace(0, series.loc[series > 0].min()) import pandas as pd import numpy as np from pyti import bollinger_bands as bbands from pyti import average_true_range as atr #if a single dataframe is passed just put it in a single item list if type(asset_data) == tuple: asset_data = [asset_data] elif type(asset_data) != list: raise ValueError( 'asset_data must be a pandas.DataFrame or a list of pandas.Dataframe.' ) feature_bars = [] for asset, data in asset_data: if verbose: print("Calculating features for {0}".format(asset)) #Lower Bollinger Band, 20 periods, std = 2 if features == None or 'lower_bb' in features: data['lower_bb'] = bbands.lower_bollinger_band(data["close"], 20, std=2.0) #Upper Bollinger Band, 20 periods, std = 2 if features == None or 'upper_bb' in features: data['upper_bb'] = bbands.upper_bollinger_band(data["close"], 20, std_mult=2.0) #Average True Range if features == None or 'atr' in features: data["atr"] = atr.average_true_range(data["close"], 24) / \ atr.average_true_range(data["close"], 200) #Volatility is the standard deviation over 12 periods of the difference #between high and low of the bar if features == None or 'volatility_12' in features: data["volatility_12"] = (data["high"] - data["low"]).rolling(12).std() #Volatility is the standard deviation over 200 periods of the difference #between high and low of the bar if features == None or 'volatility_200' in features: data["volatility_200"] = (data["high"] - data["low"]).rolling(200).std() #Volatility is relative change of the alst 12 bars over the last 200 if features == None or 'volatility' in features: data["volatility"] = data["volatility_12"] / data["volatility_200"] #Relative volume compared to the last 100 bars if features == None or 'volume_change' in features: data["volume_change"] = data["volume"] / data["volume"].rolling( 100).mean() #Distance between the close price and the upper bollinger bad if features == None or 'bb_dist_upper' in features: data["bb_dist_upper"] = data["upper_bb"] - data["close"] #Distance between the close price and the lower bollinger bad if features == None or 'bb_dist_lower' in features: data["bb_dist_lower"] = -(data["lower_bb"] - data["close"]) #Distance between the upper and lower bollinger bands if features == None or 'bb_range' in features: data["bb_range"] = (data["upper_bb"] - data["lower_bb"]) / data["close"] #The absolute value of the % return over the last 4 bars if features == None or 'change_4bar' in features: data["change_4bar"] = np.abs( np.log(data["close"] / data["close"].shift(4))) #The Augmented Dicker Fuller test which can show mean reverting or trending markets #from statsmodels.tsa.stattools import adfuller #if features == None or 'adf' in features: # data["adf"] = data['Close'].rolling(200).apply(lambda x: adfuller(x)[0], raw=False) #The log of the % return if features == None or 'log_return' in features: data["log_return"] = np.log(data["close"] / data["close"].shift(1)) #TODO add in the if feature statements #add logs - do this by replacing all zeros with the minimum value after zeros are removed if 1 == 0: data["volume_log"] = np.log(replace_zero_with_min( data["volume"])) / np.log( replace_zero_with_min(data["volume"].rolling(200).mean())) data["atr_log"] = np.log(replace_zero_with_min(data["atr"])) data["volatility_log"] = np.log( replace_zero_with_min(data["volatility"])) data["change_4bar_log"] = np.log( replace_zero_with_min(data["change_4bar"])) #add moving averages data["volume_log_ma_12"] = data["volume_log"].rolling(12).mean() data["volume_log_ma_24"] = data["volume_log"].rolling(24).mean() data["volume_log_ma_48"] = data["volume_log"].rolling(48).mean() data["volatility_log_ma_12"] = data["volatility_log"].rolling( 12).mean() data["volatility_log_ma_24"] = data["volatility_log"].rolling( 24).mean() data["volatility_log_ma_48"] = data["volatility_log"].rolling( 48).mean() data["change_4bar_log_ma_12"] = data["change_4bar_log"].rolling( 12).mean() data["change_4bar_log_ma_24"] = data["change_4bar_log"].rolling( 24).mean() data["change_4bar_log_ma_48"] = data["change_4bar_log"].rolling( 48).mean() data["log_return_ma_12"] = data["log_return"].rolling(12).mean() data["log_return_ma_24"] = data["log_return"].rolling(24).mean() data["log_return_ma_48"] = data["log_return"].rolling(48).mean() feature_bars.append((asset, data)) if save_path is not None: if verbose: print("Saving {0}...".format(asset)) save_location = save_path.replace("{ASSET}", asset) data.to_csv(save_location) #if there was just one dataFrame just return a single dataframe, otherwise #return the list of dataframes, one for each asset if len(feature_bars) == 1: return feature_bars[0] else: return feature_bars