Tuesday, May 23, 2017

Integration API: charts with live data

charte.ca integration API comes handy when you want your published chart to display up-to-date information and you do not have the luxury of logging in to the editor, tweaking the data and re-publishing the chart manually every time the data changes. Consider the following P/E ratio vs. Price to book bubble chart that we want to be updated every hour:




This chart was created and published using charte.ca online editor once, and it is updated and re-published by a script that runs every hour and performs the following tasks:
  • obtain stock quotes for specific symbols from Yahoo;
  • compare them with the data currently displayed by the chart, and, if the data is different:
    • upload new data to the chart;
    • update chart subtitle so it reflects the latest update
    • re-publish the chart
This example uses the following API calls:
  • Download data
  • Put config
  • Upload data
  • Publish chart
For full list of available API calls, see charte.ca API guide.

This particular example uses Python as scripting language, but it could be any language or framework capable of performing HTTP requests. The script is scheduled to run every hour on a system integrator's infrastructure. Full source code is below.

import urllib2
import json
import sys
import time


def call_charteca_api(url, method, content_type=None, data=None):
  opener = urllib2.build_opener(urllib2.HTTPHandler)
  request = urllib2.Request(url, data=data)
  request.add_header('api-key', api_key)
  if content_type != None:
    request.add_header('Content-Type', content_type)
  request.get_method = lambda: method
  return opener.open(request)


# Retrieve data as TSV, as it is stored in the datagrid
def download_quote_data(symbols, symbol_short_name_map, symbol_long_name_map):
  data = [["Symbol"],["P/E Ratio"],["Price to book"],["Market cap (billions)"],["Performance today"],["Name"],["Symbol"]]
  for symbol in symbols:
    # Yahoo ditched YQL on Nov 2, 2017. Get it right from the website
    opener = urllib2.build_opener(urllib2.HTTPHandler)
    req = 'https://finance.yahoo.com/quote/' + symbol
    request = urllib2.Request(req)
    request.add_header('Cache-Control', "no-cache")
    request.get_method = lambda: 'GET'
    r = opener.open(request).read()

    i1=0
    i1=r.find('root.App.main', i1)
    i1=r.find('{', i1)
    i2=r.find("\n", i1)
    i2=r.rfind(';', i1, i2)
    jsonstr=r[i1:i2]      

    #load the raw json data into a python data object
    json_data = json.loads(jsonstr)
    qss = json_data['context']['dispatcher']['stores']['QuoteSummaryStore']

    #pull the values that we are interested in 
    change_sign = "" if float(qss['price']['regularMarketChange']['fmt']) <= 0.0 else "+"
    data[0].append(symbol_short_name_map[symbol] + ": " + change_sign + qss['price']['regularMarketChange']['fmt'])
    data[1].append(qss['summaryDetail']['trailingPE']['fmt'])
    data[2].append(qss['defaultKeyStatistics']['priceToBook']['fmt'])
    data[3].append(qss['summaryDetail']['marketCap']['fmt'])
    data[4].append(change_sign+qss['price']['regularMarketChangePercent']['fmt'])
    data[5].append(symbol_long_name_map[symbol])
    data[6].append(symbol)

    # Print out tab-separated values
    tsv_data = ""
    for data_row in data:
      tsv_data += "\t".join(data_row) + "\n"

  return tsv_data


# Rertrieve existing chart grid data as it is
def download_chart_data(charteca_root, api_key, chart_id):
  print "Downloading existing chart data..."
  download_response = call_charteca_api(charteca_root + '/chart_integration/data/' + chart_id, "GET")
  download_response_data = download_response.read()
  download_response_headers = download_response.info().dict
  if "content-type" not in download_response_headers:
    print "Error: no content-type in the response"
    sys.exit(-1);
  if "application/json" in download_response_headers["content-type"]:
    # We got an error
    print download_response_data
    sys.exit(-1);
  if "text/tab-separated-values" not in download_response_headers["content-type"]:
    # We expected TSV data
    print " Error: expected TSV data in the response"
    sys.exit(-1);
  return download_response_data


# Update chart config: change the subtitle to reflect the date
def update_chart_title(charteca_root, api_key, chart_id):
  print "Updating chart config..."
  updated_on = time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime())
  update_response = call_charteca_api(charteca_root + '/chart_integration/config/' + chart_id, "PUT", content_type="application/json", data='{"config": {"subtitle": {"text1": "Data from Yahoo Finance: %s"}}}' % updated_on)
  update_respose_object = json.loads(update_response.read())
  if update_respose_object["code"] != "SUCCESS":
    print json.dumps(update_respose_object)
    sys.exit(-1);


def update_chart_data(charteca_root, api_key, chart_id, new_data):
  print "Uploading data..."
  upload_response = call_charteca_api(charteca_root + '/chart_integration/data/' + chart_id, "PUT", content_type=None, data=new_data)
  upload_respose_object = json.loads(upload_response.read())
  if upload_respose_object["code"] != "SUCCESS":
    print json.dumps(upload_respose_object)
    sys.exit(-1);


def republish_chart(charteca_root, api_key, chart_id):
  print "Publishing chart..."
  publish_response = call_charteca_api(charteca_root + '/chart_integration/publication/' + chart_id, "POST")
  publish_respose_object = json.loads(publish_response.read())
  if publish_respose_object["code"] != "SUCCESS":
    print json.dumps(publish_respose_object)
    sys.exit(-1);


charteca_root = "https://api.charte.ca"
api_key = "INSERT_YOUR_KEY_HERE"
chart_id = "g7h9GRRPqk23GRZW"

# Stock exchange symbols we are interested in. Order matters.
symbols = ["TD","BMO","BNS","CM","RY"]
symbol_short_name_map ={"TD":"TD","RY":"RBC", "BMO":"BMO", "BNS":"Scotia Bank","CM":"CIBC"}
symbol_long_name_map ={"TD":"Toronto Dominion","RY":"Royal Bank of Canada", "BMO":"Bank of Montreal", "BNS":"Scotia Bank","CM":"Canadian Imperial Bank of Commerce"}

# Retrieve new data from Yahoo, existing data from charteca integration API
new_data = download_quote_data(symbols, symbol_short_name_map, symbol_long_name_map)
old_data = download_chart_data(charteca_root, api_key, chart_id)

# Compare existing data with what we got from Yahoo
old_lines = filter(None, [line.replace("\r", "") for line in old_data.split("\n")])
new_lines = filter(None, [line.replace("\r", "") for line in new_data.split("\n")])

# Compare first few lines: performance, P/E, PriceBook, Capitalization
if old_lines[0] == new_lines[0] and old_lines[1] == new_lines[1] and old_lines[2] == new_lines[2] and old_lines[3] == new_lines[3] and old_lines[4] == new_lines[4]:
  print "Nothing to update, the data is the same"
  sys.exit(0)
else:
  print "Data changed, uploading updates..."
  print old_lines
  print new_lines

update_chart_title(charteca_root, api_key, chart_id)
update_chart_data(charteca_root, api_key, chart_id, new_data)
republish_chart(charteca_root, api_key, chart_id)

sys.exit(0)

No comments:

Post a Comment