1 Overview

The following code defines, documents and implements a series functions for forex trading with Oanda through a REST API, more about Oanda can be read at https://www.oanda.com. The functions were originally sourced from http://bit.ly/GitHubROandaAPI and modified so that they are compatible with the v20 version. The development guide is available at http://developer.oanda.com/rest-live-v20/introduction/.

1.1 Getting started

An account with OANDA, either Live or Practice, is required to access the API. Once an account has been acquired:

  1. Get the Account ID: Go to the trading Dashboard and locate the account number, it takes the form 100-001-1234567-001
  2. Get the Access Token: Click on Manage API Access and Generate Personal Access Token - it takes the form 123456789abcdef0-123456789abcdef0
  3. Save info safely: Save the Account ID and Access Token into the R environment to avoid inputting it directly into scripts. For example, Sys.setenv(OANDA_accountID = "your account id", OANDA_ACCESS_TOKEN = "your access token")

2 Functions

Load required packages

# -- ------------------------------------------------------------------------------ -- #
# -- Load required packages ------------------------------------------------------- -- #
# -- ------------------------------------------------------------------------------ -- #

Pkg <- c("RCurl", "jsonlite", "magrittr", "kableExtra", "DT", "httr")

inst <- Pkg %in% installed.packages()
if(length(Pkg[!inst]) > 0) install.packages(Pkg[!inst])
instpackages <- lapply(Pkg, library, character.only=TRUE)

2.1 List of accounts

Defining and documenting the function

#' Retrieves all accounts
#' 
#' @param accountType string. Specify either "trade" or "practice"
#' @param username string. Specify the Oanda useraname
#' @param token string. Specify the Oanda access token. The default is `Sys.getenv("OANDA_ACCESS_TOKEN")`
#' @examples
#' ListAccounts(accountType = "practice", username = "myusername")
ListAccounts <- function(accountType,
                        username, token = Sys.getenv("OANDA_ACCESS_TOKEN")) {
  Url <- paste0("https://api-fx", switch(accountType, practice = "practice", live = "trade"),
                ".oanda.com/v3/accounts?username=", username)
  Auth      <- c(Authorization = paste("Bearer", token, sep=" "))
  QueryInst1  <- RCurl::getURL(Url, cainfo=system.file("CurlSSL", "cacert.pem", package="RCurl"), httpheader = Auth)
  InstJson <- jsonlite::fromJSON(QueryInst1, simplifyDataFrame = TRUE)
  return(InstJson)
}

Running an example

out <- ListAccounts(accountType = "practice", username = Sys.getenv("OANDA_USERNAME"))
df <- data.frame(out$accounts)
# masking account IDs
df$id <- sapply(df$id, function(x) paste0("xxx-xxx-xxxxxxxx", substr(x, 17, 20)))
df$mt4AccountID <- sapply(df$mt4AccountID, function(x) ifelse(!is.na(x), paste0("xxxx", substr(x, 5, 7)), NA))
df
##                     id    tags mt4AccountID
## 1 xxx-xxx-xxxxxxxx-007                 <NA>
## 2 xxx-xxx-xxxxxxxx-003                 <NA>
## 3 xxx-xxx-xxxxxxxx-002     MT4      xxxx456
## 4 xxx-xxx-xxxxxxxx-006                 <NA>
## 5 xxx-xxx-xxxxxxxx-001                 <NA>
## 6 xxx-xxx-xxxxxxxx-005 HEDGING         <NA>
## 7 xxx-xxx-xxxxxxxx-004 HEDGING         <NA>

2.2 Retrive Account Info

Defining and documenting the function

#' Retrieves all account information
#' 
#' @param accountType string. Specify either "trade" or "practice"
#' @param accountID string. Specify the Oanda account ID. The default is `Sys.getenv("OANDA_ACCOUNT_ID")`
#' @param token string. Specify the Oanda access token. The default is `Sys.getenv("OANDA_ACCESS_TOKEN")`
#' @examples
#' AccountInfo(accountType = "practice")
AccountInfo <- function(accountType,
                        accountID = Sys.getenv("OANDA_ACCOUNT_ID"), token = Sys.getenv("OANDA_ACCESS_TOKEN")) {
  Url <- paste0("https://api-fx", switch(accountType, practice = "practice", live = "trade"),
                ".oanda.com/v3/accounts/", accountID, "/summary")
  Auth      <- c(Authorization = paste("Bearer", token, sep=" "))
  QueryInst1  <- RCurl::getURL(Url, cainfo=system.file("CurlSSL", "cacert.pem", package="RCurl"), httpheader = Auth)
  InstJson <- jsonlite::fromJSON(QueryInst1, simplifyDataFrame = TRUE)
  return(InstJson)
}

Running an example

out <- AccountInfo(accountType = "practice")
substr(out$account$id, 1, 16) = "xxx-xxx-xxxxxxxx" # masking account #
df <- data.frame(value = unlist(out))
head(df)
##                                                              value
## account.guaranteedStopLossOrderMode                       DISABLED
## account.hedgingEnabled                                       FALSE
## account.id                                    xxx-xxx-xxxxxxxx-001
## account.createdTime                 2020-11-05T02:24:58.351110600Z
## account.currency                                               CAD
## account.createdByUserID                                   16979748

2.3 Retrive Trade Info

Defining and documenting the function

#' Retrieves all information from open trades
#' 
#' @param accountType string. Specify either "trade" or "practice"
#' @param accountID string. Specify the Oanda account ID. The default is `Sys.getenv("OANDA_ACCOUNT_ID")`
#' @param token string. Specify the Oanda access token. The default is `Sys.getenv("OANDA_ACCESS_TOKEN")`
#' @examples
#' TradeInfo(accountType = "practice")
TradeInfo <- function(accountType,
                      accountID = Sys.getenv("OANDA_ACCOUNT_ID"), token = Sys.getenv("OANDA_ACCESS_TOKEN")) {
  Url <- paste0("https://api-fx", switch(accountType, practice = "practice", live = "trade"),
                ".oanda.com/v3/accounts/", accountID, "/openTrades")
  Auth      <- c(Authorization = paste("Bearer", token,sep=" "))
  QueryInst1  <- RCurl::getURL(Url,cainfo=system.file("CurlSSL","cacert.pem", package="RCurl"), httpheader = Auth)
  InstJson <- jsonlite::fromJSON(QueryInst1, simplifyDataFrame = TRUE)
  return(InstJson)
}

Running an example

TradeInfo(accountType = "practice")
## $trades
## list()
## 
## $lastTransactionID
## [1] "1190"

2.4 Retrive Order Info

Defining and documenting the function

#' Retrieves all information from pending orders
#' 
#' @param accountType string. Specify either "trade" or "practice"
#' @param accountID string. Specify the Oanda account ID. The default is `Sys.getenv("OANDA_ACCOUNT_ID")`
#' @param token string. Specify the Oanda access token. The default is `Sys.getenv("OANDA_ACCESS_TOKEN")`
#' @examples
#' OrderInfo(accountType = "practice")
OrderInfo <- function(accountType,
                      accountID = Sys.getenv("OANDA_ACCOUNT_ID"), token = Sys.getenv("OANDA_ACCESS_TOKEN")) {
  Url <- paste0("https://api-fx", switch(accountType, practice = "practice", live = "trade"),
                ".oanda.com/v3/accounts/", accountID, "/pendingOrders")
  Auth      <- c(Authorization = paste("Bearer", token,sep=" "))
  QueryInst1  <- RCurl::getURL(Url,cainfo=system.file("CurlSSL","cacert.pem", package="RCurl"), httpheader = Auth)
  InstJson <- jsonlite::fromJSON(QueryInst1, simplifyDataFrame = TRUE)
  return(InstJson)
}

Running an example

OrderInfo(accountType = "practice")
## $orders
## list()
## 
## $lastTransactionID
## [1] "1190"

2.5 Retrive Positions Info

Defining and documenting the function

#' Retrieves list of all or open only positions, aggregated by Instrument that had a position
#' 
#' @param accountType string. Specify either "trade" or "practice"
#' @param accountID string. Specify the Oanda account ID. The default is `Sys.getenv("OANDA_ACCOUNT_ID")`
#' @param token string. Specify the Oanda access token. The default is `Sys.getenv("OANDA_ACCESS_TOKEN")`
#' @param open_only boolean. Specify true if only positions that had an open trade should be included
#' @examples
#' TradeInfo(accountType = "practice")
PositionsInfo <- function(accountType,
                      accountID = Sys.getenv("OANDA_ACCOUNT_ID"), token = Sys.getenv("OANDA_ACCESS_TOKEN"),
                      open_only = T) {
  Url <- paste0("https://api-fx", switch(accountType, practice = "practice", live = "trade"),
                ".oanda.com/v3/accounts/", accountID, ifelse(open_only, "/openPositions", "/positions"))
  Auth      <- c(Authorization = paste("Bearer", token,sep=" "))
  QueryInst1  <- RCurl::getURL(Url,cainfo=system.file("CurlSSL","cacert.pem", package="RCurl"), httpheader = Auth)
  InstJson <- jsonlite::fromJSON(QueryInst1, simplifyDataFrame = TRUE)
  return(InstJson)
}

Running an example

PositionsInfo(accountType = "practice")
## $positions
## list()
## 
## $lastTransactionID
## [1] "1190"

2.6 Retrive Instruments List

Defining and documenting the function

#' Retrieves list of available instruments
#' 
#' @param accountType string. Specify either "trade" or "practice"
#' @param accountID string. Specify the Oanda account ID. The default is `Sys.getenv("OANDA_ACCOUNT_ID")`
#' @param token string. Specify the Oanda access token. The default is `Sys.getenv("OANDA_ACCESS_TOKEN")`
#' @examples
#' InstrumentsList(accountType = "practice")
InstrumentsList <- function(accountType,
                            accountID = Sys.getenv("OANDA_ACCOUNT_ID"), token = Sys.getenv("OANDA_ACCESS_TOKEN")) {
  
  Url <- paste0("https://api-fx", switch(accountType, practice = "practice", live = "trade"),
                ".oanda.com/v3/accounts/", accountID, "/instruments")
  Auth      <- c(Authorization = paste("Bearer", token,sep=" "))
  QueryInst1  <- RCurl::getURL(Url,cainfo=system.file("CurlSSL","cacert.pem", package="RCurl"), httpheader = Auth)
  InstJson <- jsonlite::fromJSON(QueryInst1, simplifyDataFrame = TRUE)
  return(InstJson$instruments)
  }

Running an example

out <- InstrumentsList(accountType = "practice")
head(out)
##         name     type displayName pipLocation displayPrecision tradeUnitsPrecision minimumTradeSize maximumTrailingStopDistance minimumTrailingStopDistance maximumPositionSize maximumOrderUnits marginRate guaranteedStopLossOrderMode                                           tags financing.longRate financing.shortRate                                                       financing.financingDaysOfWeek
## 1 NAS100_USD      CFD  US Nas 100           0                1                   1              0.1                     10000.0                         5.0                   0              2000      0.117                    DISABLED                             ASSET_CLASS, INDEX            -0.0478             -0.0022 MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY, 1, 1, 1, 1, 3, 0, 0
## 2    AUD_HKD CURRENCY     AUD/HKD          -4                5                   0                1                     1.00000                     0.00050                   0         100000000       0.13                    DISABLED                          ASSET_CLASS, CURRENCY            -0.0129             -0.0196 MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY, 1, 1, 1, 1, 1, 0, 0
## 3    USD_SGD CURRENCY     USD/SGD          -4                5                   0                1                     1.00000                     0.00050                   0         100000000      0.119                    DISABLED                          ASSET_CLASS, CURRENCY            -0.0139             -0.0155 MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY, 1, 1, 1, 1, 1, 0, 0
## 4    EUR_SEK CURRENCY     EUR/SEK          -4                5                   0                1                     1.00000                     0.00050                   0         100000000       0.06                    DISABLED                          ASSET_CLASS, CURRENCY            -0.0174             -0.0037 MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY, 1, 1, 1, 1, 1, 0, 0
## 5    XAG_HKD    METAL  Silver/HKD          -4                5                   0                1                     1.00000                     0.00050                   0            500000        0.3                    DISABLED KID_ASSET_CLASS, ASSET_CLASS, METAL, COMMODITY            -0.0248              -0.012 MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY, 1, 1, 1, 1, 1, 0, 0
## 6    XAG_CHF    METAL  Silver/CHF          -4                5                   0                1                     1.00000                     0.00050                   0            500000       0.24                    DISABLED KID_ASSET_CLASS, ASSET_CLASS, METAL, COMMODITY            -0.0021             -0.0241 MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY, 1, 1, 1, 1, 1, 0, 0

2.7 Retrive Actual Price

Defining and documenting the function

#' Retrieves the actual bid and ask price
#' 
#' @param accountType string. Specify either "trade" or "practice"
#' @param token string. Specify the Oanda access token. The default is `Sys.getenv("OANDA_ACCESS_TOKEN")`
#' @param instrument string. Specify the currency pair in the form XXX_XXX
#' @param tzone string. The timezone of the returned timestamp. The default is UTC
#' @examples
#' ActualPrice(accountType = "practice", instrument = "EUR_USD")
ActualPrice <- function(accountType,
                        accountID = Sys.getenv("OANDA_ACCOUNT_ID"), token = Sys.getenv("OANDA_ACCESS_TOKEN"),
                        instrument, tzone = NULL)
{
  Url <- paste0("https://api-fx", switch(accountType, practice = "practice", live = "trade"),
                ".oanda.com/v3/accounts/", accountID, "/pricing?", "instruments=", instrument)
  Auth <- c(Authorization = paste("Bearer", token, sep=" "))
  InstPrec <- RCurl::getURL(Url, cainfo=system.file("CurlSSL", "cacert.pem", package="RCurl"), httpheader = Auth)
  InstPrecjson <- jsonlite::fromJSON(InstPrec, simplifyDataFrame = TRUE)
  DataJSON  <- data.frame(paste(substr(InstPrecjson$time, 1, 10),substr(InstPrecjson$time, 12, 19), sep=" "),
                          InstPrecjson$prices$bids[[1]]$price,
                          InstPrecjson$prices$asks[[1]]$price)
  colnames(DataJSON) <- c("time_stamp","bid","ask")
  DataJSON$time_stamp <- as.POSIXct(strptime(DataJSON$time_stamp, "%Y-%m-%d %H:%M:%OS"), origin="1970-01-01",tz = "UTC")
  if(!is.null(tzone)) attributes(DataJSON$time_stamp)$tzone <- tzone
  return(DataJSON)
}

Running an example

ActualPrice(accountType = "practice", instrument = "EUR_USD")
##            time_stamp     bid     ask
## 1 2022-08-22 23:22:07 0.99352 0.99369

2.8 Retrive Historical Spreads

Defining and documenting the function

#' Retrieves the historical bid and ask prices at close
#' 
#' @param accountType string. Specify either "trade" or "practice"
#' @param accountID string. Specify the Oanda account ID. The default is `Sys.getenv("OANDA_ACCOUNT_ID")`
#' @param token string. Specify the Oanda access token. The default is `Sys.getenv("OANDA_ACCESS_TOKEN")`
#' @param instrument string. Specify the name of currency pair. A list of valid instrument names can be retrived using `InstrumentsList()`
#' @param granularity string. The time range represented by each candlestick.  Valid values can be found here: https://developer.oanda.com/rest-live-v20/instrument-df/#CandlestickGranularity. The default is 5 seconds.
#' @param dailyAlignment integer. The hour of day used to align candles with higher than hourly granularity. Must be between 0 and 23. The default is 17 UTC
#' @param alignmentTimezone string. The timezone to be used for the dailyAlignment parameter. This parameter does NOT affect the returned timestamp, or the from/to parameters. The timezone format used is defined by the IANA Time Zone Database. Valid values can be found here: http://developer.oanda.com/docs/timezones.txt. The default is 'America/New_York'
#' @param weeklyAlignment string. The day of the week used to align candles with weekly granularity. Valid values are: 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'. The is default 'Friday'
#' @param count integer. The number of candles to return in the response. Count will be ignored if both the start and end parameters are also specified. The default is 500 and the maximum acceptable value is 5000.
#' @param from,to string. The start and end time stamps for the range of candles requested in the following format: YYYY-MM-DD HH:MM:SS (UTC).
#' @param tzone string. The timezone of the returned timestamp. The default is UTC
#' @examples
#' HisSpreads(accountType = "practice", instrument = "EUR_USD", granularity = "H4")
HisSpreads <- function(accountType,
                       accountID = Sys.getenv("OANDA_ACCOUNT_ID"), token = Sys.getenv("OANDA_ACCESS_TOKEN"),
                       instrument, granularity, dailyAlignment = NULL, alignmentTimezone = NULL, weeklyAlignment = NULL,
                       count = NULL, from = NULL, to = NULL, tzone = NULL)
{
  Url <- paste0("https://api-fx", switch(accountType, practice = "practice", live = "trade"),
                ".oanda.com/v3/instruments/", instrument, "/candles?granularity=",
                granularity, "&price=BA",
                if (!is.null(dailyAlignment)) paste0("&dailyAlignment=", dailyAlignment),
                if (!is.null(alignmentTimezone)) paste0("&alignmentTimezone=", alignmentTimezone),
                if (!is.null(weeklyAlignment)) paste0("&weeklyAlignment=", weeklyAlignment),
                if (!is.null(count)) paste0("&count=", count),
                if (!is.null(from)) paste0("&from=", from),
                if (!is.null(to)) paste0("&to=", to))
  Auth <- c(Authorization = paste("Bearer", token, sep=" "))
  InstPrec <- RCurl::getURL(Url, cainfo=system.file("CurlSSL", "cacert.pem", package="RCurl"), httpheader = Auth)
  InstPrecjson <- jsonlite::fromJSON(InstPrec, simplifyDataFrame = TRUE)
  DataJSON  <- data.frame(paste(substr(InstPrecjson$candles$time, 1, 10),substr(InstPrecjson$candles$time, 12, 19), sep=" "),
                          as.numeric(InstPrecjson$candles$bid[[1]]),
                          as.numeric(InstPrecjson$candles$ask[[1]]))
  colnames(DataJSON) <- c("time_stamp", "bid", "ask")
  DataJSON$time_stamp <- as.POSIXct(DataJSON$time_stamp, origin="1970-01-01", tz = "UTC")
  if(!is.null(tzone)) attributes(DataJSON$time_stamp)$tzone <- tzone
  return(DataJSON)
}

Running an example

out <- HisSpreads(accountType = "practice", instrument = "EUR_USD", granularity = "H4", tzone = "Canada/Eastern")
head(out)
##            time_stamp     bid     ask
## 1 2022-04-27 13:00:00 1.05623 1.05640
## 2 2022-04-27 17:00:00 1.05568 1.05668
## 3 2022-04-27 21:00:00 1.05377 1.05393
## 4 2022-04-28 01:00:00 1.05167 1.05185
## 5 2022-04-28 05:00:00 1.05151 1.05169
## 6 2022-04-28 09:00:00 1.04848 1.04865

2.9 Retrive Historical Prices

Defining and documenting the function

#' Retrieves the historical prices at open, high, low, close
#' 
#' @param accountType string. Specify either "trade" or "practice"
#' @param accountID string. Specify the Oanda account ID. The default is `Sys.getenv("OANDA_ACCOUNT_ID")`
#' @param token string. Specify the Oanda access token. The default is `Sys.getenv("OANDA_ACCESS_TOKEN")`
#' @param instrument string. Specify the name of currency pair. A list of valid instrument names can be retrived using [InstrumentsList()]
#' @param granularity string. The time range represented by each candlestick.  Valid values can be found here: https://developer.oanda.com/rest-live-v20/instrument-df/#CandlestickGranularity. The default is 5 seconds.
#' @param price string. Specify either "M" for the middle price, "A" for the ask price, or "B" for the bid price. The default is "M"
#' @param dailyAlignment integer. The hour of day used to align candles with higher than hourly granularity. Must be between 0 and 23. The default is 17 UTC
#' @param alignmentTimezone string. The timezone to be used for the dailyAlignment parameter. This parameter does NOT affect the returned timestamp, or the from/to parameters. The timezone format used is defined by the IANA Time Zone Database. Valid values can be found here: http://developer.oanda.com/docs/timezones.txt. The default is 'America/New_York'
#' @param weeklyAlignment string. The day of the week used to align candles with weekly granularity. Valid values are: 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'. The is 'Friday'
#' @param count integer The number of candles to return in the response. Count will be ignored if both the start and end parameters are also specified. The default is 500 and the maximum acceptable value is 5000.
#' @param from,to string. The start and end timestamps for the range of candles requested in the following format: YYYY-MM-DD HH:MM:SS (UTC).
#' @param tzone string. The timezone of the returned timestamp. The default is UTC
#' @examples
#' HisPrices(accountType = "practice", instrument = "EUR_USD", granularity = "H4")
HisPrices <- function(accountType,
                       accountID = Sys.getenv("OANDA_ACCOUNT_ID"), token = Sys.getenv("OANDA_ACCESS_TOKEN"),
                       instrument, granularity, price = "M", dailyAlignment = NULL, alignmentTimezone = NULL, weeklyAlignment = NULL,
                       count = NULL, from = NULL, to = NULL, tzone = NULL){
  Url <- paste0("https://api-fx", switch(accountType, practice = "practice", live = "trade"),
                ".oanda.com/v3/instruments/", instrument, "/candles?granularity=",
                granularity, "&price=", price,
                if (!is.null(dailyAlignment)) paste0("&dailyAlignment=", dailyAlignment),
                if (!is.null(alignmentTimezone)) paste0("&alignmentTimezone=", alignmentTimezone),
                if (!is.null(weeklyAlignment)) paste0("&weeklyAlignment=", weeklyAlignment),
                if (!is.null(count)) paste0("&count=", count),
                if (!is.null(from)) paste0("&from=", from),
                if (!is.null(to)) paste0("&to=", to))
  Auth <- c(Authorization = paste("Bearer", token, sep=" "))
  InstPrec <- RCurl::getURL(Url, cainfo=system.file("CurlSSL", "cacert.pem", package="RCurl"), httpheader = Auth)
  InstPrecjson <- jsonlite::fromJSON(InstPrec, simplifyDataFrame = TRUE)
  DataJSON  <- data.frame(InstPrecjson$candles$complete,
                          InstPrecjson$candles$volume,
                          paste(substr(InstPrecjson$candles$time, 1, 10),substr(InstPrecjson$candles$time, 12, 19), sep=" "),
                          data.frame(sapply(InstPrecjson$candles[[4]], as.numeric)))
  colnames(DataJSON) <- c("complete", "volume", "time_stamp", "open", "high", "low", "close")
  if(price == "B") colnames(DataJSON) <- c("complete"," volume", "time_stamp", "open_bid", "high_bid", "low_bid", "close_bid")
  if(price == "A") colnames(DataJSON) <- c("complete"," volume", "time_stamp", "open_ask", "high_ask", "low_ask", "close_ask")
  DataJSON$time_stamp <- as.POSIXct(DataJSON$time_stamp, origin="1970-01-01", tz = "UTC")
  if(!is.null(tzone)) attributes(DataJSON$time_stamp)$tzone <- tzone
  return(DataJSON)
}

Running an example

out <- HisPrices(accountType = "practice", instrument = "EUR_USD", granularity = "H4", tzone = "Canada/Eastern")
head(out)
##   complete volume          time_stamp    open    high     low   close
## 1     TRUE  22453 2022-04-27 13:00:00 1.05632 1.05724 1.05536 1.05586
## 2     TRUE  11994 2022-04-27 17:00:00 1.05618 1.05630 1.05308 1.05384
## 3     TRUE  32959 2022-04-27 21:00:00 1.05385 1.05536 1.05088 1.05176
## 4     TRUE  67159 2022-04-28 01:00:00 1.05176 1.05650 1.04814 1.05160
## 5     TRUE  60837 2022-04-28 05:00:00 1.05160 1.05388 1.04713 1.04856
## 6     TRUE  67274 2022-04-28 09:00:00 1.04856 1.05314 1.04815 1.05127

2.10 Create New Order

Defining and documenting the function

#' Retrieves the historical prices at open, high, low, close
#' 
#' @param accountType string. Specify either "trade" or "practice"
#' @param accountID string. Specify the Oanda account ID. The default is `Sys.getenv("OANDA_ACCOUNT_ID")`
#' @param token string. Specify the Oanda access token. The default is `Sys.getenv("OANDA_ACCESS_TOKEN")`
#' @param instrument string. Specify the name of currency pair. A list of valid instrument names can be retrived using [InstrumentsList()]
#' @param orderType string. Specify the order type. Valid values include "MARKET", "LIMIT", "STOP", etc.
#' @param price numeric. Specify the price threshold if the order type is not a market order. The order will be filled at the specified price or better.
#' @param units integer. Specify the quantity of units to be filled, should be positive integer for long order or a negative integer for short orders.
#' @param timeInForce string. Specify how long the order should be pending. Valid values include "GTC" for good until cancelled, "GTD" for good until date, "FOK" for filled or killed, etc. 
#' @param gtdTime datetime. The date/time for when the order will be cancelled if the `timeInForce` argument is specified as good until date.
#' @param stopLoss numeric. Specify the stop loss price
#' @param takeProfit numeric. Specify the take profit price
#' @param TrailingStop numeric. Specify the trailing stop distance in pips
#' @examples
#' NewOrder(accountType = "practice", instrument = "EUR_USD", orderType = "LIMIT", price = 1.3, units = 100, timeInForce = "GTD", gtdTime  = as.numeric(as.POSIXct(Sys.time(),"EST") + 28800), stopLoss = 1.2, takeProfit = 1.4, TrailingStop = 0.00050)
NewOrder <- function(accountType,
                     accountID = Sys.getenv("OANDA_ACCOUNT_ID"), token = Sys.getenv("OANDA_ACCESS_TOKEN"),
                     instrument,
                     orderType,
                     price,
                     units,
                     timeInForce,
                     gtdTime = NULL,
                     stopLoss = NULL, takeProfit = NULL, TrailingStop = NULL,
                     positionFill = "DEFAULT") {
  
  Auth       <- paste0("Bearer ", token)
  Url        <- paste0("https://api-fx", switch(accountType, practice = "practice", live = "trade"),
                       ".oanda.com/v3/accounts/", accountID, "/orders")
  takeProfitOnFill <- if(!is.null(takeProfit)) paste0('{', '"price":', '"', takeProfit,'"', # ",",
                       '}')
  stopLossOnFill <- if(!is.null(takeProfit)) paste0('{', '"price":', '"', stopLoss,'"', # ",",
                     '}')
  trailingStopLossOnFill <- if(!is.null(TrailingStop)) paste0('{', '"distance":', '"', TrailingStop,'"', # ",",
                             '}')
  parseBody <- paste0('{"order":{',
                       '"units":'       , '"', units,        '"', ',',
                       '"instrument":'  , '"', instrument,   '"', ',',
                       '"timeInForce":' , '"', timeInForce,  '"', ',',
                       '"type":'        , '"', orderType,    '"', ',',
                       '"price":'        , '"', price,    '"', ',',
                      if (!is.null(gtdTime)) paste0('"gtdTime":'        , '"', gtdTime,    '"', ','),
                      if (!is.null(takeProfitOnFill)) paste0('"takeProfitOnFill":', takeProfitOnFill,  ','),
                      if (!is.null(stopLossOnFill)) paste0('"stopLossOnFill":', stopLossOnFill,  ','),
                      if (!is.null(trailingStopLossOnFill)) paste0('"trailingStopLossOnFill":', trailingStopLossOnFill,  '}}'))
  
  InstJson <- httr::content(httr::POST(url = Url, encode = "raw", body = parseBody,
                           httr::add_headers("Authorization" = Auth,
                                       "Content-Type" = "application/json")), "parsed")
  return(InstJson)
}

Running an example

out <- NewOrder(accountType = "practice", instrument = "EUR_USD", orderType = "LIMIT", price = 1.3, units = 100, timeInForce = "GTD", gtdTime  = as.numeric(as.POSIXct(Sys.time(),"EST") + 28800), stopLoss = 1.2, TrailingStop = 0.00050)
# reformatting as dataframe
substr(out$orderCreateTransaction$accountID, 1, 16) = "xxx-xxx-xxxxxxxx" # masking account ID
df <- data.frame(value = unlist(out))
head(df)
##                                                           value
## orderCreateTransaction.id                                  1191
## orderCreateTransaction.accountID           xxx-xxx-xxxxxxxx-001
## orderCreateTransaction.userID                          16979748
## orderCreateTransaction.batchID                             1191
## orderCreateTransaction.requestID              61010817493621222
## orderCreateTransaction.time      2022-08-22T23:22:08.826010562Z
# retrive the order ID
newOrderID <- out$orderCreateTransaction$id 

Order ID 1191 was succesfully created. ## Close Order

Defining and documenting the function

#' Retrieves the historical prices at open, high, low, close
#' 
#' @param accountType string. Specify either "trade" or "practice"
#' @param accountID string. Specify the Oanda account ID. The default is `Sys.getenv("OANDA_ACCOUNT_ID")`
#' @param token string. Specify the Oanda access token. The default is `Sys.getenv("OANDA_ACCESS_TOKEN")`
#' @param orderID integer Specify the order ID to be closed
#' @examples
#' CloseOrder()
CloseOrder <- function(accountType,
                     accountID = Sys.getenv("OANDA_ACCOUNT_ID"), token = Sys.getenv("OANDA_ACCESS_TOKEN"),
                     orderID) {
  Auth       <- paste0("Bearer ", token)
  Url        <- paste0("https://api-fx", switch(accountType, practice = "practice", live = "trade"),
                       ".oanda.com/v3/accounts/", accountID, "/orders/", orderID, "/cancel")
  InstJson <- PUT(url = Url, add_headers("Authorization" = Auth, "Content-Type" = "application/json"))
  return(InstJson)
}

Running an example

# Close the order in previous example
CloseOrder(accountType = "practice", orderID = newOrderID)
## Response [https://api-fxpractice.oanda.com/v3/accounts/101-002-16979748-001/orders/1191/cancel]
##   Date: 2022-08-22 23:22
##   Status: 404
##   Content-Type: application/json
##   Size: 415 B

Order ID 1191 was succesfully closed (Status 200).

3 End

Saving file as an R script to the R folder so that the functions can be easily sourced and documented.

# Knit to an R script and add to R folder
input = knitr::current_input()
xfun::Rscript_call(knitr::purl, list(input = input, output = '../R/oanda_api_functions.R'))
## [1] "../R/oanda_api_functions.R"