Home Programming Languages Using R6 classes in R to retrieve live data for markets and...

Using R6 classes in R to retrieve live data for markets and wallets

0
2362
markets and wallets
10 min read

In this tutorial, you will learn to create a simple requester to request external information from an API over the internet. You will also learn to develop exchange and wallet infrastructure using R programming.

Creating a simple requester to isolate API calls

Now, we will focus on how we actually retrieve live data. This functionality will also be implemented using R6 classes, as the interactions can be complex. First of all, we create a simple Requester class that contains the logic to retrieve data from JSON APIs found elsewhere in the internet and that will be used to get our live cryptocurrency data for wallets and markets. We don’t want logic that interacts with external APIs spread all over our classes, so we centralize it here to manage it as more specialized needs come into play later.

Learn Programming & Development with a Packt Subscription

As you can see, all this object does is offer the public request() method, and all it does is use the formJSON() function from the jsonlite package to call a URL that is being passed to it and send the data it got back to the user. Specifically, it sends it as a dataframe when the data received from the external API can be coerced into dataframe-form.

library(jsonlite)

Requester <- R6Class(
"Requester",
public = list(
request = function(URL) {
return(fromJSON(URL))
}
)
)

Developing our exchanges infrastructure

Our exchanges have multiple markets inside, and that’s the abstraction we will define now. A Market has various private attributes, as we saw before when we defined what data is expected from each file, and that’s the same data we see in our constructor. It also offers a data() method to send back a list with the data that should be saved to a database. Finally, it provides setters and getters as required. Note that the setter for the price depends on what units are requested, which can be either usd or btc, to get a market’s asset price in terms of US Dollars or Bitcoin, respectively:

Market <- R6Class(
"Market",
public = list(
initialize = function(timestamp, name, symbol, rank,
price_btc, price_usd) {
private$timestamp <- timestamp
private$name <- name
private$symbol <- symbol
private$rank <- rank
private$price_btc <- price_btc
private$price_usd <- price_usd
},
data = function() {
return(list(
timestamp = private$timestamp,
name = private$name,
symbol = private$symbol,
rank = private$rank,
price_btc = private$price_btc,
price_usd = private$price_usd
))
},
set_timestamp = function(timestamp) {
private$timestamp <- timestamp
},
get_symbol = function() {
return(private$symbol)
},
get_rank = function() {
return(private$rank)
},
get_price = function(base) {
if (base == 'btc') {
return(private$price_btc)
} else if (base == 'usd') {
return(private$price_usd)
}
}
),
private = list(
timestamp = NULL,
name = "",
symbol = "",
rank = NA,
price_btc = NA,
price_usd = NA
)
)

Now that we have our Market definition, we proceed to create our Exchange definition. This class will receive an exchange name as name and will use the exchange_requester_factory() function to get an instance of the corresponding ExchangeRequester. It also offers an update_markets() method that will be used to retrieve market data with the private markets() method and store it to disk using the timestamp and storage objects being passed to it. Note that instead of passing the timestamp through the arguments for the private markets() method, it’s saved as a class attribute and used within the private insert_metadata() method. This technique provides cleaner code, since the timestamp does not need to be passed through each function and can be retrieved when necessary.

The private markets() method calls the public markets() method in the ExchangeRequester instance saved in the private requester attribute (which was assigned to by the factory) and applies the private insert_metadata() method to update the timestamp for such objects with the one sent to the public update_markets() method call before sending them to be written to the database:

source("./requesters/exchange-requester-factory.R", chdir = TRUE)

Exchange <- R6Class(
"Exchange",
public = list(
initialize = function(name) {
private$requester <- exchange_requester_factory(name)
},
update_markets = function(timestamp, storage) {
private$timestamp <- unclass(timestamp)
storage$write_markets(private$markets())
}
),
private = list(
requester = NULL,
timestamp = NULL,
markets = function() {
return(lapply(private$requester$markets(),
private$insert_metadata))
},
insert_metadata = function(market) {
market$set_timestamp(private$timestamp)
return(market)
}
)
)

Now, we need to provide a definition for our ExchangeRequester implementations. As in the case of the Database, this ExchangeRequester will act as an interface definition that will be implemented by the CoinMarketCapRequester. We see that the ExchangeRequester specifies that all exchange requester instances should provide a public markets() method, and that a list is expected from such a method. From context, we know that this list should contain Market instances. Also, each ExchangeRequester implementation will contain a Requester object by default, since it’s being created and assigned to the requester private attribute upon class instantiation. Finally, each implementation will also have to provide a create_market() private method and will be able to use the request() private method to communicate to the Requester method request() we defined previously:

source("../../../utilities/requester.R")

KNOWN_ASSETS = list(
"BTC" = "Bitcoin",
"LTC" = "Litecoin"
)
ExchangeRequester <- R6Class(
"ExchangeRequester",
public = list(
markets = function() list()
),
private = list(
requester = Requester$new(),
create_market = function(resp) NULL,
request = function(URL) {
return(private$requester$request(URL))
}
)
)

Now we proceed to provide an implementation for CoinMarketCapRequester. As you can see, it inherits from ExchangeRequester, and it provides the required method implementations. Specifically, the markets() public method calls the private request() method from ExchangeRequester, which in turn calls the request() method from Requester, as we have seen, to retrieve data from the private URL specified.

If you request data from CoinMarketCap’s API by opening a web browser and navigating to the URL shown (https://api.coinmarketcap.com/v1/ticker), you will get a list of market data. That is the data that will be received in our CoinMarketCapRequester instance in the form of a dataframe, thanks to the Requester object, and will be transformed into numeric data where appropriate using the private clean() method, so that it’s later used to create Market instances with the apply() function call, which in turn calls the create_market() private method. Note that the timestamp is set to NULL for all markets created this way because, as you may remember from our Exchange class, it’s set before writing it to the database. There’s no need to send the timestamp information all the way down to the CoinMarketCapRequester, since we can simply write at the Exchange level right before we send the data to the database:

source("./exchange-requester.R")
source("../market.R")
CoinMarketCapRequester <- R6Class(
"CoinMarketCapRequester",
inherit = ExchangeRequester,
public = list(
markets = function() {
data <- private$clean(private$request(private$URL))
return(apply(data, 1, private$create_market))
}
),
private = list(
URL = "https://api.coinmarketcap.com/v1/ticker",
create_market = function(row) {
timestamp <- NULL
return(Market$new(
timestamp,
row[["name"]],
row[["symbol"]],
row[["rank"]],
row[["price_btc"]],
row[["price_usd"]]
))
},
clean = function(data) {
data$price_usd <- as.numeric(data$price_usd)
data$price_btc <- as.numeric(data$price_btc)
data$rank <- as.numeric(data$rank)
return(data)
}
)
)

Finally, here’s the code for our exchange_requester_factory(). As you can see, it’s basically the same idea we have used for our other factories, and its purpose is to easily let us add more implementations for our ExchangeRequeseter by simply adding else-if statements in it:

source("./coinmarketcap-requester.R")

exchange_requester_factory <- function(name) {
if (name == "CoinMarketCap") {
return(CoinMarketCapRequester$new())
} else {
stop("Unknown exchange name")
}
}

Developing our wallets infrastructure

Now that we are able to retrieve live price data from exchanges, we turn to our Wallet definition. As you can see, it specifies the type of private attributes we expect for the data that it needs to handle, as well as the public data() method to create the list of data that needs to be saved to a database at some point.

It also provides getters for email, symbol, and address, and the public pudate_assets() method, which will be used to get and save assets into the database, just as we did in the case of Exchange. As a matter of fact, the techniques followed are exactly the same, so we won’t explain them again:

source("./requesters/wallet-requester-factory.R", chdir = TRUE)

Wallet <- R6Class(
"Wallet",
public = list(
initialize = function(email, symbol, address, note) {
private$requester <- wallet_requester_factory(symbol, address)
private$email <- email
private$symbol <- symbol
private$address <- address
private$note <- note
},
data = function() {
return(list(
email = private$email,
symbol = private$symbol,
address = private$address,
note = private$note
))
},
get_email = function() {
return(as.character(private$email))
},
get_symbol = function() {
return(as.character(private$symbol))
},
get_address = function() {
return(as.character(private$address))
},
update_assets = function(timestamp, storage) {
private$timestamp <- timestamp
storage$write_assets(private$assets())
}
),
private = list(
timestamp = NULL,
requester = NULL,
email = NULL,
symbol = NULL,
address = NULL,
note = NULL,
assets = function() {
return (lapply (
private$requester$assets(),
private$insert_metadata))
},
insert_metadata = function(asset) {
timestamp(asset) <- unclass(private$timestamp)
email(asset) <- private$email
return(asset)
}
)
)

Implementing our wallet requesters

The WalletRequester will be conceptually similar to the ExchangeRequester. It will be an interface, and will be implemented in our BTCRequester and LTCRequester interfaces. As you can see, it requires a public method called assets() to be implemented and to return a list of Asset instances. It also requires a private create_asset() method to be implemented, which should return individual Asset instances, and a private url method that will build the URL required for the API call. It offers a request() private method that will be used by implementations to retrieve data from external APIs:

source("../../../utilities/requester.R")

WalletRequester <- R6Class(
"WalletRequester",
public = list(
assets = function() list()
),
private = list(
requester = Requester$new(),
create_asset = function() NULL,
url = function(address) "",
request = function(URL) {
return(private$requester$request(URL))
}
)
)

The BTCRequester and LTCRequester implementations are shown below for completeness, but will not be explained. If you have followed everything so far, they should be easy to understand:

source("./wallet-requester.R")
source("../../asset.R")
BTCRequester <- R6Class(
"BTCRequester",
inherit = WalletRequester,
public = list(
initialize = function(address) {
private$address <- address
},
assets = function() {
total <- as.numeric(private$request(private$url()))
if (total > 0) { return(list(private$create_asset(total))) }
return(list())
}

),
private = list(
address = "",
url = function(address) {
return(paste(
"https://chainz.cryptoid.info/btc/api.dws",
"?q=getbalance",
"&a=",
private$address,
sep = ""
))
},
create_asset = function(total) {
return(new(
"Asset",
email = "",
timestamp = "",
name = "Bitcoin",
symbol = "BTC",
total = total,
address = private$address
))
}
)
)
source("./wallet-requester.R")
source("../../asset.R")
LTCRequester <- R6Class(
"LTCRequester",
inherit = WalletRequester,
public = list(
initialize = function(address) {
private$address <- address
},
assets = function() {
total <- as.numeric(private$request(private$url()))
if (total > 0) { return(list(private$create_asset(total))) }
return(list())
}
),
private = list(
address = "",
url = function(address) {
return(paste(
"https://chainz.cryptoid.info/ltc/api.dws",
"?q=getbalance",
"&a=",
private$address,
sep = ""
))
},
create_asset = function(total) {
return(new(
"Asset",
email = "",
timestamp = "",
name = "Litecoin",
symbol = "LTC",
total = total,
address = private$address
))
}
)
)

The wallet_requester_factory() works just as the other factories; the only difference is that in this case, we have two possible implementations that can be returned, which can be seen in the if statement. If we decided to add a WalletRequester for another cryptocurrency, such as Ether, we could simply add the corresponding branch here, and it should work fine:

source("./btc-requester.R")
source("./ltc-requester.R")

wallet_requester_factory <- function(symbol, address) {
if (symbol == "BTC") {
return(BTCRequester$new(address))
} else if (symbol == "LTC") {
return(LTCRequester$new(address))
} else {
stop("Unknown symbol")
}
}

Hope you enjoyed this interesting tutorial and were able to retrieve live data for your application. To know more, do check out the R Programming By Example and start handling data efficiently with modular, maintainable and expressive codes.

Read More

Introduction to R Programming Language and Statistical Environment

20 ways to describe programming in 5 words

 

NO COMMENTS

LEAVE A REPLY

Please enter your comment!
Please enter your name here