options-lib 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +9 -0
- data/bin/show_quotes +35 -0
- data/lib/options-lib/helpers.rb +36 -0
- data/lib/options-lib/option.rb +62 -0
- data/lib/options-lib/option_quote.rb +29 -0
- data/lib/options-lib/yahoo_crawler.rb +169 -0
- data/lib/options-lib.rb +6 -0
- data/options-lib.gemspec +26 -0
- data/spec/helpers_spec.rb +46 -0
- data/spec/option_quote_spec.rb +23 -0
- data/spec/option_spec.rb +63 -0
- data/spec/yahoo_crawler_spec.rb +59 -0
- metadata +91 -0
data/README.markdown
ADDED
data/bin/show_quotes
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'options-lib'
|
5
|
+
rescue LoadError
|
6
|
+
require 'rubygems'
|
7
|
+
require 'options-lib'
|
8
|
+
end
|
9
|
+
|
10
|
+
if ARGV.length != 5
|
11
|
+
puts "format: show_quotes <stock> <call_or_put> <strike> <expiration> <reload_period>"
|
12
|
+
puts "example: show_quotes AAPL C 345 2013-01-18 5"
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
|
16
|
+
stock = ARGV[0]
|
17
|
+
is_call = ARGV[1].casecmp("C") == 0
|
18
|
+
strike = ARGV[2].to_f
|
19
|
+
expiration = ARGV[3]
|
20
|
+
reload_period = ARGV[4].to_i
|
21
|
+
|
22
|
+
y = YahooCrawler.new(stock, expiration)
|
23
|
+
|
24
|
+
y.auto_reload(reload_period) do
|
25
|
+
|
26
|
+
options = is_call ? y.call_options : y.put_options
|
27
|
+
quote = options[strike]
|
28
|
+
stock_price = y.curr_stock_price
|
29
|
+
|
30
|
+
puts "Stock @ #{stock_price} Options: #{quote.bid} | #{quote.ask} Spread: #{quote.spread}"
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
y.join_reload_thread
|
35
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Float
|
2
|
+
|
3
|
+
# Supress decimal part if it is zero
|
4
|
+
# 40.0 becomes "40"
|
5
|
+
# Take care of float imprecision
|
6
|
+
# 1.1-0.9 # >> 0.20000000000000007
|
7
|
+
# (1.1-0.9).prettify # >> "0.2"
|
8
|
+
def prettify
|
9
|
+
num = "%.12g" % self
|
10
|
+
num.sub!($1, '') if num =~ /\..*?(0+)$/
|
11
|
+
# might be like 2. at this point
|
12
|
+
num = num[0..-2] if num[-1] == '.'
|
13
|
+
num
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Array
|
18
|
+
|
19
|
+
# Break the array into smaller arrays (chuncks)
|
20
|
+
# If the lenght of the array is not divisible by the number of chunks (pieces)
|
21
|
+
# then first chunk will accomodate the extra item and be the larger chunk
|
22
|
+
# Ex: [1,2,3,4].chunck # => [[1,2], [3,4]]
|
23
|
+
def chunk(pieces=2)
|
24
|
+
len = self.length;
|
25
|
+
mid = (len / pieces)
|
26
|
+
chunks = []
|
27
|
+
start = 0
|
28
|
+
1.upto(pieces) do |i|
|
29
|
+
last = start + mid
|
30
|
+
last = last - 1 unless len % pieces >= i
|
31
|
+
chunks << self[start..last] || []
|
32
|
+
start = last + 1
|
33
|
+
end
|
34
|
+
chunks
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'date'
|
2
|
+
require_relative 'helpers'
|
3
|
+
|
4
|
+
class Option
|
5
|
+
|
6
|
+
CALL = 'CALL'
|
7
|
+
PUT = 'PUT'
|
8
|
+
|
9
|
+
attr_reader :stock, :exp, :type, :strike, :symbol, :internal_symbol
|
10
|
+
|
11
|
+
def initialize(type, stock, strike, exp, symbol = nil)
|
12
|
+
raise "Invalid type: #{type}" if type != CALL and type != PUT
|
13
|
+
|
14
|
+
@type, @stock, @strike = type, stock, strike.to_f
|
15
|
+
|
16
|
+
if exp =~ /(\d{4})[\-\/](\d{1,2})[\-\/](\d{1,2})/
|
17
|
+
@exp = Date.new($1.to_i, $2.to_i, $3.to_i)
|
18
|
+
else
|
19
|
+
raise "Cannot parse expiration date: #{exp}"
|
20
|
+
end
|
21
|
+
|
22
|
+
s = "#{stock}_#{type == CALL ? 'C' : 'P'}#{@strike.prettify}_#{@exp.day}"
|
23
|
+
s << Date::MONTHNAMES[@exp.month][0,3].upcase
|
24
|
+
s << @exp.year.to_s
|
25
|
+
@internal_symbol = s
|
26
|
+
|
27
|
+
if not symbol
|
28
|
+
@symbol = @internal_symbol
|
29
|
+
else
|
30
|
+
@symbol = symbol
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def is_call?
|
35
|
+
@type == CALL
|
36
|
+
end
|
37
|
+
|
38
|
+
def is_put?
|
39
|
+
@type == PUT
|
40
|
+
end
|
41
|
+
|
42
|
+
# Business days (= minus Sat and Sun) until expiration
|
43
|
+
def days_to_exp
|
44
|
+
total = 0
|
45
|
+
curr_day = Date.today
|
46
|
+
while curr_day <= exp
|
47
|
+
total += 1 if curr_day.wday != 0 and curr_day.wday != 6
|
48
|
+
curr_day = curr_day.next
|
49
|
+
end
|
50
|
+
total
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_s
|
54
|
+
@internal_symbol
|
55
|
+
end
|
56
|
+
|
57
|
+
def inspect
|
58
|
+
@internal_symbol
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'option'
|
2
|
+
|
3
|
+
class OptionQuote
|
4
|
+
|
5
|
+
attr_reader :option, :bid, :ask
|
6
|
+
|
7
|
+
def initialize(option, args)
|
8
|
+
@option = option
|
9
|
+
@bid = args[:bid] || nil
|
10
|
+
@ask = args[:ask] || nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def spread
|
14
|
+
if @bid and @ask
|
15
|
+
(@ask - @bid).prettify.to_f
|
16
|
+
else
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"#{option.to_s}: #{bid.inspect} / #{ask.inspect}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"#{option.inspect}: bid => #{bid.inspect}, ask => #{ask.inspect}"
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require 'Mechanize'
|
3
|
+
|
4
|
+
require_relative 'option'
|
5
|
+
require_relative 'option_quote'
|
6
|
+
require_relative 'helpers'
|
7
|
+
|
8
|
+
# A Yahoo!Finance crawler implemented using Mechanize to parse HTML and extract
|
9
|
+
# options quotes for given stock and expiration date.
|
10
|
+
class YahooCrawler
|
11
|
+
|
12
|
+
def initialize(stock, exp)
|
13
|
+
@mech = Mechanize.new
|
14
|
+
@stock, @exp = stock, exp
|
15
|
+
@url = "http://finance.yahoo.com/q/op?s=#{stock}&m=#{exp[0,7]}"
|
16
|
+
@stock_curr_price = nil
|
17
|
+
@call_options = Hash.new
|
18
|
+
@put_options = Hash.new
|
19
|
+
@call_strikes = Array.new
|
20
|
+
@put_strikes = Array.new
|
21
|
+
@lock = Mutex.new
|
22
|
+
@t_lock = Mutex.new
|
23
|
+
@thread = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def auto_reload(period = 60)
|
27
|
+
if not @thread.nil?
|
28
|
+
stop
|
29
|
+
end
|
30
|
+
|
31
|
+
@thread = Thread.new do
|
32
|
+
loop do
|
33
|
+
begin
|
34
|
+
fetch
|
35
|
+
yield # callback
|
36
|
+
rescue => e
|
37
|
+
puts "Error fetching data: #{e.message}"
|
38
|
+
end
|
39
|
+
sleep period
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def stop
|
46
|
+
if not @thread.nil?
|
47
|
+
@thread.kill
|
48
|
+
@thread = nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def join_reload_thread
|
53
|
+
@thread.join if @thread
|
54
|
+
end
|
55
|
+
|
56
|
+
def fetch
|
57
|
+
|
58
|
+
c_options, p_options = Hash.new, Hash.new
|
59
|
+
c_strikes, p_strikes = Array.new, Array.new
|
60
|
+
stock_price = nil
|
61
|
+
|
62
|
+
@t_lock.synchronize { # don't step into each other in case someone calls fetch
|
63
|
+
|
64
|
+
page = @mech.get(@url).body
|
65
|
+
|
66
|
+
curr_price = page.scan(/\: \<.+?\<\/big\>/)
|
67
|
+
lines = parse_data(curr_price[0])
|
68
|
+
stock_price = lines[0].to_f
|
69
|
+
|
70
|
+
calls = page.scan(/Strike\<.+Put Options/)
|
71
|
+
puts = page.scan(/Put Options\<.+Highlighted/)
|
72
|
+
|
73
|
+
lines = parse_data(calls[0])
|
74
|
+
lines = lines.chunk(lines.length / 8)
|
75
|
+
|
76
|
+
lines.each do |array|
|
77
|
+
quote = parse_quote(array, Option::CALL)
|
78
|
+
c_options[quote.option.strike] = quote
|
79
|
+
end
|
80
|
+
|
81
|
+
lines = parse_data(puts[0])
|
82
|
+
lines = lines.chunk(lines.length / 8)
|
83
|
+
|
84
|
+
lines.each do |array|
|
85
|
+
quote = parse_quote(array, Option::PUT)
|
86
|
+
p_options[quote.option.strike] = quote
|
87
|
+
end
|
88
|
+
|
89
|
+
c_options.keys.sort.each { |key| c_strikes << key }
|
90
|
+
|
91
|
+
p_options.keys.sort.each { |key| p_strikes << key }
|
92
|
+
|
93
|
+
}
|
94
|
+
|
95
|
+
@lock.synchronize {
|
96
|
+
@call_options, @put_options = c_options, p_options
|
97
|
+
@call_strikes, @put_strikes = c_strikes, p_strikes
|
98
|
+
@curr_stock_price = stock_price
|
99
|
+
}
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
def get_option_quote(type, strike)
|
104
|
+
if type == Option::CALL
|
105
|
+
get_call_option_quote(strike)
|
106
|
+
else
|
107
|
+
get_put_option_quote(strike)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_call_option_quote(strike)
|
112
|
+
call_options[strike]
|
113
|
+
end
|
114
|
+
|
115
|
+
def get_put_option_quote(strike)
|
116
|
+
put_options[strike]
|
117
|
+
end
|
118
|
+
|
119
|
+
def call_strikes
|
120
|
+
@lock.synchronize { @call_strikes }
|
121
|
+
end
|
122
|
+
|
123
|
+
def put_strikes
|
124
|
+
@lock.synchronize { @put_strikes }
|
125
|
+
end
|
126
|
+
|
127
|
+
def call_options
|
128
|
+
@lock.synchronize { @call_options }
|
129
|
+
end
|
130
|
+
|
131
|
+
def put_options
|
132
|
+
@lock.synchronize { @put_options }
|
133
|
+
end
|
134
|
+
|
135
|
+
def curr_stock_price
|
136
|
+
@lock.synchronize { @curr_stock_price }
|
137
|
+
end
|
138
|
+
|
139
|
+
def show_calls
|
140
|
+
call_strikes.each { |key| puts call_options[key] }
|
141
|
+
end
|
142
|
+
|
143
|
+
def show_puts
|
144
|
+
put_strikes.each { |key| puts put_options[key] }
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def parse_quote(line, type)
|
150
|
+
strike = line[0].to_f
|
151
|
+
symbol = line[1]
|
152
|
+
bid = f line[4]
|
153
|
+
ask = f line[5]
|
154
|
+
|
155
|
+
o = Option.new(type, @stock, strike, @exp, symbol)
|
156
|
+
OptionQuote.new(o, :bid => bid, :ask => ask)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Get all values inside tags
|
160
|
+
# Ex: <h1>234</h1> will return "234"
|
161
|
+
def parse_data(data)
|
162
|
+
data.scan(/\>[0-9\.A-Z\,\/]+\</).collect { |i| i.gsub(/[\<\>]/, "") }
|
163
|
+
end
|
164
|
+
|
165
|
+
def f(data)
|
166
|
+
data == 'N/A' ? nil : data.to_f
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
data/lib/options-lib.rb
ADDED
data/options-lib.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'options-lib'
|
3
|
+
s.version = '0.9.2'
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.summary = 'Options Library'
|
6
|
+
s.description = 'A set of classes for dealing with options. It includes a crawler for Yahoo!Finance.'
|
7
|
+
|
8
|
+
s.author = 'Sergio Oliveira Jr.'
|
9
|
+
s.email = 'sergio.oliveira.jr@gmail.com'
|
10
|
+
s.homepage = 'https://github.com/saoj/options-lib'
|
11
|
+
|
12
|
+
# These dependencies are only for people who work on this gem
|
13
|
+
s.add_development_dependency 'rspec'
|
14
|
+
|
15
|
+
# Runtime dependencies
|
16
|
+
s.add_runtime_dependency 'mechanize'
|
17
|
+
|
18
|
+
# The list of files to be contained in the gem
|
19
|
+
s.files = `git ls-files`.split("\n") - [".gitignore"]
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
|
23
|
+
# Supress the warning about no rubyforge project
|
24
|
+
s.rubyforge_project = 'nowarning'
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# require all files from lib
|
2
|
+
Dir["./lib/*.rb"].each {|file| require file }
|
3
|
+
|
4
|
+
describe Float do
|
5
|
+
|
6
|
+
it 'can format its value in a pretty way' do
|
7
|
+
|
8
|
+
# if it has a decimal part, print it
|
9
|
+
f = 50.1
|
10
|
+
f.prettify.should == "50.1"
|
11
|
+
|
12
|
+
# if decimal part is 0, do not print it
|
13
|
+
f = 50.0
|
14
|
+
f.prettify.should == "50"
|
15
|
+
|
16
|
+
# take care of ruby float imprecision
|
17
|
+
f = 1.1 - 0.9
|
18
|
+
f.should_not == 0.2 # weird but true !!!
|
19
|
+
f.prettify.should == "0.2" # much better!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe Array do
|
24
|
+
|
25
|
+
it 'can be broken into chunks of smaller arrays' do
|
26
|
+
|
27
|
+
# the first chunk is always the bigger in case the number of
|
28
|
+
# elements are not divisible by pieces
|
29
|
+
|
30
|
+
a = [1,2,3,4]
|
31
|
+
a.chunk.should == [[1,2],[3,4]]
|
32
|
+
a.chunk(3).should == [[1,2],[3],[4]]
|
33
|
+
a.chunk(4).should == [[1],[2],[3],[4]]
|
34
|
+
|
35
|
+
a << 5 << 6
|
36
|
+
a.chunk.should == [[1,2,3],[4,5,6]]
|
37
|
+
a.chunk(3).should == [[1,2],[3,4],[5,6]]
|
38
|
+
|
39
|
+
a << 7
|
40
|
+
a.chunk.should == [[1,2,3,4],[5,6,7]]
|
41
|
+
a.chunk(3).should == [[1,2,3],[4,5],[6,7]]
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# require all files from lib
|
2
|
+
Dir["./lib/*.rb"].each {|file| require file }
|
3
|
+
|
4
|
+
describe OptionQuote do
|
5
|
+
|
6
|
+
it 'has option, bid and ask prices' do
|
7
|
+
|
8
|
+
o = Option.new(Option::CALL, 'AAPL', 250, '2010-02-14')
|
9
|
+
q = OptionQuote.new(o, :bid => 1.2, :ask => 1.4)
|
10
|
+
q.option.should == o
|
11
|
+
q.bid.should == 1.2
|
12
|
+
q.ask.should == 1.4
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should have an spread' do
|
17
|
+
o = Option.new(Option::CALL, 'AAPL', 250, '2010-02-14')
|
18
|
+
q = OptionQuote.new(o, :bid => 1.2, :ask => 1.4)
|
19
|
+
q.spread.prettify.should == "0.2"
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/spec/option_spec.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# require all files from lib
|
2
|
+
Dir["./lib/*.rb"].each {|file| require file }
|
3
|
+
|
4
|
+
describe Option do
|
5
|
+
|
6
|
+
it 'can be a call or a put' do
|
7
|
+
o = Option.new(Option::CALL, 'AAPL', 250, '2010-02-14')
|
8
|
+
o.type.should == Option::CALL
|
9
|
+
o.type.should == 'CALL'
|
10
|
+
|
11
|
+
o = Option.new(Option::PUT, 'AAPL', 250, '2010-02-14')
|
12
|
+
o.type.should == Option::PUT
|
13
|
+
o.type.should == 'PUT'
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'has stock and strike price' do
|
17
|
+
o = Option.new(Option::CALL, 'AAPL', 250, '2010-02-14')
|
18
|
+
o.stock.should == 'AAPL'
|
19
|
+
o.strike.should == 250
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'converts expiration to a nice date object' do
|
24
|
+
o = Option.new(Option::PUT, 'AAPL', 250, '2010-08-14')
|
25
|
+
o.exp.day.should == 14
|
26
|
+
o.exp.month.should == 8
|
27
|
+
o.exp.year.should == 2010
|
28
|
+
|
29
|
+
o = Option.new(Option::CALL, 'AAPL', 250, '2011/09/15')
|
30
|
+
o.exp.day.should == 15
|
31
|
+
o.exp.month.should == 9
|
32
|
+
o.exp.year.should == 2011
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'throws an exception if date is bad' do
|
36
|
+
lambda {Option.new(Option::PUT, 'AAPL', 250, '10-08-14')}.should raise_error
|
37
|
+
lambda {Option.new(Option::PUT, 'AAPL', 250, '10-58-14')}.should raise_error
|
38
|
+
lambda {Option.new(Option::PUT, 'AAPL', 250, '2010-122-14')}.should raise_error
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'has an internal symbol representation implied from stock, strike and experiration' do
|
42
|
+
o = Option.new(Option::PUT, 'AAPL', 250, '2010-08-14')
|
43
|
+
o.internal_symbol.should == 'AAPL_P250_14AUG2010'
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'makes the symbol equals to the internal symbol if symbol not provided' do
|
47
|
+
o = Option.new(Option::PUT, 'AAPL', 250, '2010-08-14')
|
48
|
+
o.internal_symbol.should == o.symbol
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'can have a symbol' do
|
52
|
+
o = Option.new(Option::PUT, 'AAPL', 250, '2010-08-14', 'MYSYMBOL')
|
53
|
+
o.symbol.should == 'MYSYMBOL'
|
54
|
+
o.internal_symbol.should == 'AAPL_P250_14AUG2010'
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'can show to number of business day until expiration' do
|
58
|
+
d = Date.today + 10
|
59
|
+
o = Option.new(Option::PUT, 'AAPL', 250, d.to_s)
|
60
|
+
o.days_to_exp.should < 10
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# require all files from lib
|
2
|
+
Dir["./lib/*.rb"].each {|file| require file }
|
3
|
+
|
4
|
+
describe YahooCrawler do
|
5
|
+
|
6
|
+
y = nil
|
7
|
+
|
8
|
+
before(:all) do
|
9
|
+
y = YahooCrawler.new('AAPL', '2013-01-18')
|
10
|
+
y.fetch
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should be able to fetch options quotes from Yahoo!Finance' do
|
14
|
+
|
15
|
+
y.call_options.length.should > 0
|
16
|
+
y.put_options.length.should > 0
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should be able to get current price of stock' do
|
21
|
+
|
22
|
+
y.curr_stock_price.should > 0
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should be able to return an array of strike prices' do
|
27
|
+
|
28
|
+
y.call_strikes.length.should > 0
|
29
|
+
y.put_strikes.length.should > 0
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should be able to give an option quote by strike price' do
|
34
|
+
|
35
|
+
# get a good strike price by rounding the current stock price
|
36
|
+
good_price = ((y.curr_stock_price / 100).to_i * 100).to_f
|
37
|
+
|
38
|
+
call_quote = y.call_options[good_price]
|
39
|
+
call_quote.option.internal_symbol.should == "AAPL_C#{good_price.to_i}_18JAN2013"
|
40
|
+
call_quote.bid.should > 0 if not call_quote.bid.nil?
|
41
|
+
call_quote.ask.should > 0 if not call_quote.ask.nil?
|
42
|
+
|
43
|
+
put_quote = y.put_options[good_price]
|
44
|
+
put_quote.option.internal_symbol.should == "AAPL_P#{good_price.to_i}_18JAN2013"
|
45
|
+
put_quote.bid.should > 0 if not put_quote.bid.nil?
|
46
|
+
put_quote.ask.should > 0 if not put_quote.ask.nil?
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'should be able to update the quotes in a background thread' do
|
51
|
+
|
52
|
+
y.auto_reload(1)
|
53
|
+
y.call_options.length.should > 0
|
54
|
+
y.put_options.length.should > 0
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: options-lib
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.9.2
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sergio Oliveira Jr.
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-02-24 00:00:00 -08:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rspec
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
type: :development
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mechanize
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: "0"
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
description: A set of classes for dealing with options. It includes a crawler for Yahoo!Finance.
|
39
|
+
email: sergio.oliveira.jr@gmail.com
|
40
|
+
executables:
|
41
|
+
- show_quotes
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files: []
|
45
|
+
|
46
|
+
files:
|
47
|
+
- README.markdown
|
48
|
+
- bin/show_quotes
|
49
|
+
- lib/options-lib.rb
|
50
|
+
- lib/options-lib/helpers.rb
|
51
|
+
- lib/options-lib/option.rb
|
52
|
+
- lib/options-lib/option_quote.rb
|
53
|
+
- lib/options-lib/yahoo_crawler.rb
|
54
|
+
- options-lib.gemspec
|
55
|
+
- spec/helpers_spec.rb
|
56
|
+
- spec/option_quote_spec.rb
|
57
|
+
- spec/option_spec.rb
|
58
|
+
- spec/yahoo_crawler_spec.rb
|
59
|
+
has_rdoc: true
|
60
|
+
homepage: https://github.com/saoj/options-lib
|
61
|
+
licenses: []
|
62
|
+
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options: []
|
65
|
+
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
requirements: []
|
81
|
+
|
82
|
+
rubyforge_project: nowarning
|
83
|
+
rubygems_version: 1.5.2
|
84
|
+
signing_key:
|
85
|
+
specification_version: 3
|
86
|
+
summary: Options Library
|
87
|
+
test_files:
|
88
|
+
- spec/helpers_spec.rb
|
89
|
+
- spec/option_quote_spec.rb
|
90
|
+
- spec/option_spec.rb
|
91
|
+
- spec/yahoo_crawler_spec.rb
|