Wednesday, August 8, 2018

Quandl API Client: Part 1

If you are interested in more iOS tutorials, please visit my portfolio site at https://suzhoupanda.github.io/. Some of my games and apps haven’t been updated for a while and may be a little buggy, but they are free, so feel free to download and enjoy.    The source code for this tutorial can be cloned from this Github repo, but you’ll have to apply for your own free API keys for the Quandl and Last10k REST APIs in order for the code to work.
In this tutorial, we build an API client that can connect to Quandl’s REST API. Before we begin building the API client itself, we will define a wrapper struct that will encapsulate all the data needed to generate a URL Request object for connecting with the Quandl REST API.
 
class QuandlAPIRequest{

     enum ReturnFormat: String{
        case json
        case xml
        case csv
    }

    let apiKey = "fadk4483mBFDI9Z25Jjfjad_fdafWafdk99fw4W"
    let baseURL = "https://www.quandl.com/api/v3/datasets"
    var startDate: Date
    var endDate: Date

    var dataBase_code = "WIKI"
    var tickerSymbol: TickerSymbol = .Fedex_Corp
    var returnFormat: ReturnFormat = .json

}
For the wrapper struct, we define two instance variables, startDate and endDate, which correspond to query parameters in the Quandl API url query string and are used to define the period of time for which we wish to download time-series JSON data. In addition, we define a constant, baseURL, which is used to construct the URL string, and another constant, apiKey, which will hold the apiKey needed to authenticate the API request (The API key used here is provided for demonstration only and will not work for making real API requests).
We also define instance variables that will correspond to other query parameters in the URL string, namely,dataBase_codetickerSymbol and returnFormat, which, respectively, specify the database code query parameter for the url string, the ticker symbol for the company whose stock quote data is being downloaded, and the format of the data sent from the REST API endpoint. Note that the data type of the returnFormat is actually a nested enum that we define within the wrapper struct itself. Cases for this enum type represent the different formats in which the data will be returned from the server (i.e. .json, .xml, and .csv).
Next, we define an initializer whose parameters will take arguments for each of the instance variables above. Note that we provide default values for returnFormat and the dataBaseCode, since these are the values used most commonly for our purposes here.
 

  init(withTickerSymbol tickerSymbol: TickerSymbol, forStartDate startDate: Date, forEndDate endDate: Date, withDataBaseCode dataBaseCode: String = "WIKI", withReturnFormat returnFormat: ReturnFormat = .json){
        
        self.returnFormat = returnFormat
        self.dataBase_code = dataBaseCode
        self.tickerSymbol = tickerSymbol
        self.startDate = startDate
        self.endDate = endDate
    }

Next, we define a private instance method that will generate the url string based on the values of the instance variables discussed above. Note that we use a dateFormatter to convert the startDate and endDate to the corresponding string values that can be used for the query parameters in the url string.
private func getDataRequestURLString() -> String{
        
        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .short
        dateFormatter.timeStyle = .none
        
        let startDateStr = dateFormatter.string(from: self.startDate)
        let endDateStr = dateFormatter.string(from: self.endDate)
        
        let urlWithRequestParameters = "\(baseURL)/\(dataBase_code)/\(self.tickerSymbol.rawValue)/data.\(returnFormat.rawValue)?start_date=\(startDateStr)&end_date=\(endDateStr)&api_key=\(apiKey)"
        
        return urlWithRequestParameters
    }

Finally, we define a method getURLRequest, which is used to generate the actual URL request object that we use to connect to the REST API endpoint.
 
   func getURLRequest() -> URLRequest{
        
        
        let urlString = getDataRequestURLString()
        
        let url = URL(string: urlString)!
        
        return URLRequest(url: url)
    }
    
With our APIRequest struct ready to go, we proceed to define our Quandl API client class, which is implemented as a singleton. It is basically a wrapper for the NSURLSession.sharedSession singleton, whose methods we use here to connect to the Quandl API REST endpoint.

In addition, we define typealiases QuandlData and QuandlCompletionHandler, which will improve the readability of our code for methods which we will define shortly. In addition, we provide some constants for dictionary keys that will be used to create a userInfo dictionary for custom error objects. We also define a static variable for the QuandlAPIClient singleton.
 

class QuandlAPIClient{
    
    typealias QuandlData = [String: Any]
    typealias QuandlCompletionHandler = (String, JSONData?,NSError?) -> (Void)
    
    static let sharedClient = QuandlAPIClient()
    
    let kReasonForFailure = "reasonForFailure"
    let kHttpStatusCode = "httpStatusCode"
    
    
    var session = URLSession.shared
    
    private init(){
        
    }
}

Next, we define a private method performURLRequest(urlRequest:forCompanyName:withCompletionHandler:)that will act as a wrapper function for the NSURLSession singleton method dataTask(with:completionHandler:). This method will take the arguments provided by the original completion handler (i.e. Data?, URLResponse?, NSError?) and convert it to a format that can be used by our custom completion handlers whose data type is that of the QuandlCompletionHandler, which we just defined:
 


    private func performURLRequest(urlRequest: URLRequest, forCompanyName companyName: String, withCompletionHandler completion: @escaping QuandlCompletionHandler){
        
        let _ = session.dataTask(with: urlRequest, completionHandler: {
            
            data, response, error in
            
            guard let httpResponse = response as? HTTPURLResponse else {
                
                var userInfoDict = [String: Any]()
                
                userInfoDict[self.kReasonForFailure] = "Failed to connect to the server, no HTTP status code obtained"
                
                let error = NSError(domain: "APIRequestError", code: 0, userInfo: userInfoDict)
                
                completion(companyName,nil, error)
                
                return
            }
            
            
            guard httpResponse.statusCode == 200 else {
                
                
                var userInfoDict = [String: Any]()
                
                userInfoDict[self.kReasonForFailure] = "Connect to the server with a status code other than 200"
                
                userInfoDict[self.kHttpStatusCode] = httpResponse.statusCode
                
                let error = NSError(domain: "APIRequestError", code: 0, userInfo: userInfoDict)
                
                completion(companyName,nil, error)
                
                return
            }
            
            
            if(data != nil){
                
                do{
                    let jsonData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! QuandlData
                    
                    completion(companyName,jsonData, nil)
                    
                } catch let error as NSError{
                    
                    completion(companyName,nil, error)
                    
                }
                
            } else {
                var userInfoDict = [String: Any]()
                
                userInfoDict[self.kReasonForFailure] = "Nil values obtained for JSON data"
                
                let error = NSError(domain: "APIRequestError", code: 0, userInfo: userInfoDict)
                
                
                completion(companyName,nil, error)
            }
            
        }).resume()
    }

Next, we define another private method performURLRequest(withTicker: withStartDate:withEndDate:withJSONTaskCompletionHandler:) which will take as arguments a company ticker symbol as well as a start date and an end date for the period of time for which we want time-series stock quote data. These arguments are used, in turn, to instantiate a QuandlAPIRequest object, which is then passed into the performURLRequest(urlRequest:forCompanyName: withCompletionHandler:)method to connect to the REST API endpoint and send our URL request.
private func performURLRequest(withTicker ticker: TickerSymbol, withStartDate startDate: Date, withEndDate endDate: Date, withJSONTaskCompletionHandler completion: @escaping QuandlCompletionHandler){
        
        
        let quandlAPIRequest = QuandlAPIRequest(withTickerSymbol: ticker, forStartDate: startDate, forEndDate: endDate, withDataBaseCode: "WIKI", withReturnFormat: .json)
        
        let urlRequest = quandlAPIRequest.getURLRequest()
        
        
        performURLRequest(urlRequest: urlRequest,forCompanyName: ticker.rawValue, withCompletionHandler: completion)
    }

Note that the performURLRequest(withTicker:withStartDate:withEndDate:withJSONTaskCompletionHandler:) method also takes a completion handler for processing the returned JSON data. To further abstract away the implementation details involved in handling this data, we define two completion handlers as instance variables on our API client class, persistDataCompletionHandler, which will actually persist the data using CoreData persistent stores, and debugCompletionHandler, which will simply print out the returned JSON data for debug purposes.
 

 var persistDataCompletionHandler: QuandlCompletionHandler = {
        
        companyName, jsonData, error in
        
        if(jsonData != nil){
            
            print("About to save JSON data to persistent store....")
            
            
            //TODO:  Use jsonReader to save data
            let jsonReader = JSONReader()
            jsonReader.saveQuandlData(forCompanyName: companyName, forJSONResponseDict: jsonData!)
            
            print("Data successfully saved!")
            
        } else {
            
            print("An error occurred while attempting to get the JSON data: \(error!)")
        }
        
    }
    
    var debugCompletionHandler: QuandlCompletionHandler = {
        
        companyName, jsonData, error in
        
        if(jsonData != nil){
            
            print("The following JSON data was obtained for company: \(companyName)....")
            print(jsonData!)
            
        } else {
            
            print("An error occurred while attempting to get the JSON data: \(error!)")
        }
    }
    

With these methods in place, we define two additional high-level, publicly-available instance methods that define the interface for our API client class. These are the methods that we will use to interact with the Quandl REST API endpoint, that is, performDataPersistAPIRequest(with:startDate:endDate:) and performDebugAPIRequest(forTicker:startDate:endDate:)
, which, respectively, perform a debug API request in which the returned JSON data is printed to the console and perform an actual production-level API request in which the return JSON data is converted to NSManagedObjects which can be saved in our NSPersistent stores.
 

    func performDataPersistAPIRequest(with ticker: TickerSymbol, startDate: Date, endDate: Date){
        
        self.performURLRequest(withTicker: ticker, withStartDate: startDate, withEndDate: endDate, withJSONTaskCompletionHandler: self.persistDataCompletionHandler)
    }
    
    func performDebugAPIRequest(forTicker ticker: TickerSymbol, startDate: Date, endDate: Date){
        
        self.performURLRequest(withTicker: ticker, withStartDate: startDate, withEndDate: endDate, withJSONTaskCompletionHandler: self.debugCompletionHandler)
    }
    
Finally, we go back to our ViewController.swift class, and in the viewDidLoadmethod insert the following code:
 
    let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd"
            let startDate = dateFormatter.date(from: "2005-01-01")!
            let endDate = dateFormatter.date(from: "2014-05-10")!
    
       QuandlAPIClient.sharedClient.performDebugAPIRequest(forTicker: TickerSymbol.Fedex_Corp, startDate: startDate, endDate: endDate)
Here, we instantiate a date formatter with a specific date format fo generating start and end dates that can be passed into the QuandlAPIClient method just defined. I’ve chosen the the period from Jan. 01, 20025 to May 10, 2014. The format is totally arbitrary and you are free to use whatever works for you. Next, we access the QuandlAPIClient.sharedClient singleton and call the publicly-available method performDebugAPIRequest(forTicker:startDate:endDate:), passing in as arguments TickerSymbol.Fedex_Corp, which is the ticker symbol for Fedex Corporation, and the startDate and endDate generated via the dataFormatter shown above.
If you have followed along until now, you should get the following console output:
 
The following JSON data was obtained for company: FDX....
["dataset_data": {
    collapse = "";
    "column_index" = "";
    "column_names" =     (
        Date,
        Open,
        High,
        Low,
        Close,
        Volume,
        "Ex-Dividend",
        "Split Ratio",
        "Adj. Open",
        "Adj. High",
        "Adj. Low",
        "Adj. Close",
        "Adj. Volume"
    );
    data =     (
                (
            "2005-10-14",
            "86.90000000000001",
            "87.7",
            "85.95999999999999",
            "87.59999999999999",
            1373400,
            0,
            1,
            "80.950577512303",
            "81.69580722472899",
            "80.074932600202",
            "81.60265351067601",
            1373400
        ),
                (
            "2005-10-13",
            "86.5",
            "87.18000000000001",
            "85.98",
            "86.58",
            1834300,
            0,
            1,
            "80.57796265608999",
            "81.211407911652",
            "80.093563343013",
            "80.65248562733299",
            1834300
        ),
         ...
         ...
         ...
    (
            "2001-01-05",
            "44.7",
            "44.7",
            "43.01",
            "43.81",
            1485800,
            0,
            1,
            "41.135932585421",
            "41.135932585421",
            "39.58068144293",
            "40.316895001505",
            1485800
        )
    );
    "end_date" = "2005-10-14";
    frequency = daily;
    limit = "";
    order = "";
    "start_date" = "2001-01-05";
    transform = "";
}]

Voila! Congratulations on learning how to connect to the Quandl API REST endpoint. With precious stock quote data in hand, the world is at your fingertips. To continue to Part 2 of this tutorial, click here. Here, we will learn how to use the returned JSON data to construct NSManaged objects that can be persisted with CoreData.
If you are interested in more iOS tutorials, please visit my portfolio site at https://suzhoupanda.github.io/. Some of my games and apps haven’t been updated for a while and may be a little buggy, but they are free, so feel free to download and enjoy.

No comments:

Post a Comment