Skip to content

Instantly share code, notes, and snippets.

@lbrenman
Last active December 6, 2015 16:21
Show Gist options
  • Save lbrenman/687053cc9bdc6b802e1d to your computer and use it in GitHub Desktop.
Save lbrenman/687053cc9bdc6b802e1d to your computer and use it in GitHub Desktop.
Arrow FInancial Watch List API using ArrowDB and Connector based on MarkitOndemand REST API

#Watchlist Example

An example of a financial watch list API for storing a watch list on the cloud and fetching quote data when you retreive the watch list. The watch list is basically a list of stock symbols (e.g. AAPL, TXN, INTC, ...) stored in an ArrowDB. When the watch list is retreived, Arrow will use a block to fetch stock quote data from MarkitOndemand REST API using my stockquote Arrow connector (addstockquote.js). Alternatively, this can be done with the connector by accessing the markitondemand REST API directly (addstockquote_.js).

Why do a watch list in the cloud instead of on device? So that the watch list will be synced across multiple devices for the same user.

Concept is based on a similar concept implemented in ACS/Node.ACS:

MarkitOndemand APIs are:

Note that the MarkitOndemand have rate limiting so repeated calls in a short period may trigger the rate limiting

To support mulitple users, the watch list will have a username entry, therefore to retrieve your watch list items, you will need to do a query and pass in your username. In a real world application, you can manage this by some other means or by using Arrow ACL.

When you post a new entry into the watch list you will beed to provide your username in the post.

Users should not be able to getAll or deleteAll, etc... since they would be effecting other user's watch lists.

Here are sample curl commands:

Get all curl -u l51s0f0FKIAc4m7on5lv9vSk5Vy8vHTt: "http://127.0.0.1:8080/api/watchlist"

curl -u hC3hDQn5tpx2Ga4cBM+ARmBfhrCJM0G+: "https://e63fbd1cace3b2c1c59a9b88fb62f3b839744d8a.cloudapp-enterprise.appcelerator.com/api/watchlist" (this will be blocked in the API)

Get by ID

curl -u hC3hDQn5tpx2Ga4cBM+ARmBfhrCJM0G+: "https://e63fbd1cace3b2c1c59a9b88fb62f3b839744d8a.cloudapp-enterprise.appcelerator.com/api/watchlist/5595f051e01404461d0ceb78"

curl -u l51s0f0FKIAc4m7on5lv9vSk5Vy8vHTt: "http://127.0.0.1:8080/api/watchlist/55958bd97ed7bbfaa70af7ce"

Get query where={“username”:”lbrenman”}

curl -u l51s0f0FKIAc4m7on5lv9vSk5Vy8vHTt: "http://127.0.0.1:8080/api/watchlist/query?where=%7B%22username%22:%22lbrenman%22%7D"

curl -u hC3hDQn5tpx2Ga4cBM+ARmBfhrCJM0G+: "https://e63fbd1cace3b2c1c59a9b88fb62f3b839744d8a.cloudapp-enterprise.appcelerator.com/api/watchlist/query?where=%7B%22username%22:%22lbrenman%22%7D"

Delete by id

curl -X DELETE -u hC3hDQn5tpx2Ga4cBM+ARmBfhrCJM0G+: "https://e63fbd1cace3b2c1c59a9b88fb62f3b839744d8a.cloudapp-enterprise.appcelerator.com/api/watchlist/5595efe8e01404461d0ce8f9"

curl -X DELETE -u l51s0f0FKIAc4m7on5lv9vSk5Vy8vHTt: "http://127.0.0.1:8080/api/watchlist/559459371b4007f00207f6e2"

Post

curl -X POST -u hC3hDQn5tpx2Ga4cBM+ARmBfhrCJM0G+: "https://e63fbd1cace3b2c1c59a9b88fb62f3b839744d8a.cloudapp-enterprise.appcelerator.com/api/watchlist" -d '{"username":"lbrenman","symbol":"AAPL","LastPrice":""}' -H "Content-Type: application/json"

curl -X POST -u l51s0f0FKIAc4m7on5lv9vSk5Vy8vHTt: "http://127.0.0.1:8080/api/watchlist" -d '{"username":"lbrenman","symbol":"AAPL","LastPrice":""}' -H "Content-Type: application/json"

var Arrow = require('arrow');
var PostBlock = Arrow.Block.extend({
name: 'addstockquote',
description: 'add stock quote info for each symbol in watch list',
action: function (req, resp, next) {
if(req.method==="GET") {
var body = JSON.parse(resp.body);
var data = body[body.key];
var dataLen = data.length;
var replies = 0;
if(dataLen === 0) {
next();
return;
}
var model = Arrow.getModel('com.lbrenman.stockquote/quote');
if(dataLen){
data.forEach(function (_row, _index) {
model.findOne(_row.symbol, function (err, dat) {
if(err) {
console.log("model findOne error");
} else {
if(dat.length>0){
_row.LastPrice = dat[0].LastPrice;
}
}
replies++;
if(replies == dataLen) {
setReply();
}
});
});
} else {
model.findOne(data.symbol, function (err, dat) {
if(err) {
console.log("model findOne error");
} else {
if(dat.length>0){
data.LastPrice = dat[0].LastPrice;
}
}
setReply();
});
}
} else {
next();
}
function setReply() {
resp.success(body[body.key], next);
}
}
});
module.exports = PostBlock;
var Arrow = require('arrow');
var request = require('request');
var PostBlock = Arrow.Block.extend({
name: 'addstockquote_',
description: 'add stock quote info for each symbol in watch list',
action: function (req, resp, next) {
if(req.method==="GET") {
var body = JSON.parse(resp.body);
var data = body[body.key];
var dataLen = data.length;
var replies = 0;
if(dataLen === 0) {
next();
return;
}
if(dataLen){
data.forEach(function (_row, _index) {
var url = "http://dev.markitondemand.com/Api/Quote/json?symbol="+_row.symbol;
request(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
var res = JSON.parse(body);
_row.LastPrice = res.Data.LastPrice;
replies++;
if(replies == dataLen) {
setReply();
}
} else {
if(replies == dataLen) {
setReply();
}
}
});
});
} else {
var url = "http://dev.markitondemand.com/Api/Quote/json?symbol="+data.symbol;
request(url, function (error, response, body) {
if (!error && response.statusCode == 200) {
var res = JSON.parse(body);
data.LastPrice = res.Data.LastPrice;
} else {
console.log("markitondemand request error");
}
setReply();
});
}
} else {
next();
}
function setReply() {
resp.success(body[body.key], next);
}
}
});
module.exports = PostBlock;
{
"type": "api",
"group": "arrow",
"dependencies": {
"connector/appc.arrowdb": "1.0.74",
"connector/appc.composite": "^1.0.34",
"connector/com.lbrenman.stockquote": "^1.0.1"
},
"cloud": {
"container": "Dev",
"min": 1,
"max": 1,
"maxqueuesize": 50,
"environment": {},
"cname": null,
"certificate": null
}
}
/**
* Example configuration for connector/com.lbrenman.stockquote.
* Make the changes below as required for your environment.
*/
module.exports = {
connectors: {
'com.lbrenman.stockquote': {
generateModelsFromSchema: true,
modelAutogen: true
}
}
};
{
"name": "lbwatchlistapi",
"description": "",
"version": "1.0.0",
"author": "Leor Brenman",
"license": "",
"framework": "none",
"keywords": [
"appcelerator",
"arrow"
],
"repository": {},
"private": true,
"dependencies": {
"async": "^0.9.0",
"lodash": "^2.4.1",
"pkginfo": "^0.3.0",
"request": "~2.55.0"
},
"devDependencies": {
"arrow": "*",
"grunt": "^0.4.5",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-jshint": "^0.10.0",
"grunt-kahvesi": "^0.1.0",
"grunt-mocha-test": "^0.11.0",
"mocha": "^1.21.4",
"should": "^4.0.4"
},
"main": "app.js"
}
var Arrow = require('arrow');
var Model = Arrow.Model.extend('com.lbrenman.stockquote/quote', 'quote', {
fields: {
Name: { type: String },
Symbol: { type: String },
LastPrice: { type: Number },
Change: { type: Number },
ChangePercent: { type: Number },
Timestamp: { type: String },
MarketCap: { type: Number },
Volume: { type: Number },
ChangeYTD: { type: Number },
ChangePercentYTD: { type: Number },
High: { type: Number },
Low: { type: Number },
Open: { type: Number }
},
connector: 'com.lbrenman.stockquote',
metadata: {
'com.lbrenman.stockquote': {
endpoint: 'Quote/json?symbol='
}
}
});
module.exports = Model;
var Arrow = require('arrow');
var PreBlock = Arrow.Block.extend({
name: 'unsupportedAPI',
description: 'will block API from being called',
action: function (req, resp, next) {
resp.response.status(500); //workaround - https://jira.appcelerator.org/browse/API-852
resp.send({"success": false,"error":"unsupported API"});
next(false);
}
});
module.exports = PreBlock;
var Arrow = require('arrow');
var PreBlock = Arrow.Block.extend({
name: 'validatewatchlistcreate',
description: 'validate watch list create',
action: function (req, resp, next) {
// console.log("validatewatchlistcreate block");
var model = Arrow.getModel('WatchList');
model.query({where:{symbol:req.params.symbol, username:req.params.username}}, function(err, coll) {
if(err) {
resp.response.status(500); //workaround - https://jira.appcelerator.org/browse/API-852
resp.send({"success": false,"error":"ArrowDB error"});
next(false);
} else {
if(coll.length>0) {
resp.response.status(500); //workaround - https://jira.appcelerator.org/browse/API-852
resp.send({"success": false,"error":"duplicate symbol"});
next(false);
} else {
next();
}
}
});
}
});
module.exports = PreBlock;
var Arrow = require('arrow');
var PreBlock = Arrow.Block.extend({
name: 'validatewatchlistquery',
description: 'validate watch list query',
action: function (req, resp, next) {
if(req.params.where && req.params.where != "" && req.params.where != null) {
next();
} else {
resp.response.status(500); //workaround - https://jira.appcelerator.org/browse/API-852
resp.send({"success": false,"error":"incorrect query, use where={'username':'abc'}"});
next(false);
}
}
});
module.exports = PreBlock;
var Arrow = require("arrow");
var Model = Arrow.createModel("WatchList",{
"fields": {
"username": {
"type": "String",
"required": true,
"validator": function(val) {
if(val == "") {
return 'username must not be empty'
}
}
},
"symbol": {
"type": "String",
"required": true,
"validator": function(val) {
if(val == "") {
return 'symbol must not be empty'
}
}
},
"LastPrice": {
"type": "String",
"custom": true
}
},
"connector": "appc.arrowdb",
"actions": [
"create",
"read",
"delete"
],
"beforeCreate": "validatewatchlistcreate",
"beforeFindAll": "unsupportedAPI",
"beforeQuery": "validatewatchlistquery",
"afterQuery": "addstockquote",
"afterFindOne": "addstockquote",
// "afterQuery": "addstockquote_",
// "afterFindOne": "addstockquote_",
"singular": "WatchList",
"plural": "WatchLists"
});
module.exports = Model;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment