JestonBlu / RobinHood

An R interface for the RobinHood.com no commision investing site
https://jestonblu.github.io/RobinHood/
GNU Lesser General Public License v3.0
45 stars 12 forks source link

Error in get_historicals_options #160

Open industrial-dreaming opened 2 years ago

industrial-dreaming commented 2 years ago

Getting the following error when I run the example code for get_historicals_options:

Error in parse_url(url) : length(url) == 1 is not TRUE

Perhaps related to the changes behind the HTTP 400 Bad Request issues?

Thank you for your time!

industrial-dreaming commented 2 years ago

Did some of my own investigation and it looks like the starting point for the error takes place after running the following, with the resulting output:

rawToChar(dta$content) [1] "{"detail":"Invalid Authorization header."}"

I'm new to this, but it looks like the structure of dta$content has changed?

JestonBlu commented 2 years ago

Thanks for letting me know, this is one i forgot to test. The api seems okay, but something did change. The error is from the api_instruments_options function returning more than one url. The function is setup to expect 1 url I think. Ill investigate more and work on a patch.

api_historicals_options <- function(RH, chain_symbol, type, strike_price, expiration_date,
                                    interval = NULL, span = NULL) {

  # Call to get the option instrument id
  dta <- api_instruments_options(RH, method = "symbol",
                                 chain_symbol = chain_symbol,
                                 type = type,
                                 strike_price = strike_price,
                                 expiration_date = expiration_date)
...
Colleca commented 1 year ago

I suggest a few changes:

  1. Force results to only active and tradeable contracts. Pulling price history for inactive/untradeable (aka "dead") contracts doesn't make much sense because the current price on a dead contract will always be the same.
  2. Expiration date filters dont seem to do anything within the api_instruments_options call. So, instead i suggest dropping it from that function and adding the filter step into the api_historicals_options call if its specified.
  3. I was able to address the multiple url issue by adding a for loop to the function, i also added try catch logic, not sure if absolutely necessary but considered that a robinhood url might possibly not result in data.
  4. When a user does not specify type, this pulls both puts and calls.
  5. When a user doesnt specify strike_price, this pulls all strike prices (except see number 7 below)
  6. When a user doesnt specify expiration date, this pulls all expiration dates.
  7. Added "Near the money" option which will look up the latest price for the stock and pull options with strikes that are within a set % of the last price (set this to 10% by default, but is adjustable).
  8. Added verbosity to allow user to see that the function is working when pulling more than 1 ticker. For example when I was pulling all apple option contracts I got 1,918 hits so it took a while for it to pull them all.

Hope this helps, below worked with the minimal testing I did against it.

library(httr) library(RobinHood) library(otp) library(tictoc) library(tidyverse)

RH <- RobinHood(your account stuff)

For testing the function

chain_symbol<-"AAPL" type<-"call" strike_price <- 100 expiration_date_param<-"2023-06-09" span = "week" interval = "10minute" near_the_money = TRUE near_the_money_pct=0.1

alternate set

type<-NULL strike_price<-NULL expiration_date_param<-NULL

api_historicals_options_v2<-function (RH, chain_symbol, type=NULL, strike_price=NULL,expiration_date_param = expiration_date_param, near_the_money=near_the_money,near_the_money_pct=near_the_money_pct, interval = NULL, span = NULL,verbose=verbose) {

dta <- api_instruments_options(RH, method = "symbol", chain_symbol = chain_symbol, type = type, strike_price = strike_price, state="active",tradability="tradable")

if(!is.null(expiration_date_param)){ dta<-dta%>% dplyr::filter(expiration_date==expiration_date_param) }

If don't want to set strike price but only want near the money options

if(isTRUE(near_the_money) && is.null(strike_price)){ latest_price<-try(get_quote(RH,chain_symbol)$last_trade_price) if(inherits(latest_price,'try-error')){ print("Unable to retrieve latest price for ticker, check ticker.") break }

dta<-dta%>% dplyr::filter(strike_price >= (1-near_the_money_pct)latest_price)%>% dplyr::filter(strike_price <= (1+near_the_money_pct)latest_price) }

option_id <- dta$id url <- paste0(api_endpoints("historicals_options"), dta$id, "/?interval=", interval, "&span=", span)

token <- paste("Bearer", RH$api_response.access_token)

loop_pull<-function(){ if(i > 1 && verbose==TRUE){ print(paste0("Pulling Option Data ",i," out of ",length(url))) } dta_loop <- GET(url[i], add_headers(Accept = "application/json", Content-Type = "application/json", Authorization = token)) httr::stop_for_status(dta_loop) dta_loop <- RobinHood::mod_json(dta_loop, "fromJSON") dta_loop <- dta_loop$data_points dta_loop <- dta_loop %>% dplyr::mutate_at("begins_at", lubridate::ymd_hms) %>% dplyr::mutate_at(c("open_price", "close_price", "high_price", "low_price", "volume"), as.numeric)%>% dplyr::mutate(id = option_id[i])%>% tidyr::drop_na()%>% dplyr::full_join(dta,by = join_by(id))

return(dta_loop)

}

full_data<-data.frame() i=1 for(i in 1:length(url)){ dta_loop2<-try(loop_pull()) if(inherits(dta_loop2,'try-error')){ next } full_data<-dplyr::bind_rows(full_data,dta_loop2) } return(full_data) }

get_historicals_options_v2<-function (RH, chain_symbol, type=NULL, strike_price=NULL, expiration_date_param = NULL, interval = NULL, span = NULL,near_the_money=FALSE,near_the_money_pct=0.1,verbose=FALSE) { RobinHood::check_rh(RH)

historicals <- api_historicals_options_v2(RH, chain_symbol = chain_symbol, type = type, strike_price = strike_price, interval = interval, span = span,near_the_money = near_the_money,near_the_money_pct = near_the_money_pct,verbose=TRUE,expiration_date_param = expiration_date_param)

historicals$strike_price <- strike_price

historicals$expiration_date <- expiration_date

historicals$type <- type

historicals$chain_symbol <- chain_symbol

if(!is.null(expiration_date)){ historicals<-historicals%>% dplyr::filter(expiration_date==lubridate::ymd(expiration_date)) }

historicals <- historicals %>% dplyr::select("chain_symbol", "type", "expiration_date", "strike_price", "open_price", "close_price", "low_price", "high_price", "volume", "begins_at", "session", "interpolated")%>% dplyr::relocate(begins_at) return(historicals) }

old_function<-get_historicals_options(RH,chain_symbol = "AAPL",type = "call",strike_price = 100,expiration_date = "2023-06-09",interval = "10minute",span = "day")

For single option selection

tic() new_function<-get_historicals_options_v2(RH,chain_symbol = "AAPL",type = "call",strike_price = 100,expiration_date_param = "2023-06-09",interval = "10minute",span = "day") toc()

For wildcard option selection using near the money option.

tic() new_function<-get_historicals_options_v2(RH,chain_symbol = "AAPL",interval = "10minute",span = "day",near_the_money = TRUE,near_the_money_pct = 0.1) toc()

Colleca commented 1 year ago

the markdown is making my comment look funny just copy and paste it into R studio and it looks fine.