-
-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add historical prices api endpoint #94
Add historical prices api endpoint #94
Conversation
ec45e49
to
a8aa3d1
Compare
Looks good, will merge on green. |
def historical_prices(symbol, options = {}) | ||
slug = "stock/#{symbol}/chart" | ||
slug += "/#{options[:range]}" if options[:range] | ||
slug += "/#{options[:date]}" if options[:range] == 'date' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will be wrong if options[:date]
is missing. I would add a check to raise ArgumentError
on a missing date when range is date or something like that. Make sure to add specs for the combinations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Technically this will just return an empty array but that's pretty useless so I'll add the argument error.
Good call about the specs, pretty sloppy of me. While adding specs I noticed that you can actually receive a different payload if you ask for a specific date and don't include additional query params (ex: chartByDay: true). This will take a bit of time to investigate how to best solve so It'll be a bit before this PR is ready for review again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't let this block you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good. Basically you can get 2 types of data from the IEX endpoint, per day or per min data. The current implementation only supports the per day format.
|
||
# We only want options to include query params at this point, remove :range and :date | ||
options.delete(:range) | ||
options.delete(:date) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're modifying the hash passed in, so either options = options.dup
, or use .except
or similar to get a copy without those keys. Let's make sure there are no other places where we did this, I may have missed that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't see any downside to modifying it but happy to dup it if you prefer. This was the only place I did this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The downside is that code like this doesn't do what you'd expect it to do:
options = { range: ... }
prices_for_msft = historical_prices('msft', options)
prices_for_goog = historical_prices('goog', options)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also use options.key?(:range)
, nil
is a value and while it doesn't make sense you don't want the caller to rely on you clearing the option, this is more by convention
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup that's a downside. Thanks
module Endpoints | ||
module HistoricalPrices | ||
def historical_prices(symbol, options = {}) | ||
slug = "stock/#{symbol}/chart" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a path
rather than a slug
, let's call it path
.
@@ -14,6 +14,18 @@ def self.float_to_percentage(float_number) | |||
].join | |||
end | |||
|
|||
# Useful for values that are already a percent but we want to convert into a 2 decimal place string | |||
def self.percentage_to_string(float_percent) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is essentially a direct copy of float_to_percentage
but the the values being returned from IEX for historical prices were already a percent just in float format, so the string representation ended up being an incorrect value. Happy to try and combine these two methods with an optional parameter or something different? Don't love the copy paste nature of this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think copy paste is fine I wouldn't worry about it.
I think I've addressed all your concerns @dblock, please let me know if there are more. Also not sure why the travis-ci build failed, I don't see anything in the details page. |
The build is failing because of this ^, fix the TOC. |
README.md
Outdated
@@ -15,6 +15,7 @@ A Ruby client for the [The IEX Cloud API](https://iexcloud.io/docs/api/). | |||
- [Get a Quote](#get-a-quote) | |||
- [Get a OHLC (Open, High, Low, Close) price](#get-a-ohlc-open-high-low-close-price) | |||
- [Get a Market OHLC (Open, High, Low, Close) prices](#get-a-market-ohlc-open-high-low-close-prices) | |||
- [Get Historical Prices](#get-historial-prices) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a typo in historical, missing c
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh I expected Danger to re-post an error after the initial one where I fixed the header but not the tag.
module HistoricalPrices | ||
def historical_prices(symbol, options = {}) | ||
if options[:range] == 'date' | ||
raise ArgumentError if options[:date].nil? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unless options[...]
feels cleaner
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I prefer the if
route but happy to change.
|
||
options = options.dup | ||
path = "stock/#{symbol}/chart" | ||
path += "/#{options[:range]}" if options[:range] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if options.key?(:range)
, which is faster
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad, misunderstood your previous comment.
path = "stock/#{symbol}/chart" | ||
path += "/#{options[:range]}" if options[:range] | ||
# IEX needs dates passed without any formatting, trim out common symbols if passed in. | ||
path += "/#{options[:date].tr('-/', '')}" if options[:range] == 'date' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels wrong. IEX expects a date in a well defined format, not one without dashes or slashes, and it's not for the client to be mangling input. It's a slippery slope when IEX adds support for -5d
(last 5 days) or something like that and we have to deprecate magical behavior. So either leave the input as is, and document what's required, and/or add support for passing in a DateTime and convert it into the right kind of string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So i did this because typing in a date as 20201101
is a lot more difficult as a user of the library than typing in 2020-11-01
or 2020/11/01
as most humans would type a date. I like the DateTime approach more through so I'll change that and the documentation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, any specific reason for DateTime vs Date? This endpoint has no concept of time in its current implementation.
… and readme accordingly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm happy to merge as is, but consider these two suggestions?
rubocop -a
will fix the build
|
||
it 'retrieves historical prices' do | ||
expect(subject.size).to eq 23 | ||
expect(historical_price.date).to eq '2020-10-07' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be converted to a Date
as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah.... I think it should be however, all the other endpoints that return dates are also strings. I say lets leave this as is and I'll fix all of them up in a follow up PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this, would you prefer two fields, one for the raw data and one for the converted data? Similar to how dollar values are handled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dollar values are different from dates since those are integers in cents, and formatted values in dollars. I think this should just be a Date
type. NBD though, I'm going to merge as is and feel free to change it on top before the next release, double-checking that we do it everywhere a date is returned in other instances.
def historical_prices(symbol, options = {}) | ||
if options[:range] == 'date' | ||
raise ArgumentError unless options[:date].present? | ||
raise ArgumentError, 'Date param must be a Date object' unless options[:date].class == Date |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.is_a?(Date)
is the proper way to do this, for one it can inherit from Date
I think you want to support non-Date input and pass it through in that case. So I would just drop this case. When passing Date
, call strftime
, otherwise pass through the value as is.
options = options.dup
options[:date] = options[:date].strftime('...') if options[:date].is_a?(Date)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point about is_a
thanks.
Fair enough with passing the date through as a string. If the string date is incorrect then the call with simply error out. 👍
Ahh keep forgetting about rubocop. Apologies. |
No description provided.