├── README.md └── indicators.py /README.md: -------------------------------------------------------------------------------- 1 | # Stock Market Indicators 2 | A small Python library with most the common stock market indicators. 3 | 4 | ## Requirements 5 | * Pandas 6 | * Numpy 7 | 8 | ## Installation 9 | Clone or download the indicators.py file into your project directory. 10 | 11 | 12 | ## Usage 13 | Import the module: 14 | import indicators 15 | 16 | The functions in this library accept the data in Pandas DataFrame format. The data should contain OPEN, HIGH, LOW, CLOSE and VOLUME columns. See the comments for each function for the list of required columns. Their default names are hardcoded in functions' params, however you may supply your own column names, if they are different. Sometimes you would also need to provide periods over which to calculate the indicator values. However, for all of them the default (recommended) values are pre-assigned. 17 | 18 | ## List of implemented techinical indicators 19 | * Exponential moving average (EMA) 20 | * Moving Average Convergence/Divergence Oscillator (MACD) 21 | * Accumulation Distribution (A/D) 22 | * On Balance Volume (OBV) 23 | * Price-volume trend (PVT) 24 | * Average true range (ATR) 25 | * Bollinger Bands 26 | * Chaikin Oscillator 27 | * Typical Price 28 | * Ease of Movement 29 | * Mass Index 30 | * Average directional movement index 31 | * Money Flow Index (MFI) 32 | * Negative Volume Index (NVI) 33 | * Positive Volume Index (PVI) 34 | * Momentum 35 | * Relative Strenght Index (RSI) 36 | * Chaikin Volatility (CV) 37 | * William's Accumulation/Distribution 38 | * William's % R 39 | * TRIX 40 | * Ultimate Oscillator 41 | 42 | ## License 43 | GNU General Public License 44 | -------------------------------------------------------------------------------- /indicators.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright, Rinat Maksutov, 2017. 3 | License: GNU General Public License 4 | """ 5 | 6 | import numpy as np 7 | import pandas as pd 8 | 9 | """ 10 | Exponential moving average 11 | Source: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages 12 | Params: 13 | data: pandas DataFrame 14 | period: smoothing period 15 | column: the name of the column with values for calculating EMA in the 'data' DataFrame 16 | 17 | Returns: 18 | copy of 'data' DataFrame with 'ema[period]' column added 19 | """ 20 | def ema(data, period=0, column=''): 21 | data['ema' + str(period)] = data[column].ewm(ignore_na=False, min_periods=period, com=period, adjust=True).mean() 22 | 23 | return data 24 | 25 | """ 26 | Moving Average Convergence/Divergence Oscillator (MACD) 27 | Source: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_convergence_divergence_macd 28 | Params: 29 | data: pandas DataFrame 30 | period_long: the longer period EMA (26 days recommended) 31 | period_short: the shorter period EMA (12 days recommended) 32 | period_signal: signal line EMA (9 days recommended) 33 | column: the name of the column with values for calculating MACD in the 'data' DataFrame 34 | 35 | Returns: 36 | copy of 'data' DataFrame with 'macd_val' and 'macd_signal_line' columns added 37 | """ 38 | def macd(data, period_long=26, period_short=12, period_signal=9, column=''): 39 | remove_cols = [] 40 | if not 'ema' + str(period_long) in data.columns: 41 | data = ema(data, period_long) 42 | remove_cols.append('ema' + str(period_long)) 43 | 44 | if not 'ema' + str(period_short) in data.columns: 45 | data = ema(data, period_short) 46 | remove_cols.append('ema' + str(period_short)) 47 | 48 | data['macd_val'] = data['ema' + str(period_short)] - data['ema' + str(period_long)] 49 | data['macd_signal_line'] = data['macd_val'].ewm(ignore_na=False, min_periods=0, com=period_signal, adjust=True).mean() 50 | 51 | data = data.drop(remove_cols, axis=1) 52 | 53 | return data 54 | 55 | """ 56 | Accumulation Distribution 57 | Source: http://stockcharts.com/school/doku.php?st=accumulation+distribution&id=chart_school:technical_indicators:accumulation_distribution_line 58 | Params: 59 | data: pandas DataFrame 60 | trend_periods: the over which to calculate AD 61 | open_col: the name of the OPEN values column 62 | high_col: the name of the HIGH values column 63 | low_col: the name of the LOW values column 64 | close_col: the name of the CLOSE values column 65 | vol_col: the name of the VOL values column 66 | 67 | Returns: 68 | copy of 'data' DataFrame with 'acc_dist' and 'acc_dist_ema[trend_periods]' columns added 69 | """ 70 | def acc_dist(data, trend_periods=21, open_col='', high_col='', low_col='', close_col='', vol_col=''): 71 | for index, row in data.iterrows(): 72 | if row[high_col] != row[low_col]: 73 | ac = ((row[close_col] - row[low_col]) - (row[high_col] - row[close_col])) / (row[high_col] - row[low_col]) * row[vol_col] 74 | else: 75 | ac = 0 76 | data.set_value(index, 'acc_dist', ac) 77 | data['acc_dist_ema' + str(trend_periods)] = data['acc_dist'].ewm(ignore_na=False, min_periods=0, com=trend_periods, adjust=True).mean() 78 | 79 | return data 80 | 81 | """ 82 | On Balance Volume (OBV) 83 | Source: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:on_balance_volume_obv 84 | Params: 85 | data: pandas DataFrame 86 | trend_periods: the over which to calculate OBV 87 | close_col: the name of the CLOSE values column 88 | vol_col: the name of the VOL values column 89 | 90 | Returns: 91 | copy of 'data' DataFrame with 'obv' and 'obv_ema[trend_periods]' columns added 92 | """ 93 | def on_balance_volume(data, trend_periods=21, close_col='', vol_col=''): 94 | for index, row in data.iterrows(): 95 | if index > 0: 96 | last_obv = data.at[index - 1, 'obv'] 97 | if row[close_col] > data.at[index - 1, close_col]: 98 | current_obv = last_obv + row[vol_col] 99 | elif row[close_col] < data.at[index - 1, close_col]: 100 | current_obv = last_obv - row[vol_col] 101 | else: 102 | current_obv = last_obv 103 | else: 104 | last_obv = 0 105 | current_obv = row[vol_col] 106 | 107 | data.set_value(index, 'obv', current_obv) 108 | 109 | data['obv_ema' + str(trend_periods)] = data['obv'].ewm(ignore_na=False, min_periods=0, com=trend_periods, adjust=True).mean() 110 | 111 | return data 112 | 113 | """ 114 | Price-volume trend (PVT) (sometimes volume-price trend) 115 | Source: https://en.wikipedia.org/wiki/Volume%E2%80%93price_trend 116 | Params: 117 | data: pandas DataFrame 118 | trend_periods: the over which to calculate PVT 119 | close_col: the name of the CLOSE values column 120 | vol_col: the name of the VOL values column 121 | 122 | Returns: 123 | copy of 'data' DataFrame with 'pvt' and 'pvt_ema[trend_periods]' columns added 124 | """ 125 | def price_volume_trend(data, trend_periods=21, close_col='', vol_col=''): 126 | for index, row in data.iterrows(): 127 | if index > 0: 128 | last_val = data.at[index - 1, 'pvt'] 129 | last_close = data.at[index - 1, close_col] 130 | today_close = row[close_col] 131 | today_vol = row[vol_col] 132 | current_val = last_val + (today_vol * (today_close - last_close) / last_close) 133 | else: 134 | current_val = row[vol_col] 135 | 136 | data.set_value(index, 'pvt', current_val) 137 | 138 | data['pvt_ema' + str(trend_periods)] = data['pvt'].ewm(ignore_na=False, min_periods=0, com=trend_periods, adjust=True).mean() 139 | 140 | return data 141 | 142 | """ 143 | Average true range (ATR) 144 | Source: https://en.wikipedia.org/wiki/Average_true_range 145 | Params: 146 | data: pandas DataFrame 147 | trend_periods: the over which to calculate ATR 148 | open_col: the name of the OPEN values column 149 | high_col: the name of the HIGH values column 150 | low_col: the name of the LOW values column 151 | close_col: the name of the CLOSE values column 152 | vol_col: the name of the VOL values column 153 | drop_tr: whether to drop the True Range values column from the resulting DataFrame 154 | 155 | Returns: 156 | copy of 'data' DataFrame with 'atr' (and 'true_range' if 'drop_tr' == True) column(s) added 157 | """ 158 | def average_true_range(data, trend_periods=14, open_col='', high_col='', low_col='', close_col='', drop_tr = True): 159 | for index, row in data.iterrows(): 160 | prices = [row[high_col], row[low_col], row[close_col], row[open_col]] 161 | if index > 0: 162 | val1 = np.amax(prices) - np.amin(prices) 163 | val2 = abs(np.amax(prices) - data.at[index - 1, close_col]) 164 | val3 = abs(np.amin(prices) - data.at[index - 1, close_col]) 165 | true_range = np.amax([val1, val2, val3]) 166 | 167 | else: 168 | true_range = np.amax(prices) - np.amin(prices) 169 | 170 | data.set_value(index, 'true_range', true_range) 171 | data['atr'] = data['true_range'].ewm(ignore_na=False, min_periods=0, com=trend_periods, adjust=True).mean() 172 | if drop_tr: 173 | data = data.drop(['true_range'], axis=1) 174 | 175 | return data 176 | 177 | """ 178 | Bollinger Bands 179 | Source: https://en.wikipedia.org/wiki/Bollinger_Bands 180 | Params: 181 | data: pandas DataFrame 182 | trend_periods: the over which to calculate BB 183 | close_col: the name of the CLOSE values column 184 | 185 | Returns: 186 | copy of 'data' DataFrame with 'bol_bands_middle', 'bol_bands_upper' and 'bol_bands_lower' columns added 187 | """ 188 | def bollinger_bands(data, trend_periods=20, close_col=''): 189 | 190 | data['bol_bands_middle'] = data[close_col].ewm(ignore_na=False, min_periods=0, com=trend_periods, adjust=True).mean() 191 | for index, row in data.iterrows(): 192 | 193 | s = data[close_col].iloc[index - trend_periods: index] 194 | sums = 0 195 | middle_band = data.at[index, 'bol_bands_middle'] 196 | for e in s: 197 | sums += np.square(e - middle_band) 198 | 199 | std = np.sqrt(sums / trend_periods) 200 | d = 2 201 | upper_band = middle_band + (d * std) 202 | lower_band = middle_band - (d * std) 203 | 204 | data.set_value(index, 'bol_bands_upper', upper_band) 205 | data.set_value(index, 'bol_bands_lower', lower_band) 206 | 207 | return data 208 | 209 | """ 210 | Chaikin Oscillator 211 | Source: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:chaikin_oscillator 212 | Params: 213 | data: pandas DataFrame 214 | periods_short: period for the shorter EMA (3 days recommended) 215 | periods_long: period for the longer EMA (10 days recommended) 216 | high_col: the name of the HIGH values column 217 | low_col: the name of the LOW values column 218 | close_col: the name of the CLOSE values column 219 | vol_col: the name of the VOL values column 220 | 221 | Returns: 222 | copy of 'data' DataFrame with 'ch_osc' column added 223 | """ 224 | def chaikin_oscillator(data, periods_short=3, periods_long=10, high_col='', 225 | low_col='', close_col='', vol_col=''): 226 | ac = pd.Series([]) 227 | val_last = 0 228 | 229 | for index, row in data.iterrows(): 230 | if row[high_col] != row[low_col]: 231 | val = val_last + ((row[close_col] - row[low_col]) - (row[high_col] - row[close_col])) / (row[high_col] - row[low_col]) * row[vol_col] 232 | else: 233 | val = val_last 234 | ac.set_value(index, val) 235 | val_last = val 236 | 237 | ema_long = ac.ewm(ignore_na=False, min_periods=0, com=periods_long, adjust=True).mean() 238 | ema_short = ac.ewm(ignore_na=False, min_periods=0, com=periods_short, adjust=True).mean() 239 | data['ch_osc'] = ema_short - ema_long 240 | 241 | return data 242 | 243 | """ 244 | Typical Price 245 | Source: https://en.wikipedia.org/wiki/Typical_price 246 | Params: 247 | data: pandas DataFrame 248 | high_col: the name of the HIGH values column 249 | low_col: the name of the LOW values column 250 | close_col: the name of the CLOSE values column 251 | 252 | Returns: 253 | copy of 'data' DataFrame with 'typical_price' column added 254 | """ 255 | def typical_price(data, high_col = '', low_col = '', close_col = ''): 256 | 257 | data['typical_price'] = (data[high_col] + data[low_col] + data[close_col]) / 3 258 | 259 | return data 260 | 261 | """ 262 | Ease of Movement 263 | Source: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:ease_of_movement_emv 264 | Params: 265 | data: pandas DataFrame 266 | period: period for calculating EMV 267 | high_col: the name of the HIGH values column 268 | low_col: the name of the LOW values column 269 | vol_col: the name of the VOL values column 270 | 271 | Returns: 272 | copy of 'data' DataFrame with 'emv' and 'emv_ema_[period]' columns added 273 | """ 274 | def ease_of_movement(data, period=14, high_col='', low_col='', vol_col=''): 275 | for index, row in data.iterrows(): 276 | if index > 0: 277 | midpoint_move = (row[high_col] + row[low_col]) / 2 - (data.at[index - 1, high_col] + data.at[index - 1, low_col]) / 2 278 | else: 279 | midpoint_move = 0 280 | 281 | diff = row[high_col] - row[low_col] 282 | 283 | if diff == 0: 284 | #this is to avoid division by zero below 285 | diff = 0.000000001 286 | 287 | vol = row[vol_col] 288 | if vol == 0: 289 | vol = 1 290 | box_ratio = (vol / 100000000) / (diff) 291 | emv = midpoint_move / box_ratio 292 | 293 | data.set_value(index, 'emv', emv) 294 | 295 | data['emv_ema_'+str(period)] = data['emv'].ewm(ignore_na=False, min_periods=0, com=period, adjust=True).mean() 296 | 297 | return data 298 | 299 | """ 300 | Mass Index 301 | Source: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:mass_index 302 | Params: 303 | data: pandas DataFrame 304 | period: period for calculating MI (9 days recommended) 305 | high_col: the name of the HIGH values column 306 | low_col: the name of the LOW values column 307 | 308 | Returns: 309 | copy of 'data' DataFrame with 'mass_index' column added 310 | """ 311 | def mass_index(data, period=25, ema_period=9, high_col='', low_col=''): 312 | high_low = data[high_col] - data[low_col] + 0.000001 #this is to avoid division by zero below 313 | ema = high_low.ewm(ignore_na=False, min_periods=0, com=ema_period, adjust=True).mean() 314 | ema_ema = ema.ewm(ignore_na=False, min_periods=0, com=ema_period, adjust=True).mean() 315 | div = ema / ema_ema 316 | 317 | for index, row in data.iterrows(): 318 | if index >= period: 319 | val = div[index-25:index].sum() 320 | else: 321 | val = 0 322 | data.set_value(index, 'mass_index', val) 323 | 324 | return data 325 | 326 | """ 327 | Average directional movement index 328 | Source: https://en.wikipedia.org/wiki/Average_directional_movement_index 329 | Params: 330 | data: pandas DataFrame 331 | periods: period for calculating ADX (14 days recommended) 332 | high_col: the name of the HIGH values column 333 | low_col: the name of the LOW values column 334 | 335 | Returns: 336 | copy of 'data' DataFrame with 'adx', 'dxi', 'di_plus', 'di_minus' columns added 337 | """ 338 | def directional_movement_index(data, periods=14, high_col='', low_col=''): 339 | remove_tr_col = False 340 | if not 'true_range' in data.columns: 341 | data = average_true_range(data, drop_tr = False) 342 | remove_tr_col = True 343 | 344 | data['m_plus'] = 0. 345 | data['m_minus'] = 0. 346 | 347 | for i,row in data.iterrows(): 348 | if i>0: 349 | data.set_value(i, 'm_plus', row[high_col] - data.at[i-1, high_col]) 350 | data.set_value(i, 'm_minus', row[low_col] - data.at[i-1, low_col]) 351 | 352 | data['dm_plus'] = 0. 353 | data['dm_minus'] = 0. 354 | 355 | for i,row in data.iterrows(): 356 | if row['m_plus'] > row['m_minus'] and row['m_plus'] > 0: 357 | data.set_value(i, 'dm_plus', row['m_plus']) 358 | 359 | if row['m_minus'] > row['m_plus'] and row['m_minus'] > 0: 360 | data.set_value(i, 'dm_minus', row['m_minus']) 361 | 362 | data['di_plus'] = (data['dm_plus'] / data['true_range']).ewm(ignore_na=False, min_periods=0, com=periods, adjust=True).mean() 363 | data['di_minus'] = (data['dm_minus'] / data['true_range']).ewm(ignore_na=False, min_periods=0, com=periods, adjust=True).mean() 364 | 365 | data['dxi'] = np.abs(data['di_plus'] - data['di_minus']) / (data['di_plus'] + data['di_minus']) 366 | data.set_value(0, 'dxi',1.) 367 | data['adx'] = data['dxi'].ewm(ignore_na=False, min_periods=0, com=periods, adjust=True).mean() 368 | data = data.drop(['m_plus', 'm_minus', 'dm_plus', 'dm_minus'], axis=1) 369 | if remove_tr_col: 370 | data = data.drop(['true_range'], axis=1) 371 | 372 | return data 373 | 374 | """ 375 | Money Flow Index (MFI) 376 | Source: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:money_flow_index_mfi 377 | Params: 378 | data: pandas DataFrame 379 | periods: period for calculating MFI (14 days recommended) 380 | vol_col: the name of the VOL values column 381 | 382 | Returns: 383 | copy of 'data' DataFrame with 'money_flow_index' column added 384 | """ 385 | def money_flow_index(data, periods=14, vol_col=''): 386 | remove_tp_col = False 387 | if not 'typical_price' in data.columns: 388 | data = typical_price(data) 389 | remove_tp_col = True 390 | 391 | data['money_flow'] = data['typical_price'] * data[vol_col] 392 | data['money_ratio'] = 0. 393 | data['money_flow_index'] = 0. 394 | data['money_flow_positive'] = 0. 395 | data['money_flow_negative'] = 0. 396 | 397 | for index,row in data.iterrows(): 398 | if index > 0: 399 | if row['typical_price'] < data.at[index-1, 'typical_price']: 400 | data.set_value(index, 'money_flow_positive', row['money_flow']) 401 | else: 402 | data.set_value(index, 'money_flow_negative', row['money_flow']) 403 | 404 | if index >= periods: 405 | period_slice = data['money_flow'][index-periods:index] 406 | positive_sum = data['money_flow_positive'][index-periods:index].sum() 407 | negative_sum = data['money_flow_negative'][index-periods:index].sum() 408 | 409 | if negative_sum == 0.: 410 | #this is to avoid division by zero below 411 | negative_sum = 0.00001 412 | m_r = positive_sum / negative_sum 413 | 414 | mfi = 1-(1 / (1 + m_r)) 415 | 416 | data.set_value(index, 'money_ratio', m_r) 417 | data.set_value(index, 'money_flow_index', mfi) 418 | 419 | data = data.drop(['money_flow', 'money_ratio', 'money_flow_positive', 'money_flow_negative'], axis=1) 420 | 421 | if remove_tp_col: 422 | data = data.drop(['typical_price'], axis=1) 423 | 424 | return data 425 | 426 | """ 427 | Negative Volume Index (NVI) 428 | Source: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:negative_volume_inde 429 | Params: 430 | data: pandas DataFrame 431 | periods: period for calculating NVI (255 days recommended) 432 | close_col: the name of the CLOSE values column 433 | vol_col: the name of the VOL values column 434 | 435 | Returns: 436 | copy of 'data' DataFrame with 'nvi' and 'nvi_ema' columns added 437 | """ 438 | def negative_volume_index(data, periods=255, close_col='', vol_col=''): 439 | data['nvi'] = 0. 440 | 441 | for index,row in data.iterrows(): 442 | if index > 0: 443 | prev_nvi = data.at[index-1, 'nvi'] 444 | prev_close = data.at[index-1, close_col] 445 | if row[vol_col] < data.at[index-1, vol_col]: 446 | nvi = prev_nvi + (row[close_col] - prev_close / prev_close * prev_nvi) 447 | else: 448 | nvi = prev_nvi 449 | else: 450 | nvi = 1000 451 | data.set_value(index, 'nvi', nvi) 452 | data['nvi_ema'] = data['nvi'].ewm(ignore_na=False, min_periods=0, com=periods, adjust=True).mean() 453 | 454 | return data 455 | 456 | """ 457 | Positive Volume Index (PVI) 458 | Source: https://www.equities.com/news/the-secret-to-the-positive-volume-index 459 | Params: 460 | data: pandas DataFrame 461 | periods: period for calculating PVI (255 days recommended) 462 | close_col: the name of the CLOSE values column 463 | vol_col: the name of the VOL values column 464 | 465 | Returns: 466 | copy of 'data' DataFrame with 'pvi' and 'pvi_ema' columns added 467 | """ 468 | def positive_volume_index(data, periods=255, close_col='', vol_col=''): 469 | data['pvi'] = 0. 470 | 471 | for index,row in data.iterrows(): 472 | if index > 0: 473 | prev_pvi = data.at[index-1, 'pvi'] 474 | prev_close = data.at[index-1, close_col] 475 | if row[vol_col] > data.at[index-1, vol_col]: 476 | pvi = prev_pvi + (row[close_col] - prev_close / prev_close * prev_pvi) 477 | else: 478 | pvi = prev_pvi 479 | else: 480 | pvi = 1000 481 | data.set_value(index, 'pvi', pvi) 482 | data['pvi_ema'] = data['pvi'].ewm(ignore_na=False, min_periods=0, com=periods, adjust=True).mean() 483 | 484 | return data 485 | 486 | """ 487 | Momentum 488 | Source: https://en.wikipedia.org/wiki/Momentum_(technical_analysis) 489 | Params: 490 | data: pandas DataFrame 491 | periods: period for calculating momentum 492 | close_col: the name of the CLOSE values column 493 | 494 | Returns: 495 | copy of 'data' DataFrame with 'momentum' column added 496 | """ 497 | def momentum(data, periods=14, close_col=''): 498 | data['momentum'] = 0. 499 | 500 | for index,row in data.iterrows(): 501 | if index >= periods: 502 | prev_close = data.at[index-periods, close_col] 503 | val_perc = (row[close_col] - prev_close)/prev_close 504 | 505 | data.set_value(index, 'momentum', val_perc) 506 | 507 | return data 508 | 509 | """ 510 | Relative Strenght Index 511 | Source: https://en.wikipedia.org/wiki/Relative_strength_index 512 | Params: 513 | data: pandas DataFrame 514 | periods: period for calculating momentum 515 | close_col: the name of the CLOSE values column 516 | 517 | Returns: 518 | copy of 'data' DataFrame with 'rsi' column added 519 | """ 520 | def rsi(data, periods=14, close_col=''): 521 | data['rsi_u'] = 0. 522 | data['rsi_d'] = 0. 523 | data['rsi'] = 0. 524 | 525 | for index,row in data.iterrows(): 526 | if index >= periods: 527 | 528 | prev_close = data.at[index-periods, close_col] 529 | if prev_close < row[close_col]: 530 | data.set_value(index, 'rsi_u', row[close_col] - prev_close) 531 | elif prev_close > row[close_col]: 532 | data.set_value(index, 'rsi_d', prev_close - row[close_col]) 533 | 534 | data['rsi'] = data['rsi_u'].ewm(ignore_na=False, min_periods=0, com=periods, adjust=True).mean() / (data['rsi_u'].ewm(ignore_na=False, min_periods=0, com=periods, adjust=True).mean() + data['rsi_d'].ewm(ignore_na=False, min_periods=0, com=periods, adjust=True).mean()) 535 | 536 | data = data.drop(['rsi_u', 'rsi_d'], axis=1) 537 | 538 | return data 539 | 540 | """ 541 | Chaikin Volatility (CV) 542 | Source: https://www.marketvolume.com/technicalanalysis/chaikinvolatility.asp 543 | Params: 544 | data: pandas DataFrame 545 | ema_periods: period for smoothing Highest High and Lowest Low difference 546 | change_periods: the period for calculating the difference between Highest High and Lowest Low 547 | high_col: the name of the HIGH values column 548 | low_col: the name of the LOW values column 549 | close_col: the name of the CLOSE values column 550 | 551 | Returns: 552 | copy of 'data' DataFrame with 'chaikin_volatility' column added 553 | """ 554 | def chaikin_volatility(data, ema_periods=10, change_periods=10, high_col='', low_col='', close_col=''): 555 | data['ch_vol_hl'] = data[high_col] - data[low_col] 556 | data['ch_vol_ema'] = data['ch_vol_hl'].ewm(ignore_na=False, min_periods=0, com=ema_periods, adjust=True).mean() 557 | data['chaikin_volatility'] = 0. 558 | 559 | for index,row in data.iterrows(): 560 | if index >= change_periods: 561 | 562 | prev_value = data.at[index-change_periods, 'ch_vol_ema'] 563 | if prev_value == 0: 564 | #this is to avoid division by zero below 565 | prev_value = 0.0001 566 | data.set_value(index, 'chaikin_volatility', ((row['ch_vol_ema'] - prev_value)/prev_value)) 567 | 568 | data = data.drop(['ch_vol_hl', 'ch_vol_ema'], axis=1) 569 | 570 | return data 571 | 572 | """ 573 | William's Accumulation/Distribution 574 | Source: https://www.metastock.com/customer/resources/taaz/?p=125 575 | Params: 576 | data: pandas DataFrame 577 | high_col: the name of the HIGH values column 578 | low_col: the name of the LOW values column 579 | close_col: the name of the CLOSE values column 580 | 581 | Returns: 582 | copy of 'data' DataFrame with 'williams_ad' column added 583 | """ 584 | def williams_ad(data, high_col='', low_col='', close_col=''): 585 | data['williams_ad'] = 0. 586 | 587 | for index,row in data.iterrows(): 588 | if index > 0: 589 | prev_value = data.at[index-1, 'williams_ad'] 590 | prev_close = data.at[index-1, close_col] 591 | if row[close_col] > prev_close: 592 | ad = row[close_col] - min(prev_close, row[low_col]) 593 | elif row[close_col] < prev_close: 594 | ad = row[close_col] - max(prev_close, row[high_col]) 595 | else: 596 | ad = 0. 597 | 598 | data.set_value(index, 'williams_ad', (ad+prev_value)) 599 | 600 | return data 601 | 602 | """ 603 | William's % R 604 | Source: https://www.metastock.com/customer/resources/taaz/?p=126 605 | Params: 606 | data: pandas DataFrame 607 | periods: the period over which to calculate the indicator value 608 | high_col: the name of the HIGH values column 609 | low_col: the name of the LOW values column 610 | close_col: the name of the CLOSE values column 611 | 612 | Returns: 613 | copy of 'data' DataFrame with 'williams_r' column added 614 | """ 615 | def williams_r(data, periods=14, high_col='', low_col='', close_col=''): 616 | data['williams_r'] = 0. 617 | 618 | for index,row in data.iterrows(): 619 | if index > periods: 620 | data.set_value(index, 'williams_r', ((max(data[high_col][index-periods:index]) - row[close_col]) / 621 | (max(data[high_col][index-periods:index]) - min(data[low_col][index-periods:index])))) 622 | 623 | return data 624 | 625 | """ 626 | TRIX 627 | Source: https://www.metastock.com/customer/resources/taaz/?p=114 628 | Params: 629 | data: pandas DataFrame 630 | periods: the period over which to calculate the indicator value 631 | signal_periods: the period for signal moving average 632 | close_col: the name of the CLOSE values column 633 | 634 | Returns: 635 | copy of 'data' DataFrame with 'trix' and 'trix_signal' columns added 636 | """ 637 | def trix(data, periods=14, signal_periods=9, close_col=''): 638 | data['trix'] = data[close_col].ewm(ignore_na=False, min_periods=0, com=periods, adjust=True).mean() 639 | data['trix'] = data['trix'].ewm(ignore_na=False, min_periods=0, com=periods, adjust=True).mean() 640 | data['trix'] = data['trix'].ewm(ignore_na=False, min_periods=0, com=periods, adjust=True).mean() 641 | data['trix_signal'] = data['trix'].ewm(ignore_na=False, min_periods=0, com=signal_periods, adjust=True).mean() 642 | 643 | return data 644 | 645 | """ 646 | Ultimate Oscillator 647 | Source: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:ultimate_oscillator 648 | Params: 649 | data: pandas DataFrame 650 | period_1: the period of the first average (7 days recommended) 651 | period_2: the period of the second average (14 days recommended) 652 | period_3: the period of the third average (28 days recommended) 653 | high_col: the name of the HIGH values column 654 | low_col: the name of the LOW values column 655 | close_col: the name of the CLOSE values column 656 | 657 | Returns: 658 | copy of 'data' DataFrame with 'ultimate_oscillator' column added 659 | """ 660 | def ultimate_oscillator(data, period_1=7,period_2=14, period_3=28, high_col='', low_col='', close_col=''): 661 | data['ultimate_oscillator'] = 0. 662 | data['uo_bp'] = 0. 663 | data['uo_tr'] = 0. 664 | data['uo_avg_1'] = 0. 665 | data['uo_avg_2'] = 0. 666 | data['uo_avg_3'] = 0. 667 | 668 | for index,row in data.iterrows(): 669 | if index > 0: 670 | 671 | bp = row[close_col] - min(row[low_col], data.at[index-1, close_col]) 672 | tr = max(row[high_col], data.at[index-1, close_col]) - min(row[low_col], data.at[index-1, close_col]) 673 | 674 | data.set_value(index, 'uo_bp', bp) 675 | data.set_value(index, 'uo_tr', tr) 676 | if index >= period_1: 677 | uo_avg_1 = sum(data['uo_bp'][index-period_1:index]) / sum(data['uo_tr'][index-period_1:index]) 678 | data.set_value(index, 'uo_avg_1', uo_avg_1) 679 | if index >= period_2: 680 | uo_avg_2 = sum(data['uo_bp'][index-period_2:index]) / sum(data['uo_tr'][index-period_2:index]) 681 | data.set_value(index, 'uo_avg_2', uo_avg_2) 682 | if index >= period_3: 683 | uo_avg_3 = sum(data['uo_bp'][index-period_3:index]) / sum(data['uo_tr'][index-period_3:index]) 684 | data.set_value(index, 'uo_avg_3', uo_avg_3) 685 | uo = (4 * uo_avg_1 + 2 * uo_avg_2 + uo_avg_3) / 7 686 | data.set_value(index, 'ultimate_oscillator', uo) 687 | 688 | data = data.drop(['uo_bp', 'uo_tr', 'uo_avg_1', 'uo_avg_2', 'uo_avg_3'], axis=1) 689 | 690 | return data 691 | --------------------------------------------------------------------------------