Skip to content

Commit e769ac3

Browse files
author
Kyle Magnuson
authored
Merge pull request #2 from kylejusticemagnuson/add-indicators
Add new Indicators
2 parents 830c334 + 114be67 commit e769ac3

13 files changed

+858
-0
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ Bollinger Bands
1818
-Bandwidth
1919
-Percent Bandwidth
2020
-Range
21+
-%B
2122
Chaikin Money Flow
2223
Chande Momentum Oscillator
2324
Commodity Channel Index
@@ -62,18 +63,23 @@ Standard Variance
6263
Stochastic
6364
-%K
6465
-%D
66+
StochRSI
6567
Rate of Change
6668
Relative Strength Index
6769
Triangular Moving Average
6870
Triple Exponential Moving Average
6971
True Range
7072
Typical Price
73+
Ultimate Oscillator
7174
Vertical Horizontal Filter
7275
Volatility
76+
Volume Adjusted Moving Average
7377
Volume Index
7478
-Positive Volume Index
7579
-Negative Volume Index
80+
Volume Oscillator
7681
Weighted Moving Average
82+
Williams %R
7783
```
7884
pyti is currently only compatible with Python 2.7
7985

pyti/bollinger_bands.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,16 @@ def percent_bandwidth(data, period, std=2.0):
113113
)
114114

115115
return percent_bandwidth
116+
117+
118+
def percent_b(data, period, upper_bb_std=2.0, lower_bb_std=2.0):
119+
"""
120+
%B.
121+
122+
Formula:
123+
%B = ((data - lb) / (ub - lb)) * 100
124+
"""
125+
lb = lower_bollinger_band(data, period, lower_bb_std)
126+
ub = upper_bollinger_band(data, period, upper_bb_std)
127+
percent_b = ((np.array(data) - lb) / (ub - lb)) * 100
128+
return percent_b

pyti/stochrsi.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import numpy as np
2+
from pyti.function_helper import fill_for_noncomputable_vals
3+
from pyti.relative_strength_index import relative_strength_index
4+
5+
6+
def stochrsi(data, period):
7+
"""
8+
StochRSI.
9+
10+
Formula:
11+
SRSI = ((RSIt - RSI LOW) / (RSI HIGH - LOW RSI)) * 100
12+
"""
13+
rsi = relative_strength_index(data, period)[period:]
14+
stochrsi = map(lambda idx: 100 * ((rsi[idx] - np.min(rsi[idx+1-period:idx+1])) / (np.max(rsi[idx+1-period:idx+1]) - np.min(rsi[idx+1-period:idx+1]))), range(period-1, len(rsi)))
15+
stochrsi = fill_for_noncomputable_vals(data, stochrsi)
16+
return stochrsi

pyti/ultimate_oscillator.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import numpy as np
2+
from pyti import catch_errors
3+
from pyti.function_helper import fill_for_noncomputable_vals
4+
from pyti.true_range import true_range
5+
6+
7+
def buying_pressure(close_data, low_data):
8+
"""
9+
Buying Pressure.
10+
11+
Formula:
12+
BP = current close - min()
13+
"""
14+
catch_errors.check_for_input_len_diff(close_data, low_data)
15+
bp = map(
16+
lambda idx:
17+
close_data[idx] - np.min([low_data[idx], close_data[idx-1]]),
18+
range(1, len(close_data))
19+
)
20+
bp = fill_for_noncomputable_vals(close_data, bp)
21+
return bp
22+
23+
24+
def avg_helper(close_data, low_data, period):
25+
catch_errors.check_for_input_len_diff(close_data, low_data)
26+
catch_errors.check_for_period_error(close_data, period)
27+
bp = buying_pressure(close_data, low_data)
28+
tr = true_range(close_data, period)
29+
avg = map(
30+
lambda idx:
31+
sum(bp[idx+1-period:idx+1]) / sum(tr[idx+1-period:idx+1]),
32+
range(period-1, len(close_data))
33+
)
34+
avg = fill_for_noncomputable_vals(close_data, avg)
35+
return avg
36+
37+
38+
def average_7(close_data, low_data, period=7):
39+
"""
40+
Average7.
41+
42+
Formula:
43+
AVG7 = SUM(BP) / SUM(TR) for 7 days
44+
"""
45+
return avg_helper(close_data, low_data, period)
46+
47+
48+
def average_14(close_data, low_data, period=14):
49+
"""
50+
Averag14.
51+
52+
Formula:
53+
AVG14 = SUM(BP) / SUM(TR) for 14 days
54+
"""
55+
return avg_helper(close_data, low_data, period)
56+
57+
58+
def average_28(close_data, low_data, period=28):
59+
"""
60+
average_28.
61+
62+
Formula:
63+
AVG14 = SUM(BP) / SUM(TR) for 28 days
64+
"""
65+
return avg_helper(close_data, low_data, period)
66+
67+
68+
def ultimate_oscillator(close_data, low_data):
69+
"""
70+
Ultimate Oscillator.
71+
72+
Formula:
73+
UO = 100 * ((4 * AVG7) + (2 * AVG14) + AVG28) / (4 + 2 + 1)
74+
"""
75+
a7 = 4 * average_7(close_data, low_data)
76+
a14 = 2 * average_14(close_data, low_data)
77+
a28 = average_28(close_data, low_data)
78+
uo = 100 * ((a7 + a14 + a28) / 7)
79+
return uo
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import numpy as np
2+
from pyti import catch_errors
3+
from pyti.function_helper import fill_for_noncomputable_vals
4+
5+
6+
def volume_adjusted_moving_average(close_data, volume, period):
7+
"""
8+
Volume Adjusted Moving Average.
9+
10+
Formula:
11+
VAMA = SUM(CLOSE * VolumeRatio) / period
12+
"""
13+
catch_errors.check_for_input_len_diff(close_data, volume)
14+
catch_errors.check_for_period_error(close_data, period)
15+
16+
avg_vol = np.mean(volume)
17+
vol_incr = avg_vol * 0.67
18+
vol_ratio = map(lambda val: val / vol_incr, volume)
19+
close_vol = np.array(close_data) * vol_ratio
20+
vama = map(
21+
lambda idx:
22+
sum(close_vol[idx+1-period:idx+1]) / period,
23+
range(period-1, len(close_data))
24+
)
25+
vama = fill_for_noncomputable_vals(close_data, vama)
26+
return vama

pyti/volume_oscillator.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from pyti import catch_errors
2+
from pyti.simple_moving_average import simple_moving_average as sma
3+
4+
5+
def volume_oscillator(volume, short_period, long_period):
6+
"""
7+
Volume Oscillator.
8+
9+
Formula:
10+
vo = 100 * (SMA(vol, short) - SMA(vol, long) / SMA(vol, long))
11+
"""
12+
catch_errors.check_for_period_error(volume, short_period)
13+
catch_errors.check_for_period_error(volume, long_period)
14+
15+
vo = (100 * ((sma(volume, short_period) - sma(volume, long_period)) /
16+
sma(volume, long_period)))
17+
return vo

pyti/williams_percent_r.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import numpy as np
2+
3+
4+
def williams_percent_r(close_data):
5+
"""
6+
Williams %R.
7+
8+
Formula:
9+
wr = (HighestHigh - close / HighestHigh - LowestLow) * -100
10+
"""
11+
highest_high = np.max(close_data)
12+
lowest_low = np.min(close_data)
13+
wr = map(
14+
lambda close:
15+
((highest_high - close) / (highest_high - lowest_low)) * -100,
16+
close_data
17+
)
18+
return wr

tests/test_bollinger_bands.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,49 @@ def setUp(self):
266266
0.25008520904313003, 0.015916940699370632, 0.042754393107139112,
267267
0.2073341509427761, 0.22254729716543903, 0.41858526202755941]
268268

269+
self.percent_b_period_6_expected = [np.nan, np.nan, np.nan, np.nan,
270+
np.nan, 71.416541403692662, 83.866195707846202, 89.900444691314974,
271+
63.426415365090115, 35.767677651338673, 83.799788142320565,
272+
87.889224421599337, 59.106910290885928, 11.96458507468704,
273+
28.479369714165294, 0.48133863682257844, 12.285215487954284,
274+
6.3393602452485505, 21.267111658762513, 13.49736048726756,
275+
20.633721149749988, 52.14494904093273, 92.609473280722213,
276+
74.639976904914889, 59.094630772307845, 49.66553801448412,
277+
63.325797147897603, 55.426874977952444, 10.017435200369071,
278+
79.003157061501014, 37.672193277556708, 15.974639983239609,
279+
-1.8601440885751042, 45.20795907730156, 63.972845478064585,
280+
95.207225499258882, 85.985227580047791, 67.435119671099159,
281+
31.630463162422441, 27.571484422038111, 35.722372871057829,
282+
71.618605297219872, 82.955372856384173, 87.151285854545762,
283+
74.171319134171384, 75.559113110015375, 87.343525582840115,
284+
91.1782305681959, 47.955111759473482, 36.062995266478268,
285+
52.91782297838521, 24.865780679396728, 3.6577182625657372,
286+
12.333006154254761, 22.670145389320972, 47.500168554380693,
287+
49.343559927296866, 88.429722905161483, 14.063096880802616,
288+
28.815219042748808, 23.945630941870743, 37.480155171666482,
289+
32.483867906988174, 87.427776919156557, 55.68901103712831,
290+
101.87846525362536, 86.068033824415252, 34.418840302649009,
291+
89.7315161347979, 80.552022188749376, 24.740327162381352,
292+
15.609914152458346, 16.096651128534848, 28.621070615399287,
293+
45.025280784200753, 7.1934543203424601, 12.890555832140956,
294+
68.838787770707441, 22.066807075637879, 82.182016640801606,
295+
89.183636347352618, 77.408650391055701, 36.670131889717432,
296+
24.230650132629815, 17.0584502470041, 31.102653583047623,
297+
50.715383366677443, 50.405819514057526, 37.336878890123955,
298+
62.974998584373218, 87.183212709286494, 79.165024866150304,
299+
89.803755087524308, 89.691355187229732, 89.388803667484723,
300+
56.1341178471503, 81.511763342799497, 81.605555245978223,
301+
82.110023720757951, 75.02531441281657, 60.992424485043436,
302+
10.395174764351147, 42.909683423846282, -3.7954564196469489,
303+
20.54151740274996, 34.300910469854976, 45.435543109144909,
304+
7.397095218758114, -4.06520517146219, 12.416069594495738,
305+
19.856034359944733, 28.095083994135567, 36.679927413666206,
306+
15.436375290700454, 40.123343485150521, 10.908399076977785,
307+
21.271305460156579, -3.6877472675652037, 15.23460112813585,
308+
15.974639907730282, 30.562720118308356, 25.008520904313002,
309+
1.5916940699370632, 4.275439310713911, 20.733415094277611,
310+
22.254729716543903, 41.858526202755939]
311+
269312
def test_upper_bollinger_bands_period_6(self):
270313
period = 6
271314
upper_bb = bollinger_bands.upper_bollinger_band(self.data, period)
@@ -337,3 +380,15 @@ def test_percent_bandwidth_invalid_period(self):
337380
bollinger_bands.percent_bandwidth(self.data, period)
338381
expected = "Error: data_len < period"
339382
self.assertEqual(str(cm.exception), expected)
383+
384+
def test_percent_b_period_6(self):
385+
period = 6
386+
percent_b = bollinger_bands.percent_b(self.data, period)
387+
np.testing.assert_array_equal(percent_b, self.percent_b_period_6_expected)
388+
389+
def test_percent_b_invalid_period(self):
390+
period = 128
391+
with self.assertRaises(Exception) as cm:
392+
bollinger_bands.percent_b(self.data, period)
393+
expected = "Error: data_len < period"
394+
self.assertEqual(str(cm.exception), expected)

tests/test_stochrsi.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import unittest
2+
import numpy as np
3+
4+
from tests.sample_data import SampleData
5+
from pyti import stochrsi
6+
7+
8+
class TestStochRSI(unittest.TestCase):
9+
def setUp(self):
10+
"""Create data to use for testing."""
11+
self.data = SampleData().get_sample_close_data()
12+
13+
self.stochrsi_period_6_expected = [np.nan, np.nan, np.nan, np.nan,
14+
np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan,
15+
44.471222157460431, 0.0, 0.0, 11.492848409063964, 0.0, 0.0, 0.0,
16+
12.996028967803408, 0.0, 0.0, 100.0, 100.0, 88.834308685247748,
17+
72.485626510038344, 68.948591144212699, 81.54139590783565,
18+
72.069361538856384, 0.0, 100.0, 45.968610199984546, 9.057017058300568,
19+
0.0, 72.780883175761147, 97.098272486847122, 100.0, 100.0,
20+
78.357655504172754, 0.0, 3.3759971697837599, 23.819966852076472,
21+
68.973352093845421, 100.0, 100.0, 88.361969800298795, 100.0, 100.0,
22+
100.0, 0.0, 0.0, 25.101888829188145, 0.0, 0.0, 0.0, 0.0,
23+
47.597870253604583, 52.570804308995712, 100.0, 0.14199939183552529,
24+
24.059960445031265, 10.896141995479166, 30.513406175435293,
25+
8.9065608580022868, 100.0, 68.068787936734168, 100.0, 100.0,
26+
22.231267706492098, 100.0, 100.0, 10.017481472275215, 0.0, 0.0,
27+
18.16126672568527, 40.521482277525919, 0.0, 0.0, 100.0,
28+
35.793893628113686, 100.0, 100.0, 100.0, 33.57757547925862,
29+
8.6247722964853466, 0.0, 17.302011810094726, 49.159702249924187,
30+
60.932783640150376, 31.942145934203868, 82.095023751511064, 100.0,
31+
100.0, 100.0, 100.0, 100.0, 15.042425515475383, 66.462588569115553,
32+
74.790326011378099, 88.557628253482306, 92.354570404206441,
33+
61.458440388782407, 0.0, 36.577660953654245, 0.0, 15.598256159532903,
34+
31.358012359220449, 48.116305853565294, 0.0, 0.0, 0.0, 0.0, 0.0,
35+
33.85258109852839, 57.650642272894835, 100.0, 75.843338626788722,
36+
74.436615187163184, 0.0, 4.4097493664417158, 0.0, 94.669594611808265,
37+
65.534738257501118, 27.780745298221497, 0.0, 47.709624772979161,
38+
32.163553172474437, 100.0]
39+
40+
self.stochrsi_period_8_expected = [np.nan, np.nan, np.nan, np.nan,
41+
np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan,
42+
np.nan, np.nan, 0.0, 0.0, 0.0, 5.9170858267051587, 0.0, 0.0,
43+
44.731067481598593, 100.0, 89.261888846566762, 73.506626263784852,
44+
70.124518845179722, 92.473144438199071, 91.742030577620142,
45+
15.037735074756913, 100.0, 47.306749309021193, 10.140545886437421, 0.0,
46+
72.278552326472152, 97.679213884699251, 100.0, 100.0,
47+
80.347390204472063, 47.828534810102333, 49.538188855519977,
48+
27.611275977441203, 67.963793266686267, 78.134869525725151,
49+
99.252804098374185, 90.150479498192112, 100.0, 100.0, 100.0,
50+
4.9013099702443785, 0.0, 24.68617122074053, 0.0, 0.0, 0.0, 0.0,
51+
39.802519009096748, 29.010598833949796, 53.053222984371452, 0.0,
52+
21.631906841014569, 8.3458733315535447, 25.22414006544841,
53+
4.5780668059713552, 49.007350471187422, 34.273386605258764, 100.0,
54+
100.0, 22.403954556738867, 100.0, 100.0, 12.105988070501787, 0.0, 0.0,
55+
16.657204131414304, 37.35692115954221, 0.0, 0.0, 100.0,
56+
32.997720377696496, 100.0, 100.0, 100.0, 51.334083599997683,
57+
31.853266142141972, 8.1527836280558503, 22.745953245334995,
58+
45.508879820782681, 27.946619216922286, 14.815380622177496,
59+
80.088315251347964, 100.0, 100.0, 100.0, 100.0, 100.0,
60+
56.481950024728867, 86.652579488979171, 93.049895919142116, 100.0,
61+
100.0, 69.687224483627773, 4.7487429986927374, 40.824255220794718, 0.0,
62+
14.82258516102512, 25.574101538565898, 33.214453816875086, 0.0, 0.0,
63+
0.0, 0.0, 0.0, 12.028791696947424, 5.742958082055595,
64+
46.151131514517758, 73.698400050314845, 72.171996877638023, 0.0,
65+
3.9883199142603543, 0.0, 63.887076434178304, 43.192059380396671,
66+
18.535460030504883, 0.0, 44.316525695640522, 29.344038948303997, 100.0]
67+
68+
self.stochrsi_period_10_expected = [np.nan, np.nan, np.nan, np.nan,
69+
np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, np.nan,
70+
np.nan, np.nan, np.nan, np.nan, np.nan, np.nan, 0.0, 0.0,
71+
27.66288026615667, 68.42179408282631, 60.95511012399831,
72+
73.056366879654533, 69.632283416324114, 91.673870575592005,
73+
90.973445004595106, 62.013426837224038, 99.412333298280316,
74+
56.029604272708319, 9.5954111342724602, 0.0, 71.203658595987136,
75+
97.102435963871216, 100.0, 100.0, 81.393245300884587,
76+
49.654269940792076, 51.293355239394344, 60.927269295952172,
77+
83.862439770570063, 81.039109157752904, 100.0, 90.97577696869304, 100.0,
78+
100.0, 100.0, 58.073925890407921, 41.358292066458077,
79+
25.654266421595828, 0.0, 0.0, 0.0, 0.0, 23.713848713037535,
80+
16.806950663591991, 47.466120473538204, 0.0, 10.796474428727892,
81+
4.690894374748388, 21.71719912790817, 1.5761771583204254,
82+
41.050006983973205, 27.2736467372333, 100.0, 100.0, 21.771640646517728,
83+
100.0, 100.0, 26.039977920480617, 0.0, 0.0, 15.737216917199534,
84+
35.412360099771497, 0.0, 0.0, 50.704904555903788, 14.994477423420522,
85+
100.0, 100.0, 100.0, 51.87903440907273, 31.867669596262672,
86+
26.242262653493135, 37.395093360643585, 49.651518706252737,
87+
34.777475814569073, 14.222945117349619, 34.122394180512103,
88+
59.589894426757162, 100.0, 100.0, 100.0, 100.0, 60.387458746010005,
89+
93.702909932596725, 100.0, 100.0, 100.0, 81.695205658391245,
90+
18.390800382020387, 51.052985817884824, 0.0, 14.606027212675402,
91+
25.21593064024956, 32.720972201555149, 0.0, 0.0, 0.0, 0.0, 0.0,
92+
10.169626915478698, 3.627127999972207, 19.385791765467346,
93+
13.371820642369503, 22.43112726583951, 0.0, 3.4785405767313109, 0.0,
94+
54.371077241311575, 35.289162480238765, 7.8549519711197089, 0.0,
95+
28.494276007229587, 18.19959392470588, 100.0]
96+
97+
def test_stochrsi_period_6(self):
98+
period = 6
99+
sr = stochrsi.stochrsi(self.data, period)
100+
np.testing.assert_array_equal(sr, self.stochrsi_period_6_expected)
101+
102+
def test_stochrsi_period_8(self):
103+
period = 8
104+
sr = stochrsi.stochrsi(self.data, period)
105+
np.testing.assert_array_equal(sr, self.stochrsi_period_8_expected)
106+
107+
def test_stochrsi_period_10(self):
108+
period = 10
109+
sr = stochrsi.stochrsi(self.data, period)
110+
np.testing.assert_array_equal(sr, self.stochrsi_period_10_expected)
111+
112+
def test_stochrsi_invalid_period(self):
113+
period = 128
114+
with self.assertRaises(Exception) as cm:
115+
stochrsi.stochrsi(self.data, period)
116+
expected = "Error: data_len < period"
117+
self.assertEqual(str(cm.exception), expected)

0 commit comments

Comments
 (0)