R/wrappers.R

Defines functions get_optionspace cancel_all_orders cancel_order place_order get_balance get_orders get_schedule get_details get_info_prices get_instruments get_exchanges get_account_info get_client_info

Documented in cancel_all_orders cancel_order get_account_info get_balance get_client_info get_details get_exchanges get_info_prices get_instruments get_optionspace get_orders get_schedule place_order

#' Get client details
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#' @param ... parameters passed on to `response_df()`
#'
#' @details
#' See the [Open API Documentation](https://www.developer.saxo/openapi/referencedocs/port/v1/clients/getclient/1499e70934cb99a0c9e70d53f9ad8f7d)
#'
#' @return tibble
#'
#' @export
#'
get_client_info <- function(token, live = FALSE, ...) {

  if (live) {
    r <- httr::GET("https://gateway.saxobank.com/openapi/port/v1/clients/me",
             config = token)
  } else {
    r <- httr::GET("https://gateway.saxobank.com/sim/openapi/port/v1/clients/me",
                   httr::add_headers(Authorization = token)
    )
  }

  httr::stop_for_status(r)

  if (httr::http_type(r) != "application/json") {
    stop("API did not return json", call. = FALSE)
  }

  client <- response_df(r, ...)
  client
}


#' @title Get accounts for user
#' Returns all accounts under a particular client to which the logged in user belongs.
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#' @param ... parameters passed on to `response_df()`
#'
#' @details
#' See the [Open API Documentation](https://www.developer.saxo/openapi/referencedocs/port/v1/accounts/getaccounts/af56e3512758f8125dc6e5493d93c019)
#'
#' @return tibble
#' @export
#'
get_account_info <- function(token, live = FALSE, ...) {

  if (live) {
    r <- httr::GET("https://gateway.saxobank.com/openapi/port/v1/accounts/me",
             config = token)
  } else {
    r <- httr::GET("https://gateway.saxobank.com/sim/openapi/port/v1/accounts/me",
                   httr::add_headers(Authorization = token)
    )
  }

  account <- response_df(r, remove_constant_cols = FALSE)
  account
}


#' Get exchange names and data
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#'
#' @return tibble
#' @export
#'
get_exchanges <- function(token, live = FALSE) {

  if (live) {
    r <- httr::GET("https://gateway.saxobank.com/openapi/ref/v1/exchanges",
             config = token)
  } else {
    r <- httr::GET("https://gateway.saxobank.com/sim/openapi/ref/v1/exchanges",
                   httr::add_headers(Authorization = token)
    )
  }

  if (httr::http_type(r) != "application/json") {
    stop("API did not return json", call. = FALSE)
  }

  exchanges <- response_df(r)
  exchanges
}


#' Get stock names and identifiers
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#' @param exchange_id character, the Exchange to search, e.g. "CSE" for
#' Copenhagen Stock Exchange
#' @param asset_type character, type of asset, e.g. "Stock" or "StockOption"
#' @param ... parameters passed on to `response_df()`
#'
#' @return tibble
#' @export
#'
get_instruments <- function(token, live = FALSE, exchange_id = "CSE", asset_type = "Stock", ...) {

  if (live) {
    r <- httr::GET("https://gateway.saxobank.com/openapi/ref/v1/instruments",
                   query = list(ExchangeId = exchange_id,
                                AssetTypes = asset_type,
                                `$top` = 1000),
                   config = token)
  } else {
    r <- httr::GET("https://gateway.saxobank.com/sim/openapi/ref/v1/instruments",
                   query = list(ExchangeId = exchange_id,
                                AssetTypes = asset_type,
                                `$top` = 1000),
                   httr::add_headers(Authorization = token)
    )
  }

  httr::stop_for_status(r)

  if (httr::http_type(r) != "application/json") {
    stop("API did not return json", call. = FALSE)
  }

  stocks <- response_df(r, ...)
  stocks
}

#' Get info (non-tradeable) prices for list of instruments
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#' @param uics character, comma separated list of identifiers
#' @param asset_type character, type of asset, e.g. "Stock" or "StockOption"
#' @param amount numeric, defaults to 10000
#' @param ... parameters passed on to `response_df()`
#'
#' @return
#' tibble with instruments (e.g. stocks)
#' @export
#'
get_info_prices <- function(token, live = FALSE, uics, asset_type = "Stock", amount = 10000, ...) {

  if (live) {
    r <- httr::GET("https://gateway.saxobank.com/openapi/trade/v1/infoprices/list",
                   query = list(Uics = uics,
                                AssetType = asset_type,
                                Amount = amount,
                                FieldGroups = "DisplayAndFormat, Quote"),
                   config = token)
  } else {
    r <- httr::GET("https://gateway.saxobank.com/sim/openapi/trade/v1/infoprices/list",
                   query = list(Uics = uics,
                                AssetType = asset_type,
                                Amount = amount,
                                FieldGroups = "DisplayAndFormat, Quote"),
                   httr::add_headers(Authorization = token)
    )
  }

  prices <- response_df(r, ...)
  prices
}

#' Get detailed information for list of instruments, e.g. stocks
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#' @param uics character, comma separated list of identifiers
#' @param asset_type character, type of asset, e.g. "Stock" or "StockOption"
#' @param ... parameters passed on to `response_df()`
#'
#' @return
#' tibble with instruments (e.g. stocks)
#' @export
#'
get_details <- function(token, live = FALSE, uics, asset_type = "Stock", ...) {

  if (live) {
    r <- httr::GET("https://gateway.saxobank.com/openapi/ref/v1/instruments/details",
                   query = list(Uics = uics,
                                AssetType = asset_type),
                   config = token)
  } else {
    r <- httr::GET("https://gateway.saxobank.com/sim/openapi/ref/v1/instruments/details",
                   query = list(Uics = uics,
                                AssetType = asset_type),
                   httr::add_headers(Authorization = token)
    )
  }

  info <- response_df(r, ...)
  info
}


#' Get trading schedule for a given uic and asset type
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#' @param uic character, instrument identifier
#' @param asset_type character, type of asset, e.g. "Stock" or "StockOption"
#' @param ... parameters passed on to `response_df()`
#'
#' @return tibble with trading schedule
#' @export
#'
get_schedule <- function(token, live = FALSE, uic, asset_type = "Stock", ...) {

  if (live) {
    url <-
      paste(
        "https://gateway.saxobank.com/openapi/ref/v1/instruments/tradingschedule",
        uic,
        asset_type,
        sep = "/"
      )

    r <- httr::GET(url = url,
                   query = list(Uics = uic,
                                AssetType = asset_type),
                   config = token)
  } else {
    url <-
      paste(
        "https://gateway.saxobank.com/sim/openapi/ref/v1/instruments/tradingschedule",
        uic,
        asset_type,
        sep = "/"
      )

    r <- httr::GET(url = url,
                   query = list(Uics = uic,
                                AssetType = asset_type),
                   httr::add_headers(Authorization = token)
    )
  }

  httr::stop_for_status(r)

  schedule <- response_df(r, ...)
  schedule
}


#' Get active orders for logged-in client
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#' @param ... parameters passed on to `response_df()`
#'
#' @return tibble with active orders
#' @export
#'
get_orders <- function(token, live = FALSE, ...) {

  if (live) {
    r <- httr::GET("https://gateway.saxobank.com/openapi/port/v1/orders/me",
                   query = list("fieldGroups" = "DisplayAndFormat"),
                   config = token)
  } else {
    r <- httr::GET("https://gateway.saxobank.com/sim/openapi/port/v1/orders/me",
                   query = list("fieldGroups" = "DisplayAndFormat"),
                   httr::add_headers(Authorization = token)
    )
  }

  httr::stop_for_status(r)

  if (!purrr::is_empty(httr::content(r)$Data)) {
    orders <- response_df(r, ...)
  } else {
    orders <- NULL
  }

  orders
}

#' Get cash balance for logged-in client
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#'
#' @return numeric, cash balance
#'
get_balance <- function(token, live = FALSE) {
  if (live) {
    r <- httr::GET("https://gateway.saxobank.com/openapi/port/v1/balances/me",
             config = token)
  } else {
    r <- httr::GET("https://gateway.saxobank.com/sim/openapi/port/v1/balances/me",
                   httr::add_headers(Authorization = token)
                   )
  }

  httr::stop_for_status(r)

  balance <- httr::content(r)$CashBalance
  balance
}


#' Place a new order
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#' @param uic character, instrument identifier
#' @param buy_sell character, "Buy" or "Sell
#' @param asset_type character, type of asset to buy or sell, e.g. "Stock" (default)
#' @param amount numeric, amount to buy or sell, either by number of stocks
#' ("Quantity") or by value ("CashAmount") as specified by `amount_type`
#' @param amount_type character, either "CashAmount" or "Quantity" (default)
#' @param order_price numeric, the price of the order, optional for market orders, see
#' `order_type` parameter
#' @param order_type character, defaults to "Market", see link in Details for more
#' information
#' @param to_open_close Whether the order should be created to open/increase or
#' close/decrease a position. (Only relevant for options)
#' @param wait numeric, passed onto `sys.sleep()`, seconds to wait (in order not
#' to exceed rate limits)
#' @param ... parameters passed on to `response_df()`
#'
#' @return tibble
#'
#' @details
#' See the [Open API Documentation](https://www.developer.saxo/openapi/referencedocs/trade/v2/orders/placeorder/6cd02ecfc9130d34dc5c59fb182fc5b4)
#'
#' @export
#'
place_order <- function(token,
                        live = FALSE,
                        uic,
                        buy_sell,
                        asset_type = "Stock",
                        amount = 1,
                        amount_type = "Quantity",
                        order_price = 1,
                        order_type = "Market",
                        to_open_close = "ToOpen",
                        wait = 1,
                        ...) {

  body <- list(
    "Uic" = uic,
    "BuySell" = buy_sell,
    "AssetType" = asset_type,
    "Amount" = amount,
    "AmountType" = amount_type,
    "OrderPrice" = order_price,
    "OrderType" = order_type,
    "OrderRelation" = "StandAlone",
    "ManualOrder" = TRUE,
    "ToOpenClose" = to_open_close
  )

  if (live) {
    r <-
      httr::POST(
        "https://gateway.saxobank.com/openapi/trade/v2/orders",
        body = body,
        config = token,
        encode = "form"
      )
  } else {
    r <-
      httr::POST(
        "https://gateway.saxobank.com/sim/openapi/trade/v2/orders",
        body = body,
        config = httr::add_headers(Authorization = token),
        encode = "form"
      )
  }

  order <- response_df(r)
  Sys.sleep(wait) # to not exceed rate limits
  order
}


#' Cancel one or more orders
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#' @param account_key character
#' @param order_ids character, one or more order ids
#'
#' @return tibble
#'
#' @details
#' See the [Open API Documentation](https://www.developer.saxo/openapi/referencedocs/trade/v2/orders/cancelorder/a1fd2fffa62f21901f23318f65fe8147)
#'
#' @export
#'
cancel_order <- function(token, live = FALSE, account_key, order_ids) {

  if (live) {
    url <-
      paste0(
        "https://gateway.saxobank.com/openapi/trade/v2/orders/",
        order_ids,
        "/?",
        "AccountKey=",
        account_key
      )

    r <- httr::DELETE(
      url = url,
      config = token)

  } else {
    url <-
      paste0(
        "https://gateway.saxobank.com/sim/openapi/trade/v2/orders/",
        order_ids,
        "/?",
        "AccountKey=",
        account_key
      )

    r <- httr::DELETE(
      url = url,
      config = httr::add_headers(Authorization = token)
    )
  }

  httr::stop_for_status(r)

  cancel <- response_df(r)
  cancel
}


#' Cancel all orders
#' Calls `get_orders()` and `cancel_order()` to cancel *all* orders for an account
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#' @param account_key character
#' @param ... parameters passed on to `response_df()`
#'
#' @return tibble, containing list of cancelled order ids
#' @export
#'
cancel_all_orders <- function(token, live = FALSE, account_key, ...) {

  order_ids <- get_orders(token = token, live = live)

  if (is.null(order_ids)) return("No orders found")

  ids <- order_ids %>%
    dplyr::select(dplyr::ends_with("OrderId")) %>%
    dplyr::pull(1) %>%
    paste(collapse = ",")

  cancel_all <- cancel_order(token = token, live = live, account_key = account_key, order_ids = ids, ...)
  cancel_all
}


#' Get option space for a given root id
#'
#' Returns the `$SpecificOptions` element of the `$OptionSpace`
#' element of the response object from a call to `contractoptionspaces`
#' endpoint.
#'
#' @importFrom rlang .data
#' @importFrom tibble rowid_to_column
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#' @param client_key character
#' @param option_root_id numeric, id of the required option root
#'
#' @return tibble
#'
#' @details
#' See the [Open API Documentation](https://www.developer.saxo/openapi/referencedocs/ref/v1/instruments/getcontractoptionspace/9168dcd1c267be2c577576179daaa2fb)
#'
#' @export
#'
get_optionspace <- function(token, live = FALSE, client_key, option_root_id) {
  if (live) {
    url <-
      paste0(
        "https://gateway.saxobank.com/openapi/ref/v1/instruments/contractoptionspaces/",
        option_root_id
      )

    r <- httr::GET(url = url,
                   query = list(ClientKey = client_key, OptionSpaceSegment = "AllDates"),
                   config = token
                   )

  } else {
    url <-
      paste0(
        "https://gateway.saxobank.com/sim/openapi/ref/v1/instruments/contractoptionspaces/",
        option_root_id
      )

    r <- httr::GET(url = url,
                   query = list(ClientKey = client_key, OptionSpaceSegment = "AllDates"),
                   config = httr::add_headers(Authorization = token)
                   )

  }

  httr::stop_for_status(r)

  r_content <- r %>% httr::content()

  meta_data <- c(
    "ContractSize",
    "CurrencyCode",
    "Description",
    "ExerciseStyle",
    "IsTradable",
    "LotSize",
    "LotSizeType",
    "OptionRootId",
    "SettlementStyle",
    "Symbol",
    "UnderlyingAssetType"
  ) %>%
    purrr::set_names() %>%
    purrr::map_dfr( ~ pluck(r_content, .x))

  option_space <- r_content %>%
    purrr::pluck("OptionSpace")

  specific_options <- option_space %>%
    purrr::map(purrr::pluck, "SpecificOptions") %>%
    purrr::map(dplyr::bind_rows) %>%
    purrr::set_names(seq_along(.)) %>%
    dplyr::bind_rows(.id = "id") %>%
    dplyr::mutate(dplyr::across(.data$id, as.integer))

  expiry_dates <- option_space %>%
    purrr::map_dfr(`[`, c("Expiry", "DisplayDaysToLastTradeDate", "DisplayDaysToExpiry", "LastTradeDate")) %>%
    tibble::rowid_to_column("id") %>%
    dplyr::mutate(dplyr::across(.data$LastTradeDate, lubridate::as_datetime))

  option_df <- specific_options %>%
    dplyr::left_join(expiry_dates, by = "id") %>%
    dplyr::bind_cols(meta_data)

  option_df
  }


#' Create a price subscription on an instrument
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#' @param uic character, instrument identifier
#' @param asset_type character, type of asset, e.g. "Stock" or "StockOption"
#'
#' @return tibble

#' @details
#' See the [Open API Documentation](https://www.developer.saxo/openapi/referencedocs/trade/v1/prices/addsubscriptionasync/e1dbfa7d3e2ef801a7c4ade9e57f8812)
#'
#' @export
#'
make_subscription <- function(token, live = FALSE, uic, asset_type) {
  body <- list(
    "Arguments" = list(
      "Uic" = uic,
      "AssetType" = asset_type,
      "FieldGroups" = list(
        "Commissions",
        "PriceInfo",
        "PriceInfoDetails",
        "Quote",
        "Greeks"
      )
    ),
    "ContextId" = random_id(),
    "ReferenceId" = random_id()
  )

  if (live) {
    r <-
      httr::POST(
        "https://gateway.saxobank.com/openapi/trade/v1/prices/subscriptions",
        body = body,
        config = token,
        encode = "json"
      )
  } else {
    r <-
      httr::POST(
        "https://gateway.saxobank.com/sim/openapi/trade/v1/prices/subscriptions",
        body = body,
        config = httr::add_headers(Authorization = token),
        encode = "json"
      )
  }

  prices <- r %>% response_df()
  prices
}


#' Reset simulation account balance
#'
#' Reset simulation account with a new cash balance. This will also remove
#' all orders and positions associated with the account. Cannot be used in
#' live environment.
#'
#'
#' @param token character (containing a '24 hour token')
#' @param client_key character
#' @param new_balance numeric, new balance of simulation account. Between 0
#' and 10.000.000; defaults to 1.000.000
#'
#' @return tibble
#'
#' @details
#' See the [Open API Documentation](https://www.developer.saxo/openapi/referencedocs/port/v1/accounts/resetaccount/3b6e0d7c0ba729645f75e4f217390b5c)
#'
#' @export
#'
reset_sim_balance <- function(token, client_key, new_balance = 1000000) {
    url <- paste0("https://gateway.saxobank.com/sim/openapi/port/v1/accounts/", client_key, "/reset")
    body <- list("NewBalance" = new_balance)

    r <- httr::PUT(url = url,
                    body = body,
                    config = httr::add_headers(Authorization = token),
                    encode = "json"
                    )

  httr::stop_for_status(r)
  httr::http_status(r)$message
}


#' Get positions for the logged-in client
#'
#' Get all positions i.e. assets in users portfolio
#'
#' @param token either a character or a token2.0 reference class (RC) object
#' as returned by `httr::oauth2.0_token()`. Sim environment uses character,
#' (a '24 hour token'); live environment a token object.
#' @param live boolean, TRUE for live environment, i.e. real money.
#' Defaults to FALSE, i.e. simulation environment.
#'
#' @return tibble

#' @details
#' See the [Open API Documentation](https://www.developer.saxo/openapi/referencedocs/port/v1/positions/getpositions/5b51b5d67e4ab74321d111719a9457e2)
#'
#' @export
#'
get_positions <- function(token, live = FALSE) {

  if (live) {
    url <-
      paste0(
        "https://gateway.saxobank.com/openapi/port/v1/positions/me/"
      )

    r <- httr::GET(url = url,
                   query = list(FieldGroups = "DisplayAndFormat, PositionBase, PositionView",
                                PriceMode = "RegularTradingHours"),
                   config = token
    )

  } else {
    url <-
      paste0(
        "https://gateway.saxobank.com/sim/openapi/port/v1/positions/me/"
      )

    r <- httr::GET(url = url,
                   query = list(FieldGroups = "DisplayAndFormat, PositionBase, PositionView",
                                PriceMode = "RegularTradingHours"),
                   config = httr::add_headers(Authorization = token)
    )

  }

  positions <- r %>% response_df()
  positions
}
lassehjorthmadsen/sherwood documentation built on Sept. 6, 2022, 3:47 p.m.