Skip to content

Commit 1ce854c

Browse files
authored
Merge pull request #937 from ruby-concurrency/fix-examples-that-uses-yahoo
Switched from Yahoo stock API to Alpha Vantage
2 parents 0fe5618 + 3270635 commit 1ce854c

File tree

6 files changed

+207
-76
lines changed

6 files changed

+207
-76
lines changed

docs-source/dataflow_top_stock_calc.md

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,39 +5,64 @@ It as an example from chapter 1 "Introduction", section 1.2 "What's Scala?" of t
55
## What It Does
66

77
This program takes a list of one or more stock symbols and a year. It then concurrently
8-
obtains the relevant stock data from Yahoo's iChart service for each symbol. Once all
8+
obtains the relevant stock data from Alpha Vantage service for each symbol. Once all
99
the data has been retrieved the program determines which stock had the highest year-end
1010
closing price.
1111

12-
* A sample of the data can be downloaded [here](http://ichart.finance.yahoo.com/table.csv?s=AAPL&a=11&b=01&c=2008&d=11&e=31&f=2008&g=m).
12+
To use this example you need to obtain a free api key in [AlphaVantage](https://www.alphavantage.co/support/#api-key).
13+
1314

1415
#### The Ruby Code
1516

1617
```ruby
1718
require 'concurrent'
19+
require 'csv'
1820
require 'open-uri'
1921

20-
def get_year_end_closing(symbol, year)
21-
uri = "http://ichart.finance.yahoo.com/table.csv?s=#{symbol}&a=11&b=01&c=#{year}&d=11&e=31&f=#{year}&g=m"
22-
data = open(uri) {|f| f.collect{|line| line.strip } }
23-
price = data[1].split(',')[4]
24-
price.to_f
25-
[symbol, price.to_f]
22+
def get_year_end_closing(symbol, year, api_key)
23+
uri = "https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=#{symbol}&apikey=#{api_key}&datatype=csv"
24+
data = []
25+
csv = URI.parse(uri).read
26+
if csv.include?('call frequency')
27+
return :rate_limit_exceeded
28+
end
29+
CSV.parse(csv, headers: true) do |row|
30+
data << row['close'].to_f if row['timestamp'].include?(year.to_s)
31+
end
32+
price = data.max
33+
[symbol, price]
2634
end
2735

28-
def get_top_stock(symbols, year, timeout = 5)
29-
stock_prices = symbols.collect{|symbol| Concurrent::dataflow{ get_year_end_closing(symbol, year) }}
36+
def get_top_stock(symbols, year, timeout = 10)
37+
api_key = ENV['ALPHAVANTAGE_KEY']
38+
abort(error_message) unless api_key
39+
40+
stock_prices = symbols.collect{|symbol| Concurrent::dataflow{ get_year_end_closing(symbol, year, api_key) }}
3041
Concurrent::dataflow(*stock_prices) { |*prices|
42+
next :rate_limit_exceeded if prices.include?(:rate_limit_exceeded)
3143
prices.reduce(['', 0.0]){|highest, price| price.last > highest.last ? price : highest}
3244
}.value(timeout)
3345
end
3446

47+
def error_message
48+
<<~EOF
49+
PLEASE provide a Alpha Vantage api key for the example to work
50+
usage:
51+
ALPHAVANTAGE_KEY=YOUR_API_KEY bundle exec ruby top-stock-scala/top-stock.rb
52+
EOF
53+
end
54+
3555
symbols = ['AAPL', 'GOOG', 'IBM', 'ORCL', 'MSFT']
36-
year = 2008
56+
year = 2018
3757

38-
top_stock, highest_price = get_top_stock(symbols, year)
58+
result = get_top_stock(symbols, year)
3959

40-
puts "Top stock of #{year} is #{top_stock} closing at price $#{highest_price}"
60+
if result == :rate_limit_exceeded
61+
puts "API rate limit exceeded"
62+
else
63+
top_stock, highest_price = result
64+
puts "Top stock of #{year} is #{top_stock} closing at price $#{highest_price}"
65+
end
4166
```
4267

4368
#### The Scala Code
@@ -60,16 +85,16 @@ val (topStock, highestPrice) = getTopStock(symbols.length)
6085
printf("Top stock of %d is %s closing at price %f\n", year, topStock, highestPrice)
6186
//END:PART1
6287

63-
//START:PART2
88+
//START:PART2
6489
def getYearEndClosing(symbol : String, year : Int) = {
6590
val url = "http://ichart.finance.yahoo.com/table.csv?s=" +
6691
symbol + "&a=11&b=01&c=" + year + "&d=11&e=31&f=" + year + "&g=m"
67-
92+
6893
val data = io.Source.fromURL(url).mkString
69-
val price = data.split("\n")(1).split(",")(4).toDouble
94+
val price = data.split("\n")(1).split(",")(4).toDouble
7095
(symbol, price)
71-
}
72-
//END:PART2
96+
}
97+
//END:PART2
7398

7499
//START:PART3
75100
def getTopStock(count : Int) : (String, Double) = {
@@ -79,6 +104,6 @@ def getTopStock(count : Int) : (String, Double) = {
79104
if (price > previousHigh._2) (symbol, price) else previousHigh
80105
}
81106
}
82-
}
107+
}
83108
//START:PART3
84109
```

docs-source/future.md

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,29 +21,42 @@ A fulfilled example:
2121

2222
```ruby
2323
require 'concurrent'
24-
require 'thread' # for Queue
25-
require 'open-uri' # for open(uri)
24+
require 'csv'
25+
require 'open-uri'
2626

2727
class Ticker
28-
def get_year_end_closing(symbol, year)
29-
uri = "http://ichart.finance.yahoo.com/table.csv?s=#{symbol}&a=11&b=01&c=#{year}&d=11&e=31&f=#{year}&g=m"
30-
data = open(uri) {|f| f.collect{|line| line.strip } }
31-
data[1].split(',')[4].to_f
28+
def get_year_end_closing(symbol, year, api_key)
29+
uri = "https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=#{symbol}&apikey=#{api_key}&datatype=csv"
30+
data = []
31+
csv = URI.parse(uri).read
32+
if csv.include?('call frequency')
33+
return :rate_limit_exceeded
34+
end
35+
CSV.parse(csv, headers: true) do |row|
36+
data << row['close'].to_f if row['timestamp'].include?(year.to_s)
37+
end
38+
year_end = data.first
39+
year_end
40+
rescue => e
41+
p e
3242
end
3343
end
3444

45+
api_key = ENV['ALPHAVANTAGE_KEY']
46+
abort(error_message) unless api_key
47+
3548
# Future
36-
price = Concurrent::Future.execute{ Ticker.new.get_year_end_closing('TWTR', 2013) }
37-
price.state #=> :pending
38-
price.pending? #=> true
39-
price.value(0) #=> nil (does not block)
49+
price = Concurrent::Future.execute{ Ticker.new.get_year_end_closing('TWTR', 2013, api_key) }
50+
p price.state #=> :pending
51+
p price.pending? #=> true
52+
p price.value(0) #=> nil (does not block)
4053

4154
sleep(1) # do other stuff
4255

43-
price.value #=> 63.65 (after blocking if necessary)
44-
price.state #=> :fulfilled
45-
price.fulfilled? #=> true
46-
price.value #=> 63.65
56+
p price.value #=> 63.65 (after blocking if necessary)
57+
p price.state #=> :fulfilled
58+
p price.fulfilled? #=> true
59+
p price.value #=> 63.65
4760
```
4861

4962

docs-source/future.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
require 'concurrent'
2+
require 'csv'
3+
require 'open-uri'
4+
5+
class Ticker
6+
def get_year_end_closing(symbol, year, api_key)
7+
uri = "https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=#{symbol}&apikey=#{api_key}&datatype=csv"
8+
data = []
9+
csv = URI.parse(uri).read
10+
if csv.include?('call frequency')
11+
return :rate_limit_exceeded
12+
end
13+
CSV.parse(csv, headers: true) do |row|
14+
data << row['close'].to_f if row['timestamp'].include?(year.to_s)
15+
end
16+
year_end = data.first
17+
year_end
18+
rescue => e
19+
p e
20+
end
21+
end
22+
23+
api_key = ENV['ALPHAVANTAGE_KEY']
24+
abort(error_message) unless api_key
25+
26+
# Future
27+
price = Concurrent::Future.execute{ Ticker.new.get_year_end_closing('TWTR', 2013, api_key) }
28+
p price.state #=> :pending
29+
p price.pending? #=> true
30+
p price.value(0) #=> nil (does not block)
31+
32+
sleep(1) # do other stuff
33+
34+
p price.value #=> 63.65 (after blocking if necessary)
35+
p price.state #=> :fulfilled
36+
p price.fulfilled? #=> true
37+
p price.value #=> 63.65

docs-source/top-stock-scala/README.md

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,43 +7,62 @@ It as an example from chapter 1 "Introduction", section 1.2 "What's Scala?" of t
77
## What It Does
88

99
This program takes a list of one or more stock symbols and a year. It then concurrently
10-
obtains the relevant stock data from Yahoo's iChart service for each symbol. Once all
10+
obtains the relevant stock data from Alpha Vantage service for each symbol. Once all
1111
the data has been retrieved the program determines which stock had the highest year-end
1212
closing price.
1313

14-
http://ichart.finance.yahoo.com/table.csv?s=AAPL&a=11&b=01&c=2008&d=11&e=31&f=2008&g=m
14+
To use this example you need to obtain a free api key in [AlphaVantage](https://www.alphavantage.co/support/#api-key).
15+
16+
https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=AAPL&apikey=1234567&datatype=csv"
1517

1618
### Run It
1719

1820
This example can be run from the console. From the root of the repo run:
1921

20-
> $ bundle exec ruby top-stock-scala/top-stock.rb
22+
> $ ALPHAVANTAGE_KEY=YOUR_API_KEY bundle exec ruby top-stock-scala/top-stock.rb
2123

2224
The output should be:
2325

24-
> Top stock of 2008 is GOOG closing at price $307.65
26+
> Top stock of 2008 is GOOG closing at price $307.65
2527

2628
#### The Ruby Code
2729

2830
```ruby
2931
require 'concurrent'
32+
require 'csv'
3033
require 'open-uri'
3134

32-
def get_year_end_closing(symbol, year)
33-
uri = "http://ichart.finance.yahoo.com/table.csv?s=#{symbol}&a=11&b=01&c=#{year}&d=11&e=31&f=#{year}&g=m"
34-
data = open(uri) {|f| f.collect{|line| line.strip } }
35-
price = data[1].split(',')[4]
35+
def get_year_end_closing(symbol, year, api_key)
36+
uri = "https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=#{symbol}&apikey=#{api_key}&datatype=csv"
37+
data = []
38+
URI.open(uri) do |f|
39+
CSV.parse(f, headers: true) do |row|
40+
data << row['close'] if row['timestamp'].include?(year.to_s)
41+
end
42+
end
43+
price = data.max
3644
price.to_f
3745
[symbol, price.to_f]
3846
end
3947

40-
def get_top_stock(symbols, year, timeout = 5)
41-
stock_prices = symbols.collect{|symbol| Concurrent::dataflow{ get_year_end_closing(symbol, year) }}
48+
def get_top_stock(symbols, year, timeout = 10)
49+
api_key = ENV['ALPHAVANTAGE_KEY']
50+
abort(error_message) unless api_key
51+
52+
stock_prices = symbols.collect{|symbol| Concurrent::dataflow{ get_year_end_closing(symbol, year, api_key) }}
4253
Concurrent::dataflow(*stock_prices) { |*prices|
4354
prices.reduce(['', 0.0]){|highest, price| price.last > highest.last ? price : highest}
4455
}.value(timeout)
4556
end
4657

58+
def error_message
59+
<<~EOF
60+
PLEASE provide a Alpha Vantage api key for the example to work
61+
usage:
62+
ALPHAVANTAGE_KEY=YOUR_API_KEY bundle exec ruby top-stock-scala/top-stock.rb
63+
EOF
64+
end
65+
4766
symbols = ['AAPL', 'GOOG', 'IBM', 'ORCL', 'MSFT']
4867
year = 2008
4968

@@ -72,16 +91,16 @@ val (topStock, highestPrice) = getTopStock(symbols.length)
7291
printf("Top stock of %d is %s closing at price %f\n", year, topStock, highestPrice)
7392
//END:PART1
7493

75-
//START:PART2
94+
//START:PART2
7695
def getYearEndClosing(symbol : String, year : Int) = {
7796
val url = "http://ichart.finance.yahoo.com/table.csv?s=" +
7897
symbol + "&a=11&b=01&c=" + year + "&d=11&e=31&f=" + year + "&g=m"
79-
98+
8099
val data = io.Source.fromURL(url).mkString
81-
val price = data.split("\n")(1).split(",")(4).toDouble
100+
val price = data.split("\n")(1).split(",")(4).toDouble
82101
(symbol, price)
83-
}
84-
//END:PART2
102+
}
103+
//END:PART2
85104

86105
//START:PART3
87106
def getTopStock(count : Int) : (String, Double) = {
@@ -91,6 +110,6 @@ def getTopStock(count : Int) : (String, Double) = {
91110
if (price > previousHigh._2) (symbol, price) else previousHigh
92111
}
93112
}
94-
}
113+
}
95114
//END:PART3
96115
```
Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,48 @@
11
require 'concurrent'
2+
require 'csv'
23
require 'open-uri'
34

4-
def get_year_end_closing(symbol, year)
5-
uri = "http://ichart.finance.yahoo.com/table.csv?s=#{symbol}&a=11&b=01&c=#{year}&d=11&e=31&f=#{year}&g=m"
6-
data = open(uri) {|f| f.collect{|line| line.strip } }
7-
price = data[1].split(',')[4]
8-
price.to_f
9-
[symbol, price.to_f]
5+
def get_year_end_closing(symbol, year, api_key)
6+
uri = "https://www.alphavantage.co/query?function=TIME_SERIES_MONTHLY&symbol=#{symbol}&apikey=#{api_key}&datatype=csv"
7+
data = []
8+
csv = URI.parse(uri).read
9+
if csv.include?('call frequency')
10+
return :rate_limit_exceeded
11+
end
12+
CSV.parse(csv, headers: true) do |row|
13+
data << row['close'].to_f if row['timestamp'].include?(year.to_s)
14+
end
15+
price = data.max
16+
[symbol, price]
1017
end
1118

12-
def get_top_stock(symbols, year, timeout = 5)
13-
stock_prices = symbols.collect{|symbol| Concurrent::dataflow{ get_year_end_closing(symbol, year) }}
19+
def get_top_stock(symbols, year, timeout = 10)
20+
api_key = ENV['ALPHAVANTAGE_KEY']
21+
abort(error_message) unless api_key
22+
23+
stock_prices = symbols.collect{|symbol| Concurrent::dataflow{ get_year_end_closing(symbol, year, api_key) }}
1424
Concurrent::dataflow(*stock_prices) { |*prices|
25+
next :rate_limit_exceeded if prices.include?(:rate_limit_exceeded)
1526
prices.reduce(['', 0.0]){|highest, price| price.last > highest.last ? price : highest}
1627
}.value(timeout)
1728
end
1829

30+
def error_message
31+
<<~EOF
32+
PLEASE provide a Alpha Vantage api key for the example to work
33+
usage:
34+
ALPHAVANTAGE_KEY=YOUR_API_KEY bundle exec ruby top-stock-scala/top-stock.rb
35+
EOF
36+
end
37+
1938
symbols = ['AAPL', 'GOOG', 'IBM', 'ORCL', 'MSFT']
20-
year = 2008
39+
year = 2018
2140

22-
top_stock, highest_price = get_top_stock(symbols, year)
41+
result = get_top_stock(symbols, year)
2342

24-
puts "Top stock of #{year} is #{top_stock} closing at price $#{highest_price}"
43+
if result == :rate_limit_exceeded
44+
puts "API rate limit exceeded"
45+
else
46+
top_stock, highest_price = result
47+
puts "Top stock of #{year} is #{top_stock} closing at price $#{highest_price}"
48+
end

0 commit comments

Comments
 (0)