Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes for NSE #7

Open
arjsandhu opened this issue Aug 21, 2018 · 0 comments
Open

Changes for NSE #7

arjsandhu opened this issue Aug 21, 2018 · 0 comments

Comments

@arjsandhu
Copy link

`/*
This file is a node.js module.

This is a sample implementation of UDF-compatible datafeed wrapper for Quandl (historical data) and yahoo.finance (quotes).
Some algorithms may be incorrect because it's rather an UDF implementation sample
then a proper datafeed implementation.

*/

/* global require /
/
global console /
/
global exports /
/
global process */

"use strict";

var version = '2.0.2';

var https = require("https");
var http = require("http");

var quandlCache = {};

var quandlCacheCleanupTime = 3 * 60 * 60 * 1000; // 3 hours
var quandlKeysValidateTime = 15 * 60 * 1000; // 15 minutes
var yahooFailedStateCacheTime = 3 * 60 * 60 * 1000; // 3 hours;
var quandlMinimumDate = '1970-01-01';

// this cache is intended to reduce number of requests to Quandl
setInterval(function () {
quandlCache = {};
console.warn(dateForLogs() + 'Quandl cache invalidated');
}, quandlCacheCleanupTime);

function dateForLogs() {
return (new Date()).toISOString() + ': ';
}

var defaultResponseHeader = {
"Content-Type": "text/plain",
'Access-Control-Allow-Origin': '*'
};

function sendJsonResponse(response, jsonData) {
response.writeHead(200, defaultResponseHeader);
response.write(JSON.stringify(jsonData));
response.end();
}

function dateToYMD(date) {
var obj = new Date(date);
var year = obj.getFullYear();
var month = obj.getMonth() + 1;
var day = obj.getDate();
return year + "-" + month + "-" + day;
}

var quandlKeys = process.env.QUANDL_API_KEY.split(','); // you should create a free account on quandl.com to get this key, you can set some keys concatenated with a comma
var invalidQuandlKeys = [];

function getValidQuandlKey() {
for (var i = 0; i < quandlKeys.length; i++) {
var key = quandlKeys[i];
if (invalidQuandlKeys.indexOf(key) === -1) {
return key;
}
}
return null;
}

function markQuandlKeyAsInvalid(key) {
if (invalidQuandlKeys.indexOf(key) !== -1) {
return;
}

invalidQuandlKeys.push(key);

console.warn(dateForLogs() + 'Quandl key invalidated ' + key);

setTimeout(function() {
	console.log(dateForLogs() + "Quandl key restored: " + invalidQuandlKeys.shift());
}, quandlKeysValidateTime);

}

function sendError(error, response) {
response.writeHead(200, defaultResponseHeader);
response.write("{"s":"error","errmsg":"" + error + ""}");
response.end();
}

function httpGet(datafeedHost, path, callback) {
var options = {
host: datafeedHost,
path: path
};

function onDataCallback(response) {
	var result = '';

	response.on('data', function (chunk) {
		result += chunk;
	});

	response.on('end', function () {
		if (response.statusCode !== 200) {
			callback({ status: 'ERR_STATUS_CODE', errmsg: response.statusMessage || '' });
			return;
		}

		callback({ status: 'ok', data: result });
	});
}

var req = https.request(options, onDataCallback);

req.on('socket', function (socket) {
	socket.setTimeout(5000);
	socket.on('timeout', function () {
		console.log(dateForLogs() + 'timeout');
		req.abort();
	});
});

req.on('error', function (e) {
	callback({ status: 'ERR_SOCKET', errmsg: e.message || '' });
});

req.end();

}

function convertQuandlHistoryToUDFFormat(data) {
function parseDate(input) {
var parts = input.split('-');
return Date.UTC(parts[0], parts[1] - 1, parts[2]);
}

function columnIndices(columns) {
	var indices = {};
	for (var i = 0; i < columns.length; i++) {
		indices[columns[i].name] = i;
	}

	return indices;
}
console.log(data);
var result = {
	t: [],
	c: [],
	o: [],
	h: [],
	l: [],
	v: [],
	s: "ok"
};

try {
	var json = JSON.parse(data);
	var datatable = json.datatable;
	var idx = columnIndices(datatable.columns);

	datatable.data.forEach(function (row) {
		result.t.push(parseDate(row[idx.date]) / 1000);
		result.o.push(row[idx.open]);
		result.h.push(row[idx.high]);
		result.l.push(row[idx.low]);
		result.c.push(row[idx.close]);
		result.v.push(row[idx.volume]);
	});

} catch (error) {
	return null;
}

return result;

}
function MyConvertQuandlHistoryToUDFFormat(data) {
function parseDate(input) {
var parts = input.split('-');
return Date.UTC(parts[0], parts[1] - 1, parts[2]);
}

function columnIndices(columns) {
	var indices = {};
	for (var i = 0; i < columns.length; i++) {
		indices[columns[i].name] = i;
	}

	return indices;
}
//console.log(data);
var result = {
	t: [],
	c: [],
	o: [],
	h: [],
	l: [],
	v: [],
	s: "ok"
};
try {
	var json = JSON.parse(data);
	var datatable = json.dataset;
	//var idx = columnIndices(datatable.column_names);
	//console.log(idx);
	
datatable.data.forEach(function (row) {
		result.t.push(parseDate(row[0]) / 1000);
		result.o.push(row[1]);
		result.h.push(row[2]);
		result.l.push(row[3]);
		result.c.push(row[4]);
		result.v.push(row[5]);
	});
} catch (error) {
	return null;
}
return result;

}
function convertYahooQuotesToUDFFormat(tickersMap, data) {
if (!data.query || !data.query.results) {
var errmsg = "ERROR: empty quotes response: " + JSON.stringify(data);
console.log(dateForLogs() + errmsg);
return {
s: "error",
errmsg: errmsg
};
}

var result = {
	s: "ok",
	d: []
};

[].concat(data.query.results.quote).forEach(function (quote) {
	var ticker = tickersMap[quote.symbol];

	// this field is an error token
	if (quote["ErrorIndicationreturnedforsymbolchangedinvalid"] || !quote.StockExchange) {
		result.d.push({
			s: "error",
			n: ticker,
			v: {}
		});
		return;
	}

	result.d.push({
		s: "ok",
		n: ticker,
		v: {
			ch: +(quote.ChangeRealtime || quote.Change),
			chp: +((quote.PercentChange || quote.ChangeinPercent) && (quote.PercentChange || quote.ChangeinPercent).replace(/[+-]?(.*)%/, "$1")),

			short_name: quote.Symbol,
			exchange: quote.StockExchange,
			original_name: quote.StockExchange + ":" + quote.Symbol,
			description: quote.Name,

			lp: +quote.LastTradePriceOnly,
			ask: +quote.AskRealtime,
			bid: +quote.BidRealtime,

			open_price: +quote.Open,
			high_price: +quote.DaysHigh,
			low_price: +quote.DaysLow,
			prev_close_price: +quote.PreviousClose,
			volume: +quote.Volume,
		}
	});
});
return result;

}

function proxyRequest(controller, options, response) {
controller.request(options, function (res) {
var result = '';

	res.on('data', function (chunk) {
		result += chunk;
	});

	res.on('end', function () {
		if (res.statusCode !== 200) {
			response.writeHead(200, defaultResponseHeader);
			response.write(JSON.stringify({
				s: 'error',
				errmsg: 'Failed to get news'
			}));
			response.end();
			return;
		}
		response.writeHead(200, defaultResponseHeader);
		response.write(result);
		response.end();
	});
}).end();

}

function RequestProcessor(symbolsDatabase) {
this._symbolsDatabase = symbolsDatabase;
this._failedYahooTime = {};
}

function filterDataPeriod(data, fromSeconds, toSeconds) {
if (!data || !data.t) {
return data;
}

if (data.t[data.t.length - 1] < fromSeconds) {
	return {
		s: 'no_data',
		nextTime: data.t[data.t.length - 1]
	};
}

var fromIndex = null;
var toIndex = null;
var times = data.t;
for (var i = 0; i < times.length; i++) {
	var time = times[i];
	if (fromIndex === null && time >= fromSeconds) {
		fromIndex = i;
	}
	if (toIndex === null && time >= toSeconds) {
		toIndex = time > toSeconds ? i - 1 : i;
	}
	if (fromIndex !== null && toIndex !== null) {
		break;
	}
}

fromIndex = fromIndex || 0;
toIndex = toIndex ? toIndex + 1 : times.length;

var s = data.s;

if (toSeconds < times[0]) {
	s = 'no_data';
}

toIndex = Math.min(fromIndex + 1000, toIndex); // do not send more than 1000 bars for server capacity reasons

return {
	t: data.t.slice(fromIndex, toIndex),
	o: data.o.slice(fromIndex, toIndex),
	h: data.h.slice(fromIndex, toIndex),
	l: data.l.slice(fromIndex, toIndex),
	c: data.c.slice(fromIndex, toIndex),
	v: data.v.slice(fromIndex, toIndex),
	s: s
};

}

RequestProcessor.prototype._sendConfig = function (response) {

var config = {
	supports_search: true,
	supports_group_request: false,
	supports_marks: true,
	supports_timescale_marks: true,
	supports_time: true,
	exchanges: [
		{
			value: "",
			name: "All Exchanges",
			desc: ""
		},
		{
			value: "NasdaqNM",
			name: "NasdaqNM",
			desc: "NasdaqNM"
		},
		{
			value: "NYSE",
			name: "NYSE",
			desc: "NYSE"
		},
		{
			value: "NCM",
			name: "NCM",
			desc: "NCM"
		},
		{
			value: "NGM",
			name: "NGM",
			desc: "NGM"
		},
		{
			value: "NSE",
			name: "NSE",
			desc: "NSE"
		}],
	symbols_types: [
		{
			name: "All types",
			value: ""
		},
		{
			name: "Stock",
			value: "stock"
		},
		{
			name: "Index",
			value: "index"
		}
	],
	supported_resolutions: ["D", "2D", "3D", "W", "3W", "M", '6M']
};

response.writeHead(200, defaultResponseHeader);
response.write(JSON.stringify(config));
response.end();

};

RequestProcessor.prototype._sendMarks = function (response) {
var now = new Date();
now = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())) / 1000;
var day = 60 * 60 * 24;

var marks = {
	id: [0, 1, 2, 3, 4, 5],
	time: [now, now - day * 4, now - day * 7, now - day * 7, now - day * 15, now - day * 30],
	color: ["red", "blue", "green", "red", "blue", "green"],
	text: ["Today", "4 days back", "7 days back + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "7 days back once again", "15 days back", "30 days back"],
	label: ["A", "B", "CORE", "D", "EURO", "F"],
	labelFontColor: ["white", "white", "red", "#FFFFFF", "white", "#000"],
	minSize: [14, 28, 7, 40, 7, 14]
};

response.writeHead(200, defaultResponseHeader);
response.write(JSON.stringify(marks));
response.end();

};

RequestProcessor.prototype._sendTime = function (response) {
var now = new Date();
response.writeHead(200, defaultResponseHeader);
response.write(Math.floor(now / 1000) + '');
response.end();
};

RequestProcessor.prototype._sendTimescaleMarks = function (response) {
var now = new Date();
now = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())) / 1000;
var day = 60 * 60 * 24;

var marks = [
	{
		id: "tsm1",
		time: now,
		color: "red",
		label: "A",
		tooltip: ""
	},
	{
		id: "tsm2",
		time: now - day * 4,
		color: "blue",
		label: "D",
		tooltip: ["Dividends: $0.56", "Date: " + new Date((now - day * 4) * 1000).toDateString()]
	},
	{
		id: "tsm3",
		time: now - day * 7,
		color: "green",
		label: "D",
		tooltip: ["Dividends: $3.46", "Date: " + new Date((now - day * 7) * 1000).toDateString()]
	},
	{
		id: "tsm4",
		time: now - day * 15,
		color: "#999999",
		label: "E",
		tooltip: ["Earnings: $3.44", "Estimate: $3.60"]
	},
	{
		id: "tsm7",
		time: now - day * 30,
		color: "red",
		label: "E",
		tooltip: ["Earnings: $5.40", "Estimate: $5.00"]
	},
];

response.writeHead(200, defaultResponseHeader);
response.write(JSON.stringify(marks));
response.end();

};

RequestProcessor.prototype._sendSymbolSearchResults = function (query, type, exchange, maxRecords, response) {
if (!maxRecords) {
throw "wrong_query";
}

var result = this._symbolsDatabase.search(query, type, exchange, maxRecords);

response.writeHead(200, defaultResponseHeader);
response.write(JSON.stringify(result));
response.end();

};

RequestProcessor.prototype._prepareSymbolInfo = function (symbolName) {
var symbolInfo = this._symbolsDatabase.symbolInfo(symbolName);

if (!symbolInfo) {
	throw "unknown_symbol " + symbolName;
}

return {
	"name": symbolInfo.name,
	"exchange-traded": symbolInfo.exchange,
	"exchange-listed": symbolInfo.exchange,
	"timezone": "Asia/Kolkata",
	"minmov": 1,
	"minmov2": 0,
	"pointvalue": 1,
	"session": "0800-1630",
	"has_intraday": false,
	"has_no_volume": symbolInfo.type !== "stock",
	"description": symbolInfo.description.length > 0 ? symbolInfo.description : symbolInfo.name,
	"type": symbolInfo.type,
	"supported_resolutions": ["D", "2D", "3D", "W", "3W", "M", "6M"],
	"pricescale": 100,
	"ticker": symbolInfo.name.toUpperCase()
};

};

RequestProcessor.prototype._sendSymbolInfo = function (symbolName, response) {
var info = this._prepareSymbolInfo(symbolName);

response.writeHead(200, defaultResponseHeader);
response.write(JSON.stringify(info));
response.end();

};

RequestProcessor.prototype._sendSymbolHistory = function (symbol, startDateTimestamp, endDateTimestamp, resolution, response) {
function sendResult(content) {
var header = Object.assign({}, defaultResponseHeader);
header["Content-Length"] = content.length;
response.writeHead(200, header);
response.write(content, null, function () {
response.end();
});
}

function secondsToISO(sec) {
	if (sec === null || sec === undefined) {
		return 'n/a';
	}
	return (new Date(sec * 1000).toISOString());
}

function logForData(data, key, isCached) {
	var fromCacheTime = data && data.t ? data.t[0] : null;
	var toCacheTime = data && data.t ? data.t[data.t.length - 1] : null;
	console.log(dateForLogs() + "Return QUANDL result" + (isCached ? " from cache" : "") + ": " + key + ", from " + secondsToISO(fromCacheTime) + " to " + secondsToISO(toCacheTime));
}

console.log(dateForLogs() + "Got history request for " + symbol + ", " + resolution + " from " + secondsToISO(startDateTimestamp)+ " to " + secondsToISO(endDateTimestamp));

// always request all data to reduce number of requests to quandl
var from = quandlMinimumDate;
var to = dateToYMD(Date.now());

var key = symbol + "|" + from + "|" + to;

if (quandlCache[key]) {
	var dataFromCache = filterDataPeriod(quandlCache[key], startDateTimestamp, endDateTimestamp);
	logForData(dataFromCache, key, true);
	sendResult(JSON.stringify(dataFromCache));
	return;
}

var quandlKey = getValidQuandlKey();

if (quandlKey === null) {
	console.log(dateForLogs() + "No valid quandl key available");
	sendError('No valid API Keys available', response);
	return;
}

var address = "/api/v3/datasets/NSE/"+symbol+
	".json?api_key="+quandlKey+
	"&start_date="+from+
	"&end_date="+to+"";

console.log(dateForLogs() + "Sending request to quandl  " + key + ". url="+address+"");


httpGet("www.quandl.com", address, function (result) {
		if (response.finished) {
		// we can be here if error happened on socket disconnect
		return;
	}

	if (result.status !== 'ok') {
		if (result.status === 'ERR_SOCKET') {
			console.log('Socket problem with request: ' + result.errmsg);
			sendError("Socket problem with request " + result.errmsg, response);
			return;
		}

		console.error(dateForLogs() + "Error response from quandl for key " + key + ". Message: " + result.errmsg);
		markQuandlKeyAsInvalid(quandlKey);
		sendError("Error quandl response " + result.errmsg, response);
		return;
	}

	console.log(dateForLogs() + "Got response from quandl  " + key + ". Try to parse.");
	
	var data = MyConvertQuandlHistoryToUDFFormat(result.data);
	//console.log(data);
	//response.write(JSON.stringify(convertQuandlHistoryToUDFFormat(result.data)));
	if (data === null) {
		var dataStr = typeof result === "string" ? result.slice(0, 100) : result;
		console.error(dateForLogs() + " failed to parse: " + dataStr);
		sendError("Invalid quandl response", response);
		return;
	}

	if (data.t.length !== 0) {
		console.log(dateForLogs() + "Successfully parsed and put to cache " + data.t.length + " bars.");
		quandlCache[key] = data;
	} else {
		console.log(dateForLogs() + "Parsing returned empty result.");
	}

	var filteredData = filterDataPeriod(data, startDateTimestamp, endDateTimestamp);
	console.log(filteredData);
	logForData(filteredData, key, false);
	sendResult(JSON.stringify(filteredData));
	});

};

RequestProcessor.prototype._quotesQuandlWorkaround = function (tickersMap) {
var from = quandlMinimumDate;
var to = dateToYMD(Date.now());

var result = {
	s: "ok",
	d: [],
	source: 'Quandl',
};

Object.keys(tickersMap).forEach(function(symbol) {
	var key = symbol + "|" + from + "|" + to;
	var ticker = tickersMap[symbol];

	var data = quandlCache[key];
	var length = data === undefined ? 0 : data.c.length;

	if (length > 0) {
		var lastBar = {
			o: data.o[length - 1],
			h: data.o[length - 1],
			l: data.o[length - 1],
			c: data.o[length - 1],
			v: data.o[length - 1],
		};

		result.d.push({
			s: "ok",
			n: ticker,
			v: {
				ch: 0,
				chp: 0,

				short_name: symbol,
				exchange: '',
				original_name: ticker,
				description: ticker,

				lp: lastBar.c,
				ask: lastBar.c,
				bid: lastBar.c,

				open_price: lastBar.o,
				high_price: lastBar.h,
				low_price: lastBar.l,
				prev_close_price: length > 1 ? data.c[length - 2] : lastBar.o,
				volume: lastBar.v,
			}
		});
	}
});

return result;

};

RequestProcessor.prototype._sendQuotes = function (tickersString, response) {
var tickersMap = {}; // maps YQL symbol to ticker

var tickers = tickersString.split(",");
[].concat(tickers).forEach(function (ticker) {
	var yqlSymbol = ticker.replace(/.*:(.*)/, "$1");
	tickersMap[yqlSymbol] = ticker;
});

if (this._failedYahooTime[tickersString] && Date.now() - this._failedYahooTime[tickersString] < yahooFailedStateCacheTime) {
	sendJsonResponse(response, this._quotesQuandlWorkaround(tickersMap));
	console.log("Quotes request : " + tickersString + ' processed from quandl cache');
	return;
}

var that = this;

var yql = "env 'store://datatables.org/alltableswithkeys'; select * from yahoo.finance.quotes where symbol in ('" + Object.keys(tickersMap).join("','") + "')";
console.log("Quotes query: " + yql);

var options = {
	host: "query.yahooapis.com",
	path: "/v1/public/yql?q=" + encodeURIComponent(yql) +
	"&format=json" +
	"&env=store://datatables.org/alltableswithkeys"
};
// for debug purposes
// console.log(options.host + options.path);

http.request(options, function (res) {
	var result = '';

	res.on('data', function (chunk) {
		result += chunk;
	});

	res.on('end', function () {
		var jsonResponse = { s: 'error' };

		if (res.statusCode === 200) {
			jsonResponse = convertYahooQuotesToUDFFormat(tickersMap, JSON.parse(result));
		} else {
			console.error('Yahoo Fails with code ' + res.statusCode);
		}

		if (jsonResponse.s === 'error') {
			that._failedYahooTime[tickersString] = Date.now();
			jsonResponse = that._quotesQuandlWorkaround(tickersMap);
			console.log("Quotes request : " + tickersString + ' processed from quandl');
		}

		sendJsonResponse(response, jsonResponse);
	});
}).end();

};

RequestProcessor.prototype._sendNews = function (symbol, response) {
var options = {
host: "feeds.finance.yahoo.com",
path: "/rss/2.0/headline?s=" + symbol + "&region=US&lang=en-US"
};

proxyRequest(https, options, response);

};

RequestProcessor.prototype._sendFuturesmag = function (response) {
var options = {
host: "www.futuresmag.com",
path: "/rss/all"
};

proxyRequest(http, options, response);

};

RequestProcessor.prototype.processRequest = function (action, query, response) {
try {
if (action === "/config") {
this._sendConfig(response);
}
else if (action === "/symbols" && !!query["symbol"]) {
this._sendSymbolInfo(query["symbol"], response);
}
else if (action === "/search") {
this._sendSymbolSearchResults(query["query"], query["type"], query["exchange"], query["limit"], response);
}
else if (action === "/history") {
this._sendSymbolHistory(query["symbol"], query["from"], query["to"], query["resolution"].toLowerCase(), response);
}
else if (action === "/quotes") {
this._sendQuotes(query["symbols"], response);
}
else if (action === "/marks") {
this._sendMarks(response);
}
else if (action === "/time") {
this._sendTime(response);
}
else if (action === "/timescale_marks") {
this._sendTimescaleMarks(response);
}
else if (action === "/news") {
this._sendNews(query["symbol"], response);
}
else if (action === "/futuresmag") {
this._sendFuturesmag(response);
} else {
response.writeHead(200, defaultResponseHeader);
response.write('Datafeed version is ' + version +
'\nValid keys count is ' + String(quandlKeys.length - invalidQuandlKeys.length) +
'\nCurrent key is ' + (getValidQuandlKey() || '').slice(0, 3) +
(invalidQuandlKeys.length !== 0 ? '\nInvalid keys are ' + invalidQuandlKeys.reduce(function(prev, cur) { return prev + cur.slice(0, 3) + ','; }, '') : ''));
response.end();
}
}
catch (error) {
sendError(error, response);
console.error('Exception: ' + error);
}
};

exports.RequestProcessor = RequestProcessor;
`
I think i make all the charges work for NSE data from quandl, but i am getting nothing on TV graph

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant