signal_tools 0.2.2 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.rdoc +22 -3
- data/lib/signal_tools.rb +7 -0
- data/lib/signal_tools/stock.rb +25 -286
- data/lib/signal_tools/technicals/average_directional_index.rb +106 -0
- data/lib/signal_tools/technicals/average_true_range.rb +38 -0
- data/lib/signal_tools/technicals/common.rb +44 -0
- data/lib/signal_tools/technicals/ema.rb +44 -0
- data/lib/signal_tools/technicals/fast_stochastic.rb +19 -0
- data/lib/signal_tools/technicals/macd.rb +41 -0
- data/lib/signal_tools/technicals/slow_stochastic.rb +31 -0
- data/lib/signal_tools/technicals/stochastic.rb +36 -0
- data/lib/signal_tools/technicals/true_range.rb +25 -0
- data/test/lib/signal_tools/technicals/test_average_directional_index.rb +19 -0
- data/test/lib/signal_tools/technicals/test_average_true_range.rb +21 -0
- data/test/lib/signal_tools/technicals/test_ema.rb +21 -0
- data/test/lib/signal_tools/technicals/test_fast_stochastic.rb +26 -0
- data/test/lib/signal_tools/technicals/test_macd.rb +26 -0
- data/test/lib/signal_tools/technicals/test_slow_stochastic.rb +26 -0
- data/test/test_helper.rb +15 -19
- data/test/test_signal_tools.rb +2 -3
- data/test/test_stock.rb +7 -61
- data/test/test_stock_data.rb +6 -5
- metadata +63 -20
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 95c229f7b3633f9907032443788386156bbe95c9
|
4
|
+
data.tar.gz: 7f8ee54376035b7326a881afc701bf625ea9c678
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9030bd61f15ca7349cfc230698127226703c7dd729685ff4cd3bf32504a779094708bea8a0e40b3c442466953c1dbf357385fddc238e1266fc59995b42310d61
|
7
|
+
data.tar.gz: 33f9665391f185da998bf59cf0bb2fae1547ca91dc05de95d0b7063d955af8675d180254714399c30d3d9e48cf64d04d6f0c1ecfb2385e1c7f6f05fa1f9d55c9
|
data/README.rdoc
CHANGED
@@ -1,11 +1,10 @@
|
|
1
1
|
== Signal Tools
|
2
2
|
|
3
|
-
Signal tools allows you to
|
3
|
+
Signal tools allows you to generate technical analysis data for a given stock (like MACD, stochastic, and exponential moving averages).
|
4
4
|
|
5
5
|
== Installation
|
6
6
|
|
7
7
|
gem install signal_tools
|
8
|
-
bundle install --without development test
|
9
8
|
|
10
9
|
== Usage
|
11
10
|
|
@@ -33,4 +32,24 @@ Signal tools allows you to create technical analysis data for a given stock (lik
|
|
33
32
|
|
34
33
|
== Copyright
|
35
34
|
|
36
|
-
Copyright (c)
|
35
|
+
Copyright (c) 2015 Matt White.
|
36
|
+
|
37
|
+
MIT License
|
38
|
+
|
39
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
40
|
+
of this software and associated documentation files (the "Software"), to deal
|
41
|
+
in the Software without restriction, including without limitation the rights
|
42
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
43
|
+
copies of the Software, and to permit persons to whom the Software is
|
44
|
+
furnished to do so, subject to the following conditions:
|
45
|
+
|
46
|
+
The above copyright notice and this permission notice shall be included in all
|
47
|
+
copies or substantial portions of the Software.
|
48
|
+
|
49
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
50
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
51
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
52
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
53
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
54
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
55
|
+
SOFTWARE.
|
data/lib/signal_tools.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
require "signal_tools/stock_data"
|
2
2
|
require "signal_tools/stock"
|
3
|
+
require "signal_tools/technicals/average_directional_index"
|
4
|
+
require "signal_tools/technicals/average_true_range"
|
5
|
+
require "signal_tools/technicals/common"
|
6
|
+
require "signal_tools/technicals/ema"
|
7
|
+
require "signal_tools/technicals/fast_stochastic"
|
8
|
+
require "signal_tools/technicals/slow_stochastic"
|
9
|
+
require "signal_tools/technicals/macd"
|
3
10
|
|
4
11
|
module SignalTools
|
5
12
|
def self.sum(array)
|
data/lib/signal_tools/stock.rb
CHANGED
@@ -1,311 +1,50 @@
|
|
1
|
+
require './lib/signal_tools/technicals/common'
|
2
|
+
|
1
3
|
module SignalTools
|
2
4
|
class Stock
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
ATR_Seed_Days = 14
|
5
|
+
include ::SignalTools::Technicals::Common
|
6
|
+
|
7
|
+
DEFAULT_PERIOD = 90
|
7
8
|
|
8
9
|
attr_accessor :ticker
|
9
|
-
attr_reader :stock_data
|
10
|
+
attr_reader :dates, :stock_data
|
10
11
|
|
11
|
-
def initialize(ticker, from_date=Date.today-
|
12
|
+
def initialize(ticker, from_date=Date.today-DEFAULT_PERIOD, to_date=Date.today)
|
12
13
|
from_date = Date.parse(from_date) unless from_date.is_a?(Date)
|
13
14
|
to_date = Date.parse(to_date) unless to_date.is_a?(Date)
|
14
15
|
@ticker = ticker
|
15
16
|
@stock_data = SignalTools::StockData.new(ticker, from_date, to_date)
|
17
|
+
@dates = stock_data.dates
|
16
18
|
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
trim_data_to_range
|
20
|
+
def ema(period=10, type=:default)
|
21
|
+
ema_data = SignalTools::Technicals::EMA.new(stock_data.close_prices, period, type).calculate
|
22
|
+
trim_data_to_range(ema_data, dates.size)
|
21
23
|
end
|
22
24
|
|
23
25
|
def macd(fast=8, slow=17, signal=9)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
def fast_stochastic(k=14, d=5)
|
28
|
-
trim_data_to_range!(fast_stochastic_points(k, d))
|
29
|
-
end
|
30
|
-
|
31
|
-
def slow_stochastic(k=14, d=5)
|
32
|
-
trim_data_to_range!(slow_stochastic_points(k, d))
|
33
|
-
end
|
34
|
-
|
35
|
-
def atr(period=14)
|
36
|
-
trim_data_to_range!(average_true_ranges(period))
|
37
|
-
end
|
38
|
-
|
39
|
-
def adx(period=14)
|
40
|
-
trim_data_to_range!(average_directional_indexes(period))
|
41
|
-
end
|
42
|
-
|
43
|
-
def dates
|
44
|
-
@stock_data.dates
|
45
|
-
end
|
46
|
-
|
47
|
-
def close_prices
|
48
|
-
@close_prices = trim_data_to_range(@stock_data.close_prices)
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
#### EMA methods
|
54
|
-
|
55
|
-
def ema_points(period, data, type=:default)
|
56
|
-
emas = [default_simple_average(data, EMA_Seed_Days)]
|
57
|
-
if type == :wilder
|
58
|
-
data.slice(EMA_Seed_Days..-1).each { |current| emas << calculate_wilder_ema(emas.last, current, period) }
|
59
|
-
else
|
60
|
-
data.slice(EMA_Seed_Days..-1).each { |current| emas << calculate_ema(emas.last, current, period) }
|
61
|
-
end
|
62
|
-
emas
|
63
|
-
end
|
64
|
-
|
65
|
-
#Takes current value, previous day's EMA, and number of days. Returns EMA for that day.
|
66
|
-
def calculate_ema(previous, current, period)
|
67
|
-
(current - previous) * (2.0 / (period + 1)) + previous
|
68
|
-
end
|
69
|
-
|
70
|
-
#Uses Wilder's moving average formula.
|
71
|
-
def calculate_wilder_ema(previous, current, period)
|
72
|
-
(previous * (period - 1) + current) / period
|
73
|
-
end
|
74
|
-
|
75
|
-
#Takes a period and array of data and calculates the sum ema over the period specified.
|
76
|
-
def period_sum_ema(period, data)
|
77
|
-
raise if data.size <= period
|
78
|
-
sum_emas = [SignalTools.sum(data[0...period])]
|
79
|
-
data[(period..-1)].each do |today|
|
80
|
-
sum_emas << (sum_emas.last - (sum_emas.last / period) + today)
|
81
|
-
end
|
82
|
-
sum_emas
|
83
|
-
end
|
84
|
-
|
85
|
-
#### MACD Methods
|
86
|
-
|
87
|
-
# Takes a period of days for fast, slow, signal, and time period (eg 8,17,9).
|
88
|
-
def macd_points(fast, slow, signal)
|
89
|
-
fast_ema_points = ema_points(fast, @stock_data.close_prices)
|
90
|
-
slow_ema_points = ema_points(slow, @stock_data.close_prices)
|
91
|
-
macd_and_divergence_points(fast_ema_points, slow_ema_points, signal)
|
92
|
-
end
|
93
|
-
|
94
|
-
def macd_and_divergence_points(fast_ema_points, slow_ema_points, signal)
|
95
|
-
macds = differences_between_arrays(fast_ema_points, slow_ema_points)
|
96
|
-
signal_points = ema_points(signal, macds)
|
97
|
-
divergences = differences_between_arrays(macds, signal_points)
|
98
|
-
{:signal_points => signal_points, :divergences => divergences}
|
99
|
-
end
|
100
|
-
|
101
|
-
# Returns an array with the differences between the first_points and second_points
|
102
|
-
def differences_between_arrays(first_points, second_points)
|
103
|
-
SignalTools.truncate_to_shortest!(first_points, second_points)
|
104
|
-
differences = []
|
105
|
-
first_points.each_with_index { |fp, index| differences << fp - second_points[index] }
|
106
|
-
differences
|
107
|
-
end
|
108
|
-
|
109
|
-
#### Stochastic Methods
|
110
|
-
|
111
|
-
def fast_stochastic_points(k_period, d_period)
|
112
|
-
k_points = calculate_fast_stochastic_k_points(k_period)
|
113
|
-
d_points = calculate_d_points(k_points, d_period)
|
114
|
-
k_d_points(k_points, d_points)
|
115
|
-
end
|
116
|
-
|
117
|
-
def slow_stochastic_points(k_period, d_period)
|
118
|
-
fast_points = fast_stochastic_points(k_period, d_period)
|
119
|
-
k_points = slow_k_points(fast_points[:k])
|
120
|
-
slow_d_points = calculate_d_points(k_points, d_period)
|
121
|
-
k_d_points(k_points, slow_d_points)
|
122
|
-
end
|
123
|
-
|
124
|
-
def calculate_fast_stochastic_k_points(period)
|
125
|
-
index = 0
|
126
|
-
points = []
|
127
|
-
while((index + period) <= @stock_data.close_prices.size)
|
128
|
-
today_cp = @stock_data.close_prices[index + period - 1]
|
129
|
-
low_price = get_for_period(@stock_data.low_prices, index, index + period - 1, :min)
|
130
|
-
high_price = get_for_period(@stock_data.high_prices, index, index + period - 1, :max)
|
131
|
-
points << (today_cp - low_price) / (high_price - low_price)
|
132
|
-
index += 1
|
133
|
-
end
|
134
|
-
points
|
135
|
-
end
|
136
|
-
|
137
|
-
def calculate_d_points(k_points, period)
|
138
|
-
collection_for_array(k_points, period, :average)
|
139
|
-
end
|
140
|
-
|
141
|
-
def k_d_points(k_points, d_points)
|
142
|
-
raise unless k_points.size > d_points.size
|
143
|
-
SignalTools.truncate_to_shortest!(k_points, d_points)
|
144
|
-
{:k => k_points, :d => d_points}
|
145
|
-
end
|
146
|
-
|
147
|
-
def slow_k_points(fast_k_points)
|
148
|
-
collection_for_array(fast_k_points, Slow_K_SMA, :average)
|
149
|
-
end
|
150
|
-
|
151
|
-
#### True Range Methods
|
152
|
-
|
153
|
-
# Takes a smoothing period and historical data and calculates the average
|
154
|
-
# true ranges.
|
155
|
-
def average_true_ranges(period)
|
156
|
-
trs = true_ranges
|
157
|
-
atrs = [default_simple_average(trs.slice!(0...ATR_Seed_Days), ATR_Seed_Days)]
|
158
|
-
trs.each { |tr| atrs << calculate_average_true_range(atrs.last, tr, period) }
|
159
|
-
atrs
|
160
|
-
end
|
161
|
-
|
162
|
-
# Takes historical data and computes the true ranges.
|
163
|
-
def true_ranges
|
164
|
-
trs = [@stock_data.high_prices.first - @stock_data.low_prices.first]
|
165
|
-
index = 1
|
166
|
-
while index < (@stock_data.high_prices.size)
|
167
|
-
trs << true_range(@stock_data.raw_data[index], @stock_data.raw_data[index-1])
|
168
|
-
index += 1
|
169
|
-
end
|
170
|
-
trs
|
171
|
-
end
|
172
|
-
|
173
|
-
# Takes today's data and yesterday's data and computes the true range.
|
174
|
-
def true_range(today, yesterday)
|
175
|
-
[
|
176
|
-
today[SignalTools::StockData::Indexes[:high]] - today[SignalTools::StockData::Indexes[:low]],
|
177
|
-
(yesterday[SignalTools::StockData::Indexes[:close]] - today[SignalTools::StockData::Indexes[:high]]).abs,
|
178
|
-
(yesterday[SignalTools::StockData::Indexes[:close]] - today[SignalTools::StockData::Indexes[:low]]).abs
|
179
|
-
].max
|
180
|
-
end
|
181
|
-
|
182
|
-
# Takes yesterday's average true range, today's true range, and the smoothing
|
183
|
-
# period and calculates the day's average true range.
|
184
|
-
def calculate_average_true_range(yesterday_atr, today_tr, period)
|
185
|
-
(yesterday_atr * (period - 1) + today_tr) / period
|
186
|
-
end
|
187
|
-
|
188
|
-
#### Average Directional Index Methods
|
189
|
-
|
190
|
-
def average_directional_indexes(period)
|
191
|
-
dxs = directional_indexes(plus_directional_index(period), minus_directional_index(period))
|
192
|
-
adxs = ema_points(period, dxs, :wilder)
|
193
|
-
adxs
|
194
|
-
end
|
195
|
-
|
196
|
-
def directional_indexes(plus_dis, minus_dis)
|
197
|
-
SignalTools.truncate_to_shortest!(plus_dis, minus_dis)
|
198
|
-
differences, sums = [], []
|
199
|
-
index = 0
|
200
|
-
while index < plus_dis.size
|
201
|
-
differences << (plus_dis[index] - minus_dis[index]).abs
|
202
|
-
sums << (plus_dis[index] + minus_dis[index])
|
203
|
-
index += 1
|
204
|
-
end
|
205
|
-
quotients(differences, sums)
|
206
|
-
end
|
207
|
-
|
208
|
-
def plus_directional_index(period)
|
209
|
-
plus_dms = plus_directional_movement(@stock_data.raw_data)
|
210
|
-
plus_dm_sums = period_sum_ema(period, plus_dms)
|
211
|
-
true_range_sums = period_sum_ema(period, true_ranges)
|
212
|
-
quotients(plus_dm_sums, true_range_sums)
|
213
|
-
end
|
214
|
-
|
215
|
-
def minus_directional_index(period)
|
216
|
-
minus_dms = minus_directional_movement(@stock_data.raw_data)
|
217
|
-
minus_dm_sums = period_sum_ema(period, minus_dms)
|
218
|
-
true_range_sums = period_sum_ema(period, true_ranges)
|
219
|
-
quotients(minus_dm_sums, true_range_sums)
|
220
|
-
end
|
221
|
-
|
222
|
-
def quotients(first, second)
|
223
|
-
SignalTools.truncate_to_shortest!(first, second)
|
224
|
-
index = 0
|
225
|
-
quots = []
|
226
|
-
while index < first.size
|
227
|
-
quots << first[index] / second[index]
|
228
|
-
index += 1
|
229
|
-
end
|
230
|
-
quots
|
231
|
-
end
|
232
|
-
|
233
|
-
def plus_directional_movement(data)
|
234
|
-
plus_dm = []
|
235
|
-
data.each_cons(2) do |two_days|
|
236
|
-
um = up_move(two_days.last, two_days.first)
|
237
|
-
dm = down_move(two_days.last, two_days.first)
|
238
|
-
plus_dm << ((um > dm) ? um : 0)
|
239
|
-
end
|
240
|
-
plus_dm
|
241
|
-
end
|
242
|
-
|
243
|
-
def minus_directional_movement(data)
|
244
|
-
minus_dm = []
|
245
|
-
data.each_cons(2) do |two_days|
|
246
|
-
um = up_move(two_days.last, two_days.first)
|
247
|
-
dm = down_move(two_days.last, two_days.first)
|
248
|
-
minus_dm << ((dm > um) ? dm : 0)
|
249
|
-
end
|
250
|
-
minus_dm
|
251
|
-
end
|
252
|
-
|
253
|
-
#TODO: Pass in only the high prices to this method
|
254
|
-
# Up move is today_high - yesterday_high
|
255
|
-
def up_move(today, yesterday)
|
256
|
-
diff = today[SignalTools::StockData::Indexes[:high]] - yesterday[SignalTools::StockData::Indexes[:high]]
|
257
|
-
diff > 0 ? diff : 0
|
258
|
-
end
|
259
|
-
|
260
|
-
#TODO: Pass in only the low prices to this method
|
261
|
-
# Down move is yesterday_low - today_low
|
262
|
-
def down_move(today, yesterday)
|
263
|
-
diff = yesterday[SignalTools::StockData::Indexes[:low]] - today[SignalTools::StockData::Indexes[:low]]
|
264
|
-
diff > 0 ? diff : 0
|
265
|
-
end
|
266
|
-
|
267
|
-
#### Misc Utility Methods
|
268
|
-
|
269
|
-
# Returns only the points specific to the date range given.
|
270
|
-
def trim_data_to_range!(data)
|
271
|
-
if data.is_a? Array
|
272
|
-
data.slice!(0..(-dates.size-1))
|
273
|
-
elsif data.is_a? Hash
|
274
|
-
data.each { |k,v| v = v.slice!(0..(-dates.size-1)) }
|
275
|
-
end
|
276
|
-
data
|
26
|
+
macd_data = SignalTools::Technicals::MACD.new(stock_data.close_prices, fast, slow, signal).calculate
|
27
|
+
trim_data_to_range(macd_data, dates.size)
|
277
28
|
end
|
278
29
|
|
279
|
-
def
|
280
|
-
|
30
|
+
def fast_stochastic(k_period=14, d_period=5)
|
31
|
+
stochastic_data = SignalTools::Technicals::FastStochastic.new(stock_data, k_period, d_period).calculate
|
32
|
+
trim_data_to_range(stochastic_data, dates.size)
|
281
33
|
end
|
282
34
|
|
283
|
-
|
284
|
-
|
285
|
-
|
35
|
+
def slow_stochastic(k_period=14, d_period=5)
|
36
|
+
stochastic_data = SignalTools::Technicals::SlowStochastic.new(stock_data, k_period, d_period).calculate
|
37
|
+
trim_data_to_range(stochastic_data, dates.size)
|
286
38
|
end
|
287
39
|
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
when :average
|
292
|
-
SignalTools.average(points.slice(start..finish))
|
293
|
-
else
|
294
|
-
(points.slice(start..finish)).send(method)
|
295
|
-
end
|
40
|
+
def average_true_range(period=14)
|
41
|
+
atr_data = SignalTools::Technicals::AverageTrueRange.new(stock_data, period).calculate
|
42
|
+
trim_data_to_range(atr_data, dates.size)
|
296
43
|
end
|
297
44
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
raise unless points.size >= period
|
302
|
-
collection = []
|
303
|
-
index = 0
|
304
|
-
while((index + period - 1) < points.size)
|
305
|
-
collection << get_for_period(points, index, (index + period - 1), method)
|
306
|
-
index += 1
|
307
|
-
end
|
308
|
-
collection
|
45
|
+
def average_directional_index(period=14)
|
46
|
+
adx_data = SignalTools::Technicals::AverageDirectionalIndex.new(stock_data, period).calculate
|
47
|
+
trim_data_to_range(adx_data, dates.size)
|
309
48
|
end
|
310
49
|
end
|
311
50
|
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require './lib/signal_tools/technicals/true_range'
|
2
|
+
|
3
|
+
module SignalTools::Technicals
|
4
|
+
class AverageDirectionalIndex
|
5
|
+
include TrueRange
|
6
|
+
|
7
|
+
attr_reader :period, :stock_data
|
8
|
+
|
9
|
+
def initialize(stock_data, period)
|
10
|
+
@stock_data = stock_data
|
11
|
+
@period = period
|
12
|
+
end
|
13
|
+
|
14
|
+
def calculate
|
15
|
+
# trim_data_to_range!(average_directional_indexes(period))
|
16
|
+
average_directional_indexes
|
17
|
+
end
|
18
|
+
|
19
|
+
def average_directional_indexes
|
20
|
+
dxs = directional_indexes(plus_directional_index, minus_directional_index)
|
21
|
+
adxs = EMA.new(dxs, period, :wilder).calculate
|
22
|
+
adxs
|
23
|
+
end
|
24
|
+
|
25
|
+
def directional_indexes(plus_dis, minus_dis)
|
26
|
+
SignalTools.truncate_to_shortest!(plus_dis, minus_dis)
|
27
|
+
differences, sums = [], []
|
28
|
+
index = 0
|
29
|
+
while index < plus_dis.size
|
30
|
+
differences << (plus_dis[index] - minus_dis[index]).abs
|
31
|
+
sums << (plus_dis[index] + minus_dis[index])
|
32
|
+
index += 1
|
33
|
+
end
|
34
|
+
quotients(differences, sums)
|
35
|
+
end
|
36
|
+
|
37
|
+
def plus_directional_index
|
38
|
+
plus_dms = plus_directional_movement(@stock_data.raw_data)
|
39
|
+
plus_dm_sums = period_sum_ema(plus_dms)
|
40
|
+
true_range_sums = period_sum_ema(true_ranges(stock_data))
|
41
|
+
quotients(plus_dm_sums, true_range_sums)
|
42
|
+
end
|
43
|
+
|
44
|
+
def minus_directional_index
|
45
|
+
minus_dms = minus_directional_movement(@stock_data.raw_data)
|
46
|
+
minus_dm_sums = period_sum_ema(minus_dms)
|
47
|
+
true_range_sums = period_sum_ema(true_ranges(stock_data))
|
48
|
+
quotients(minus_dm_sums, true_range_sums)
|
49
|
+
end
|
50
|
+
|
51
|
+
def quotients(first, second)
|
52
|
+
SignalTools.truncate_to_shortest!(first, second)
|
53
|
+
index = 0
|
54
|
+
quots = []
|
55
|
+
while index < first.size
|
56
|
+
quots << first[index] / second[index]
|
57
|
+
index += 1
|
58
|
+
end
|
59
|
+
quots
|
60
|
+
end
|
61
|
+
|
62
|
+
def plus_directional_movement(data)
|
63
|
+
plus_dm = []
|
64
|
+
data.each_cons(2) do |two_days|
|
65
|
+
um = up_move(two_days.last, two_days.first)
|
66
|
+
dm = down_move(two_days.last, two_days.first)
|
67
|
+
plus_dm << ((um > dm) ? um : 0)
|
68
|
+
end
|
69
|
+
plus_dm
|
70
|
+
end
|
71
|
+
|
72
|
+
def minus_directional_movement(data)
|
73
|
+
minus_dm = []
|
74
|
+
data.each_cons(2) do |two_days|
|
75
|
+
um = up_move(two_days.last, two_days.first)
|
76
|
+
dm = down_move(two_days.last, two_days.first)
|
77
|
+
minus_dm << ((dm > um) ? dm : 0)
|
78
|
+
end
|
79
|
+
minus_dm
|
80
|
+
end
|
81
|
+
|
82
|
+
#TODO: Pass in only the high prices to this method
|
83
|
+
# Up move is today_high - yesterday_high
|
84
|
+
def up_move(today, yesterday)
|
85
|
+
diff = today[SignalTools::StockData::Indexes[:high]] - yesterday[SignalTools::StockData::Indexes[:high]]
|
86
|
+
diff > 0 ? diff : 0
|
87
|
+
end
|
88
|
+
|
89
|
+
#TODO: Pass in only the low prices to this method
|
90
|
+
# Down move is yesterday_low - today_low
|
91
|
+
def down_move(today, yesterday)
|
92
|
+
diff = yesterday[SignalTools::StockData::Indexes[:low]] - today[SignalTools::StockData::Indexes[:low]]
|
93
|
+
diff > 0 ? diff : 0
|
94
|
+
end
|
95
|
+
|
96
|
+
#Takes a period and array of data and calculates the sum ema over the period specified.
|
97
|
+
def period_sum_ema(data)
|
98
|
+
raise if data.size <= period
|
99
|
+
sum_emas = [SignalTools.sum(data[0...period])]
|
100
|
+
data[(period..-1)].each do |today|
|
101
|
+
sum_emas << (sum_emas.last - (sum_emas.last / period) + today)
|
102
|
+
end
|
103
|
+
sum_emas
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require './lib/signal_tools/technicals/common'
|
2
|
+
require './lib/signal_tools/technicals/true_range'
|
3
|
+
|
4
|
+
module SignalTools::Technicals
|
5
|
+
class AverageTrueRange
|
6
|
+
include Common
|
7
|
+
include TrueRange
|
8
|
+
|
9
|
+
DEFAULT_PERIOD = 14
|
10
|
+
|
11
|
+
attr_reader :period, :stock_data
|
12
|
+
|
13
|
+
def initialize(stock_data, period)
|
14
|
+
@period = period
|
15
|
+
@stock_data = stock_data
|
16
|
+
end
|
17
|
+
|
18
|
+
def calculate
|
19
|
+
average_true_ranges
|
20
|
+
end
|
21
|
+
|
22
|
+
# Takes a smoothing period and historical data and calculates the average
|
23
|
+
# true ranges.
|
24
|
+
def average_true_ranges
|
25
|
+
trs = true_ranges(stock_data)
|
26
|
+
atrs = [default_simple_average(trs.slice!(0...DEFAULT_PERIOD), DEFAULT_PERIOD)]
|
27
|
+
trs.each { |tr| atrs << calculate_average_true_range(atrs.last, tr, period) }
|
28
|
+
atrs
|
29
|
+
end
|
30
|
+
|
31
|
+
# Takes yesterday's average true range, today's true range, and the smoothing
|
32
|
+
# period and calculates the day's average true range.
|
33
|
+
def calculate_average_true_range(yesterday_atr, today_tr, period)
|
34
|
+
(yesterday_atr * (period - 1) + today_tr) / period
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require './lib/signal_tools/technicals/common'
|
2
|
+
|
3
|
+
module SignalTools
|
4
|
+
module Technicals
|
5
|
+
module Common
|
6
|
+
def trim_data_to_range(data, size)
|
7
|
+
if data.is_a?(Array)
|
8
|
+
data.last(size)
|
9
|
+
elsif data.is_a?(Hash)
|
10
|
+
data.keys.each { |key| data[key] = data[key].first(size) }
|
11
|
+
data
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Gets the first 0...period of numbers from data and returns a simple average.
|
16
|
+
def default_simple_average(data, period)
|
17
|
+
SignalTools.average(data.slice(0...period))
|
18
|
+
end
|
19
|
+
|
20
|
+
#Runs method for the given slice of the array.
|
21
|
+
def get_for_period(points, start, finish, method)
|
22
|
+
case method
|
23
|
+
when :average
|
24
|
+
SignalTools.average(points.slice(start..finish))
|
25
|
+
else
|
26
|
+
(points.slice(start..finish)).send(method)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#Returns a collection of values by iterating over an array, slicing it period
|
31
|
+
# elements at a time and calling method for each slice.
|
32
|
+
def collection_for_array(points, period, method)
|
33
|
+
raise unless points.size >= period
|
34
|
+
collection = []
|
35
|
+
index = 0
|
36
|
+
while((index + period - 1) < points.size)
|
37
|
+
collection << get_for_period(points, index, (index + period - 1), method)
|
38
|
+
index += 1
|
39
|
+
end
|
40
|
+
collection
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require './lib/signal_tools/technicals/common'
|
2
|
+
|
3
|
+
module SignalTools::Technicals
|
4
|
+
class EMA
|
5
|
+
include Common
|
6
|
+
|
7
|
+
EMA_DEFAULT = 10
|
8
|
+
|
9
|
+
attr_reader :data, :period, :type
|
10
|
+
|
11
|
+
def initialize(data, period, type=:default)
|
12
|
+
@data = data
|
13
|
+
@period = period
|
14
|
+
@type = type
|
15
|
+
end
|
16
|
+
|
17
|
+
def calculate
|
18
|
+
ema_points
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
#TODO: Break Wilder into its own class
|
24
|
+
def ema_points
|
25
|
+
emas = [default_simple_average(data, EMA_DEFAULT)]
|
26
|
+
if type == :wilder
|
27
|
+
data.slice(EMA_DEFAULT..-1).each { |current| emas << calculate_wilder_ema(emas.last, current) }
|
28
|
+
else
|
29
|
+
data.slice(EMA_DEFAULT..-1).each { |current| emas << calculate_ema(emas.last, current) }
|
30
|
+
end
|
31
|
+
emas
|
32
|
+
end
|
33
|
+
|
34
|
+
#Takes current value, previous day's EMA, and number of days. Returns EMA for that day.
|
35
|
+
def calculate_ema(previous, current)
|
36
|
+
(current - previous) * (2.0 / (period + 1)) + previous
|
37
|
+
end
|
38
|
+
|
39
|
+
#Uses Wilder's moving average formula.
|
40
|
+
def calculate_wilder_ema(previous, current)
|
41
|
+
(previous * (period - 1) + current) / period
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require './lib/signal_tools/technicals/common'
|
2
|
+
require './lib/signal_tools/technicals/stochastic'
|
3
|
+
|
4
|
+
module SignalTools::Technicals
|
5
|
+
class FastStochastic
|
6
|
+
include Common
|
7
|
+
include Stochastic
|
8
|
+
|
9
|
+
def initialize(stock_data, k_period, d_period)
|
10
|
+
@d_period = d_period
|
11
|
+
@k_period = k_period
|
12
|
+
@stock_data = stock_data
|
13
|
+
end
|
14
|
+
|
15
|
+
def calculate
|
16
|
+
fast_stochastic_points
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module SignalTools
|
2
|
+
module Technicals
|
3
|
+
class MACD
|
4
|
+
attr_reader :fast, :slow, :signal, :data
|
5
|
+
|
6
|
+
def initialize(data, fast, slow, signal)
|
7
|
+
@data = data
|
8
|
+
@fast = fast
|
9
|
+
@slow = slow
|
10
|
+
@signal = signal
|
11
|
+
end
|
12
|
+
|
13
|
+
def calculate
|
14
|
+
# trim_data_to_range!(macd_points)
|
15
|
+
macd_points
|
16
|
+
end
|
17
|
+
|
18
|
+
# Takes a period of days for fast, slow, signal, and time period (eg 8,17,9).
|
19
|
+
def macd_points
|
20
|
+
fast_ema_points = SignalTools::Technicals::EMA.new(data, fast).calculate
|
21
|
+
slow_ema_points = SignalTools::Technicals::EMA.new(data, slow).calculate
|
22
|
+
macd_and_divergence_points(fast_ema_points, slow_ema_points)
|
23
|
+
end
|
24
|
+
|
25
|
+
def macd_and_divergence_points(fast_ema_points, slow_ema_points)
|
26
|
+
macds = differences_between_arrays(fast_ema_points, slow_ema_points)
|
27
|
+
signal_points = SignalTools::Technicals::EMA.new(macds, signal).calculate
|
28
|
+
divergences = differences_between_arrays(macds, signal_points)
|
29
|
+
{:signal_points => signal_points, :divergences => divergences}
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns an array with the differences between the first_points and second_points
|
33
|
+
def differences_between_arrays(first_points, second_points)
|
34
|
+
SignalTools.truncate_to_shortest!(first_points, second_points)
|
35
|
+
differences = []
|
36
|
+
first_points.each_with_index { |fp, index| differences << fp - second_points[index] }
|
37
|
+
differences
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require './lib/signal_tools/technicals/stochastic'
|
2
|
+
|
3
|
+
module SignalTools::Technicals
|
4
|
+
class SlowStochastic
|
5
|
+
include Stochastic
|
6
|
+
|
7
|
+
SMA_DEFAULT = 3
|
8
|
+
|
9
|
+
def initialize(stock_data, k_period, d_period)
|
10
|
+
@d_period = d_period
|
11
|
+
@k_period = k_period
|
12
|
+
@stock_data = stock_data
|
13
|
+
end
|
14
|
+
|
15
|
+
def calculate
|
16
|
+
# trim_data_to_range!(slow_stochastic_points(k_period, d_period))
|
17
|
+
slow_stochastic_points
|
18
|
+
end
|
19
|
+
|
20
|
+
def slow_stochastic_points
|
21
|
+
fast_points = fast_stochastic_points
|
22
|
+
k_points = slow_k_points(fast_points[:k])
|
23
|
+
slow_d_points = calculate_d_points(k_points, d_period)
|
24
|
+
k_d_points(k_points, slow_d_points)
|
25
|
+
end
|
26
|
+
|
27
|
+
def slow_k_points(fast_k_points)
|
28
|
+
collection_for_array(fast_k_points, SMA_DEFAULT, :average)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require './lib/signal_tools/technicals/common'
|
2
|
+
|
3
|
+
module SignalTools::Technicals::Stochastic
|
4
|
+
include ::SignalTools::Technicals::Common
|
5
|
+
|
6
|
+
attr_reader :d_period, :k_period, :stock_data
|
7
|
+
|
8
|
+
def calculate_d_points(k_points, period)
|
9
|
+
collection_for_array(k_points, period, :average)
|
10
|
+
end
|
11
|
+
|
12
|
+
def k_d_points(k_points, d_points)
|
13
|
+
raise unless k_points.size > d_points.size
|
14
|
+
SignalTools.truncate_to_shortest!(k_points, d_points)
|
15
|
+
{:k => k_points, :d => d_points}
|
16
|
+
end
|
17
|
+
|
18
|
+
def fast_stochastic_points
|
19
|
+
k_points = calculate_fast_stochastic_k_points
|
20
|
+
d_points = calculate_d_points(k_points, d_period)
|
21
|
+
k_d_points(k_points, d_points)
|
22
|
+
end
|
23
|
+
|
24
|
+
def calculate_fast_stochastic_k_points
|
25
|
+
index = 0
|
26
|
+
points = []
|
27
|
+
while((index + k_period) <= stock_data.close_prices.size)
|
28
|
+
today_cp = stock_data.close_prices[index + k_period - 1]
|
29
|
+
low_price = get_for_period(stock_data.low_prices, index, index + k_period - 1, :min)
|
30
|
+
high_price = get_for_period(stock_data.high_prices, index, index + k_period - 1, :max)
|
31
|
+
points << (today_cp - low_price) / (high_price - low_price)
|
32
|
+
index += 1
|
33
|
+
end
|
34
|
+
points
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module SignalTools
|
2
|
+
module Technicals
|
3
|
+
module TrueRange
|
4
|
+
# Takes historical data and computes the true ranges.
|
5
|
+
def true_ranges(stock_data)
|
6
|
+
trs = [stock_data.high_prices.first - stock_data.low_prices.first]
|
7
|
+
index = 1
|
8
|
+
while index < (stock_data.high_prices.size)
|
9
|
+
trs << true_range(stock_data.raw_data[index], stock_data.raw_data[index-1])
|
10
|
+
index += 1
|
11
|
+
end
|
12
|
+
trs
|
13
|
+
end
|
14
|
+
|
15
|
+
# Takes today's data and yesterday's data and computes the true range.
|
16
|
+
def true_range(today, yesterday)
|
17
|
+
[
|
18
|
+
today[SignalTools::StockData::Indexes[:high]] - today[SignalTools::StockData::Indexes[:low]],
|
19
|
+
(yesterday[SignalTools::StockData::Indexes[:close]] - today[SignalTools::StockData::Indexes[:high]]).abs,
|
20
|
+
(yesterday[SignalTools::StockData::Indexes[:close]] - today[SignalTools::StockData::Indexes[:low]]).abs
|
21
|
+
].max
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require './test/test_helper'
|
2
|
+
|
3
|
+
class TestAverageDirectionalIndex < Minitest::Test
|
4
|
+
def setup
|
5
|
+
ticker = "TESTING"
|
6
|
+
@days = 90
|
7
|
+
|
8
|
+
YahooFinance.stub(:get_historical_quotes, data_for_tests(@days)) do
|
9
|
+
@stock = SignalTools::Stock.new(ticker)
|
10
|
+
end
|
11
|
+
|
12
|
+
@stock_data = @stock.stock_data
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_calculate
|
16
|
+
assert_equal "0.491321", "%.6f" % SignalTools::Technicals::AverageDirectionalIndex.new(@stock_data, 14).calculate[-1]
|
17
|
+
assert_equal "0.496588", "%.6f" % SignalTools::Technicals::AverageDirectionalIndex.new(@stock_data, 14).calculate[-5]
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require './test/test_helper'
|
2
|
+
|
3
|
+
class TestAverageTrueRange < Minitest::Test
|
4
|
+
def setup
|
5
|
+
ticker = "TESTING"
|
6
|
+
@days = 90
|
7
|
+
|
8
|
+
YahooFinance.stub(:get_historical_quotes, data_for_tests(@days)) do
|
9
|
+
@stock = SignalTools::Stock.new(ticker)
|
10
|
+
end
|
11
|
+
|
12
|
+
@stock_data = @stock.stock_data
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_calculate
|
16
|
+
assert_equal "3.195750", "%.6f" % SignalTools::Technicals::AverageTrueRange.new(@stock_data, 14).calculate[-1]
|
17
|
+
assert_equal "3.438910", "%.6f" % SignalTools::Technicals::AverageTrueRange.new(@stock_data, 14).calculate[-5]
|
18
|
+
assert_equal "3.208282", "%.6f" % SignalTools::Technicals::AverageTrueRange.new(@stock_data, 15).calculate[-1]
|
19
|
+
assert_equal "3.434397", "%.6f" % SignalTools::Technicals::AverageTrueRange.new(@stock_data, 15).calculate[-5]
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require './test/test_helper'
|
2
|
+
|
3
|
+
class TestEMA < Minitest::Test
|
4
|
+
def setup
|
5
|
+
ticker = "TESTING"
|
6
|
+
@days = 90
|
7
|
+
|
8
|
+
YahooFinance.stub(:get_historical_quotes, data_for_tests(@days)) do
|
9
|
+
@stock = SignalTools::Stock.new(ticker)
|
10
|
+
end
|
11
|
+
|
12
|
+
@close_prices = @stock.stock_data.close_prices
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_calculate
|
16
|
+
assert_equal "2.344948", "%.6f" % SignalTools::Technicals::EMA.new(@close_prices, 10, :default).calculate[-1]
|
17
|
+
assert_equal "2.736776", "%.6f" % SignalTools::Technicals::EMA.new(@close_prices, 10, :default).calculate[-5]
|
18
|
+
assert_equal "2.556322", "%.6f" % SignalTools::Technicals::EMA.new(@close_prices, 25, :default).calculate[-1]
|
19
|
+
assert_equal "2.705835", "%.6f" % SignalTools::Technicals::EMA.new(@close_prices, 25, :default).calculate[-5]
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require './test/test_helper'
|
2
|
+
|
3
|
+
class TestFastStochastic < Minitest::Test
|
4
|
+
def setup
|
5
|
+
ticker = "TESTING"
|
6
|
+
@days = 90
|
7
|
+
|
8
|
+
YahooFinance.stub(:get_historical_quotes, data_for_tests(@days)) do
|
9
|
+
@stock = SignalTools::Stock.new(ticker)
|
10
|
+
end
|
11
|
+
|
12
|
+
@stock_data = @stock.stock_data
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_calculate
|
16
|
+
assert_equal "0.057143", "%.6f" % SignalTools::Technicals::FastStochastic.new(@stock_data, 14, 5).calculate[:k][-1]
|
17
|
+
assert_equal "0.571429", "%.6f" % SignalTools::Technicals::FastStochastic.new(@stock_data, 14, 5).calculate[:k][-5]
|
18
|
+
assert_equal "0.314286", "%.6f" % SignalTools::Technicals::FastStochastic.new(@stock_data, 14, 5).calculate[:d][-1]
|
19
|
+
assert_equal "0.314286", "%.6f" % SignalTools::Technicals::FastStochastic.new(@stock_data, 14, 5).calculate[:d][-5]
|
20
|
+
|
21
|
+
assert_equal "0.057143", "%.6f" % SignalTools::Technicals::FastStochastic.new(@stock_data, 12, 3).calculate[:k][-1]
|
22
|
+
assert_equal "0.571429", "%.6f" % SignalTools::Technicals::FastStochastic.new(@stock_data, 12, 3).calculate[:k][-5]
|
23
|
+
assert_equal "0.185714", "%.6f" % SignalTools::Technicals::FastStochastic.new(@stock_data, 12, 3).calculate[:d][-1]
|
24
|
+
assert_equal "0.271429", "%.6f" % SignalTools::Technicals::FastStochastic.new(@stock_data, 12, 3).calculate[:d][-5]
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require './test/test_helper'
|
2
|
+
|
3
|
+
class TestMACD < Minitest::Test
|
4
|
+
def setup
|
5
|
+
ticker = "TESTING"
|
6
|
+
@days = 90
|
7
|
+
|
8
|
+
YahooFinance.stub(:get_historical_quotes, data_for_tests(@days)) do
|
9
|
+
@data = SignalTools::Stock.new(ticker)
|
10
|
+
end
|
11
|
+
|
12
|
+
@data = @data.stock_data.close_prices
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_calculate
|
16
|
+
assert_equal "-0.034645", "%.6f" % SignalTools::Technicals::MACD.new(@data, 8, 17, 9).calculate[:signal_points][-1]
|
17
|
+
assert_equal "-0.018762", "%.6f" % SignalTools::Technicals::MACD.new(@data, 8, 17, 9).calculate[:signal_points][-5]
|
18
|
+
assert_equal "-0.195043", "%.6f" % SignalTools::Technicals::MACD.new(@data, 8, 17, 9).calculate[:divergences][-1]
|
19
|
+
assert_equal "0.063532", "%.6f" % SignalTools::Technicals::MACD.new(@data, 8, 17, 9).calculate[:divergences][-5]
|
20
|
+
|
21
|
+
assert_equal "-0.022654", "%.6f" % SignalTools::Technicals::MACD.new(@data, 12, 26, 9).calculate[:signal_points][-1]
|
22
|
+
assert_equal "-0.014099", "%.6f" % SignalTools::Technicals::MACD.new(@data, 12, 26, 9).calculate[:signal_points][-5]
|
23
|
+
assert_equal "-0.136291", "%.6f" % SignalTools::Technicals::MACD.new(@data, 12, 26, 9).calculate[:divergences][-1]
|
24
|
+
assert_equal "0.034219", "%.6f" % SignalTools::Technicals::MACD.new(@data, 12, 26, 9).calculate[:divergences][-5]
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require './test/test_helper'
|
2
|
+
|
3
|
+
class TestSlowStochastic < Minitest::Test
|
4
|
+
def setup
|
5
|
+
ticker = "TESTING"
|
6
|
+
@days = 90
|
7
|
+
|
8
|
+
YahooFinance.stub(:get_historical_quotes, data_for_tests(@days)) do
|
9
|
+
@stock = SignalTools::Stock.new(ticker)
|
10
|
+
end
|
11
|
+
|
12
|
+
@stock_data = @stock.stock_data
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_calculate
|
16
|
+
assert_equal "0.185714", "%.6f" % SignalTools::Technicals::SlowStochastic.new(@stock_data, 14, 5).calculate[:k][-1]
|
17
|
+
assert_equal "0.271429", "%.6f" % SignalTools::Technicals::SlowStochastic.new(@stock_data, 14, 5).calculate[:k][-5]
|
18
|
+
assert_equal "0.314286", "%.6f" % SignalTools::Technicals::SlowStochastic.new(@stock_data, 14, 5).calculate[:d][-1]
|
19
|
+
assert_equal "0.314286", "%.6f" % SignalTools::Technicals::SlowStochastic.new(@stock_data, 14, 5).calculate[:d][-5]
|
20
|
+
|
21
|
+
assert_equal "0.185714", "%.6f" % SignalTools::Technicals::SlowStochastic.new(@stock_data, 12, 3).calculate[:k][-1]
|
22
|
+
assert_equal "0.271429", "%.6f" % SignalTools::Technicals::SlowStochastic.new(@stock_data, 12, 3).calculate[:k][-5]
|
23
|
+
assert_equal "0.314286", "%.6f" % SignalTools::Technicals::SlowStochastic.new(@stock_data, 12, 3).calculate[:d][-1]
|
24
|
+
assert_equal "0.257143", "%.6f" % SignalTools::Technicals::SlowStochastic.new(@stock_data, 12, 3).calculate[:d][-5]
|
25
|
+
end
|
26
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -1,25 +1,21 @@
|
|
1
|
-
require '
|
2
|
-
require 'test/unit'
|
3
|
-
require 'flexmock/test_unit'
|
1
|
+
require 'minitest/autorun'
|
4
2
|
|
5
3
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
6
4
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
7
5
|
require 'signal_tools'
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
]
|
22
|
-
end
|
23
|
-
historical_data
|
7
|
+
def data_for_tests(period)
|
8
|
+
repeat = 5
|
9
|
+
historical_data = []
|
10
|
+
(0...SignalTools::StockData::Extra_Days+period).each do |i|
|
11
|
+
seed = i % repeat + 1
|
12
|
+
historical_data << [
|
13
|
+
(Date.today-i).to_s,
|
14
|
+
(seed * 0.8).to_s, #Open
|
15
|
+
(seed * 1.5).to_s, #High
|
16
|
+
(seed * 0.5).to_s, #Low
|
17
|
+
(seed * 0.9).to_s #Close
|
18
|
+
]
|
24
19
|
end
|
25
|
-
|
20
|
+
historical_data
|
21
|
+
end
|
data/test/test_signal_tools.rb
CHANGED
data/test/test_stock.rb
CHANGED
@@ -1,67 +1,13 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
require 'signal_tools'
|
1
|
+
require './test/test_helper'
|
3
2
|
|
4
|
-
class TestStock < Test
|
3
|
+
class TestStock < Minitest::Test
|
5
4
|
def setup
|
6
5
|
ticker = "TESTING"
|
7
6
|
@days = 90
|
8
|
-
flexmock(YahooFinance).should_receive(:get_historical_quotes).with_any_args.and_return(data_for_tests(@days))
|
9
|
-
@stock = SignalTools::Stock.new(ticker)
|
10
|
-
end
|
11
|
-
|
12
|
-
def test_ema
|
13
|
-
assert_equal "2.344948", "%.6f" % @stock.ema[-1]
|
14
|
-
assert_equal "2.736776", "%.6f" % @stock.ema[-5]
|
15
|
-
assert_equal "2.556322", "%.6f" % @stock.ema(25)[-1]
|
16
|
-
assert_equal "2.705835", "%.6f" % @stock.ema(25)[-5]
|
17
|
-
end
|
18
|
-
|
19
|
-
def test_macd
|
20
|
-
assert_equal "-0.034645", "%.6f" % @stock.macd[:signal_points][-1]
|
21
|
-
assert_equal "-0.018762", "%.6f" % @stock.macd[:signal_points][-5]
|
22
|
-
assert_equal "-0.195043", "%.6f" % @stock.macd[:divergences][-1]
|
23
|
-
assert_equal "0.063532", "%.6f" % @stock.macd[:divergences][-5]
|
24
|
-
|
25
|
-
assert_equal "-0.022654", "%.6f" % @stock.macd(12, 26, 9)[:signal_points][-1]
|
26
|
-
assert_equal "-0.014099", "%.6f" % @stock.macd(12, 26, 9)[:signal_points][-5]
|
27
|
-
assert_equal "-0.136291", "%.6f" % @stock.macd(12, 26, 9)[:divergences][-1]
|
28
|
-
assert_equal "0.034219", "%.6f" % @stock.macd(12, 26, 9)[:divergences][-5]
|
29
|
-
end
|
30
|
-
|
31
|
-
def test_fast_stochastic
|
32
|
-
assert_equal "0.057143", "%.6f" % @stock.fast_stochastic[:k][-1]
|
33
|
-
assert_equal "0.571429", "%.6f" % @stock.fast_stochastic[:k][-5]
|
34
|
-
assert_equal "0.314286", "%.6f" % @stock.fast_stochastic[:d][-1]
|
35
|
-
assert_equal "0.314286", "%.6f" % @stock.fast_stochastic[:d][-5]
|
36
|
-
|
37
|
-
assert_equal "0.057143", "%.6f" % @stock.fast_stochastic(12, 3)[:k][-1]
|
38
|
-
assert_equal "0.571429", "%.6f" % @stock.fast_stochastic(12, 3)[:k][-5]
|
39
|
-
assert_equal "0.185714", "%.6f" % @stock.fast_stochastic(12, 3)[:d][-1]
|
40
|
-
assert_equal "0.271429", "%.6f" % @stock.fast_stochastic(12, 3)[:d][-5]
|
41
|
-
end
|
42
|
-
|
43
|
-
def test_slow_stochastic
|
44
|
-
assert_equal "0.185714", "%.6f" % @stock.slow_stochastic[:k][-1]
|
45
|
-
assert_equal "0.271429", "%.6f" % @stock.slow_stochastic[:k][-5]
|
46
|
-
assert_equal "0.314286", "%.6f" % @stock.slow_stochastic[:d][-1]
|
47
|
-
assert_equal "0.314286", "%.6f" % @stock.slow_stochastic[:d][-5]
|
48
|
-
|
49
|
-
assert_equal "0.185714", "%.6f" % @stock.slow_stochastic(12, 3)[:k][-1]
|
50
|
-
assert_equal "0.271429", "%.6f" % @stock.slow_stochastic(12, 3)[:k][-5]
|
51
|
-
assert_equal "0.314286", "%.6f" % @stock.slow_stochastic(12, 3)[:d][-1]
|
52
|
-
assert_equal "0.257143", "%.6f" % @stock.slow_stochastic(12, 3)[:d][-5]
|
53
|
-
end
|
54
|
-
|
55
|
-
def test_atr
|
56
|
-
assert_equal "3.195750", "%.6f" % @stock.atr[-1]
|
57
|
-
assert_equal "3.438910", "%.6f" % @stock.atr[-5]
|
58
|
-
assert_equal "3.208282", "%.6f" % @stock.atr(15)[-1]
|
59
|
-
assert_equal "3.434397", "%.6f" % @stock.atr(15)[-5]
|
60
|
-
end
|
61
7
|
|
62
|
-
|
63
|
-
|
64
|
-
|
8
|
+
YahooFinance.stub(:get_historical_quotes, data_for_tests(@days)) do
|
9
|
+
@stock = SignalTools::Stock.new(ticker)
|
10
|
+
end
|
65
11
|
end
|
66
12
|
|
67
13
|
def test_stock_should_have_correct_number_of_data_elements
|
@@ -70,7 +16,7 @@ class TestStock < Test::Unit::TestCase
|
|
70
16
|
assert_equal(@days, @stock.macd[:divergences].size)
|
71
17
|
assert_equal(@days, @stock.fast_stochastic[:k].size)
|
72
18
|
assert_equal(@days, @stock.slow_stochastic[:k].size)
|
73
|
-
assert_equal(@days, @stock.
|
74
|
-
assert_equal(@days, @stock.
|
19
|
+
assert_equal(@days, @stock.average_true_range.size)
|
20
|
+
assert_equal(@days, @stock.average_directional_index.size)
|
75
21
|
end
|
76
22
|
end
|
data/test/test_stock_data.rb
CHANGED
@@ -1,15 +1,16 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
require 'signal_tools'
|
1
|
+
require './test/test_helper'
|
3
2
|
|
4
|
-
class TestStockData < Test
|
3
|
+
class TestStockData < Minitest::Test
|
5
4
|
def setup
|
6
5
|
ticker = "TESTING"
|
7
6
|
@days = 90
|
8
7
|
@total_days = @days + SignalTools::StockData::Extra_Days
|
9
8
|
@from_date = Date.today - @days
|
10
9
|
@to_date = Date.today
|
11
|
-
|
12
|
-
|
10
|
+
|
11
|
+
YahooFinance.stub(:get_historical_quotes, data_for_tests(@days)) do
|
12
|
+
@stock_data = SignalTools::StockData.new(ticker, @from_date, @to_date)
|
13
|
+
end
|
13
14
|
end
|
14
15
|
|
15
16
|
def test_dates
|
metadata
CHANGED
@@ -1,27 +1,55 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: signal_tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.3.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Matt White
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2015-05-28 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: yahoofinance
|
16
|
-
requirement:
|
17
|
-
none: false
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - "~>"
|
20
18
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
19
|
+
version: 1.2.0
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.2.0
|
22
23
|
type: :runtime
|
23
24
|
prerelease: false
|
24
|
-
version_requirements:
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.2.0
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.2.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: minitest
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 5.5.0
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 5.5.0
|
43
|
+
type: :development
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 5.5.0
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 5.5.0
|
25
53
|
description: Gem to create technical analysis data for a given stock (like MACD, stochastic,
|
26
54
|
and exponential moving averages).
|
27
55
|
email: mattw922@gmail.com
|
@@ -29,39 +57,54 @@ executables: []
|
|
29
57
|
extensions: []
|
30
58
|
extra_rdoc_files: []
|
31
59
|
files:
|
60
|
+
- LICENSE
|
61
|
+
- README.rdoc
|
32
62
|
- Rakefile
|
33
63
|
- lib/signal_tools.rb
|
34
64
|
- lib/signal_tools/stock.rb
|
35
65
|
- lib/signal_tools/stock_data.rb
|
36
|
-
-
|
37
|
-
-
|
66
|
+
- lib/signal_tools/technicals/average_directional_index.rb
|
67
|
+
- lib/signal_tools/technicals/average_true_range.rb
|
68
|
+
- lib/signal_tools/technicals/common.rb
|
69
|
+
- lib/signal_tools/technicals/ema.rb
|
70
|
+
- lib/signal_tools/technicals/fast_stochastic.rb
|
71
|
+
- lib/signal_tools/technicals/macd.rb
|
72
|
+
- lib/signal_tools/technicals/slow_stochastic.rb
|
73
|
+
- lib/signal_tools/technicals/stochastic.rb
|
74
|
+
- lib/signal_tools/technicals/true_range.rb
|
75
|
+
- test/lib/signal_tools/technicals/test_average_directional_index.rb
|
76
|
+
- test/lib/signal_tools/technicals/test_average_true_range.rb
|
77
|
+
- test/lib/signal_tools/technicals/test_ema.rb
|
78
|
+
- test/lib/signal_tools/technicals/test_fast_stochastic.rb
|
79
|
+
- test/lib/signal_tools/technicals/test_macd.rb
|
80
|
+
- test/lib/signal_tools/technicals/test_slow_stochastic.rb
|
81
|
+
- test/test_helper.rb
|
38
82
|
- test/test_signal_tools.rb
|
83
|
+
- test/test_stock.rb
|
39
84
|
- test/test_stock_data.rb
|
40
|
-
- test/
|
41
|
-
- README.rdoc
|
42
|
-
- LICENSE
|
85
|
+
- test/test_tickers
|
43
86
|
homepage: http://github.com/whitethunder/signal_tools
|
44
|
-
licenses:
|
87
|
+
licenses:
|
88
|
+
- MIT
|
89
|
+
metadata: {}
|
45
90
|
post_install_message:
|
46
91
|
rdoc_options: []
|
47
92
|
require_paths:
|
48
93
|
- lib
|
49
94
|
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
-
none: false
|
51
95
|
requirements:
|
52
|
-
- -
|
96
|
+
- - ">="
|
53
97
|
- !ruby/object:Gem::Version
|
54
98
|
version: '0'
|
55
99
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
-
none: false
|
57
100
|
requirements:
|
58
|
-
- -
|
101
|
+
- - ">="
|
59
102
|
- !ruby/object:Gem::Version
|
60
103
|
version: '0'
|
61
104
|
requirements: []
|
62
105
|
rubyforge_project:
|
63
|
-
rubygems_version:
|
106
|
+
rubygems_version: 2.4.5
|
64
107
|
signing_key:
|
65
|
-
specification_version:
|
108
|
+
specification_version: 4
|
66
109
|
summary: Create technical analysis data for a given stock.
|
67
110
|
test_files: []
|