342 lines
8.0 KiB
JavaScript
342 lines
8.0 KiB
JavaScript
import concat from "lodash/concat";
|
|
import uniq from "lodash/uniq";
|
|
import find from "lodash/find";
|
|
import sampleSize from "lodash/sampleSize";
|
|
import keys from "lodash/keys";
|
|
|
|
export default class {
|
|
constructor() {
|
|
this.subscribers = {};
|
|
this.timestamp = new Date();
|
|
|
|
// create the initial state of data
|
|
this.initialiseTickerData();
|
|
}
|
|
|
|
addSubscriber(subscriber, symbol) {
|
|
if (!this.subscribers[symbol]) {
|
|
this.subscribers[symbol] = [];
|
|
}
|
|
this.subscribers[symbol].push(subscriber);
|
|
|
|
if (!this.updateInterval) {
|
|
this.updateInterval = setInterval(this.applyDeltasToTickerData.bind(this), 500);
|
|
}
|
|
}
|
|
|
|
removeSubscriber(subscriber, symbol) {
|
|
let subscribers = this.subscribers[symbol];
|
|
subscribers.splice(subscribers.indexOf(subscriber), 1);
|
|
}
|
|
|
|
removeSubscribers() {
|
|
this.subscribers = {};
|
|
}
|
|
|
|
getTicker(symbol) {
|
|
return this.tickerData[symbol];
|
|
}
|
|
|
|
getExchanges() {
|
|
return EXCHANGES;
|
|
}
|
|
|
|
getExchangeInformation(exchangeName) {
|
|
return find(EXCHANGES, (exchange) => {
|
|
return exchange.symbol === exchangeName;
|
|
})
|
|
}
|
|
|
|
getTickerDetail(symbol) {
|
|
return this.createTickerDetail(symbol);
|
|
}
|
|
|
|
/*
|
|
* the rest of this class exists primarily to create mock data - it can safely be ignored
|
|
* as it is secondary to the main ideas being demonstrated by the rest of the application
|
|
*/
|
|
initialiseTickerData() {
|
|
this.tickerData = {};
|
|
|
|
const allSymbols = uniq(concat(NASDAQ_SYMBOLS, LSE_SYMBOLS, JSE_SYMBOLS, DE_SYMBOLS));
|
|
allSymbols.forEach((symbol) => {
|
|
this.tickerData[symbol] = this.generateTickerRow(symbol);
|
|
});
|
|
}
|
|
|
|
generateTickerRow(symbol) {
|
|
let price = this.random(10, 600);
|
|
return {
|
|
symbol,
|
|
price,
|
|
bid: price - this.random(1, 3),
|
|
ask: price + this.random(1, 3),
|
|
recommendation: ['Buy', 'Hold', 'Sell'][Math.floor(this.random(0, 2))]
|
|
}
|
|
}
|
|
|
|
random(min, max) {
|
|
return parseFloat((Math.random() * (max - min + 1) + min))
|
|
}
|
|
|
|
applyDeltasToTickerData() {
|
|
let symbols = keys(this.subscribers);
|
|
let properties = ['price', 'bid', 'ask'];
|
|
|
|
let symbolsToAlter = sampleSize(symbols, symbols.length / 4);
|
|
let propertyToAlter = sampleSize(properties, 1);
|
|
|
|
symbolsToAlter.forEach((symbol) => {
|
|
this.tickerData[symbol] = {
|
|
symbol,
|
|
price: this.tickerData[symbol].price,
|
|
bid: this.tickerData[symbol].bid,
|
|
ask: this.tickerData[symbol].ask
|
|
};
|
|
|
|
this.tickerData[symbol][propertyToAlter] = +this.tickerData[symbol][propertyToAlter] + this.random(-2, 2);
|
|
});
|
|
|
|
symbols.forEach((symbol) => {
|
|
this.subscribers[symbol].forEach((subscriber) => {
|
|
subscriber(this.tickerData[symbol]);
|
|
});
|
|
});
|
|
}
|
|
|
|
formatNumber(input) {
|
|
return input.toFixed(2);
|
|
}
|
|
|
|
formatWithDecimalPlaces(x) {
|
|
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
}
|
|
|
|
createTickerDetail(symbol) {
|
|
let ticker = this.getTicker(symbol);
|
|
let currentPrice = ticker.price;
|
|
let tenthOfCurrentPrice = currentPrice / 10;
|
|
let previousPrice = +currentPrice + this.random(-tenthOfCurrentPrice, tenthOfCurrentPrice);
|
|
|
|
let twentiethOfCurrentPrice = currentPrice / 20;
|
|
let yearAgoPrice = this.random(-twentiethOfCurrentPrice, twentiethOfCurrentPrice);
|
|
|
|
let range = `${this.formatNumber(previousPrice)} - ${this.formatNumber(currentPrice)}`;
|
|
let fiftyTwoWeek = `${this.formatNumber(yearAgoPrice)} - ${this.formatNumber(currentPrice)}`;
|
|
|
|
let open = this.formatNumber(ticker.bid); // not the same, but will do for demo purposes
|
|
|
|
let vol = this.formatWithDecimalPlaces(this.random(5000, 20000).toFixed(2));
|
|
let avg = `${this.formatNumber(this.random(10, 30))}M`;
|
|
|
|
let dividend = this.random(0, 1).toFixed(2);
|
|
let yld = this.random(1, 2).toFixed(2);
|
|
|
|
let eps = this.random(5, 10).toFixed(2);
|
|
|
|
let shares = `${this.random(3000, 10000).toFixed(2)}M`;
|
|
|
|
let marketCap = `${this.random(100000, 900000).toFixed(2)}M`;
|
|
|
|
let historicalData = this.generateHistoricalData(100, this.timestamp, currentPrice);
|
|
|
|
return {
|
|
pricingDelta: {
|
|
currentPrice,
|
|
previousPrice
|
|
},
|
|
timestamp: this.timestamp.toDateString(),
|
|
tickerSummary: {
|
|
range,
|
|
fiftyTwoWeek,
|
|
open,
|
|
vol,
|
|
avg,
|
|
dividend,
|
|
yld,
|
|
eps,
|
|
shares,
|
|
marketCap
|
|
},
|
|
historicalData
|
|
}
|
|
}
|
|
|
|
formatDate(date) {
|
|
return `${date.getDate()}-${date.getMonth() + 1}-${date.getFullYear()}`;
|
|
}
|
|
|
|
generateHistoricalData(numberOfPoints, endDate, endPrice) {
|
|
let historicalData = [{
|
|
"date": this.formatDate(endDate),
|
|
"price": endPrice
|
|
}
|
|
];
|
|
|
|
let numberOfTransitions = 15;
|
|
let pointsPerTransition = numberOfPoints / numberOfTransitions;
|
|
|
|
let lastDate = endDate;
|
|
let lastPrice = endPrice;
|
|
for (let transition = 0; transition < numberOfTransitions; transition++) {
|
|
let swing = (Math.random() >= 0.5) ? 1 : -1;
|
|
|
|
for (let i = 0; i <= pointsPerTransition; i++) {
|
|
lastDate.setDate(lastDate.getDate() - 1);
|
|
lastPrice = lastPrice + (swing * this.random(-1, 10));
|
|
lastPrice = lastPrice < 0 ? 0 : lastPrice;
|
|
|
|
historicalData.splice(0, 0, ({
|
|
"date": this.formatDate(lastDate),
|
|
"price": lastPrice
|
|
}));
|
|
}
|
|
}
|
|
|
|
return historicalData;
|
|
}
|
|
}
|
|
|
|
const NASDAQ_SYMBOLS = [
|
|
"SNCL.L",
|
|
"RNK.L",
|
|
"SWJ.L",
|
|
"JDT.L",
|
|
"UANC.L",
|
|
"SDP.L",
|
|
"HSBA.L",
|
|
"XPL.L",
|
|
"KLR.L",
|
|
"SSE.L",
|
|
"JSI.L",
|
|
"UBMN.L",
|
|
"WPC.L",
|
|
"VTC.L",
|
|
"UTG.L",
|
|
"DOR.L",
|
|
"44RS.L",
|
|
"GPOR.L",
|
|
"ASL.L",
|
|
"40JP.L",
|
|
"133716",
|
|
"PJF.L",
|
|
"MLC.L",
|
|
"137817",
|
|
"GHE.L",
|
|
"PML.L",
|
|
"SBRY.L",
|
|
"LEN.L",
|
|
"STS.L",
|
|
"138654",
|
|
"PTEC.L"
|
|
];
|
|
|
|
const LSE_SYMBOLS = [
|
|
"PVG.L",
|
|
"SN.L,",
|
|
"SWJ.L",
|
|
"JDT.L",
|
|
"UANC.L",
|
|
"SDP.L",
|
|
"HSBA.L",
|
|
"XPL.L",
|
|
"KLR.L",
|
|
"SSE.L",
|
|
"JSI.L",
|
|
"UBMN.L",
|
|
"DLN.L",
|
|
"SIR.L",
|
|
"SEC.L",
|
|
"DOR.L",
|
|
"44RS.L",
|
|
"GPOR.L",
|
|
"ASL.L",
|
|
"40JP.L",
|
|
"133716",
|
|
"PJF.L",
|
|
"MLC.L",
|
|
"137817",
|
|
"GHE.L",
|
|
"PML.L",
|
|
"SBRY.L",
|
|
"LEN.L",
|
|
"MAV4.L",
|
|
"GLEN.L",
|
|
"EDGD.L",
|
|
];
|
|
|
|
const JSE_SYMBOLS = [
|
|
"ECV.L",
|
|
"MHN.L",
|
|
"SWJ.L",
|
|
"JDT.L",
|
|
"UANC.L",
|
|
"PLAZ.L",
|
|
"CLDN.L",
|
|
"XPL.L",
|
|
"KLR.L",
|
|
"SSE.L",
|
|
"JSI.L",
|
|
"UBMN.L",
|
|
"WPC.L",
|
|
"VTC.L",
|
|
"UTG.L",
|
|
"DOR.L",
|
|
"44RS.L",
|
|
"GPOR.L",
|
|
"ASL.L",
|
|
"40JP.L",
|
|
"133716",
|
|
"CRW.L",
|
|
"JPR.L",
|
|
"UTLC.L",
|
|
"GHS.L",
|
|
"PML.L",
|
|
"SBRY.L",
|
|
"LEN.L",
|
|
"STS.L",
|
|
"138654",
|
|
"RWS.L"
|
|
];
|
|
|
|
const DE_SYMBOLS = [
|
|
"ECV.L",
|
|
"MHN.L",
|
|
"SWJ.L",
|
|
"JDT.L",
|
|
"UANC.L",
|
|
"SDP.L",
|
|
"KBC.L",
|
|
"VM.L,",
|
|
"KLR.L",
|
|
"SSE.L",
|
|
"JSI.L",
|
|
"UBMN.L",
|
|
"WPC.L",
|
|
"VTC.L",
|
|
"UTG.L",
|
|
"DOR.L",
|
|
"44RS.L",
|
|
"GPOR.L",
|
|
"ASL.L",
|
|
"40JP.L",
|
|
"133716",
|
|
"PJF.L",
|
|
"MLC.L",
|
|
"DPV6.L",
|
|
"LMIN.L",
|
|
"PML.L",
|
|
"SBRY.L",
|
|
"LEN.L",
|
|
"STS.L",
|
|
"BKIR.L",
|
|
"AFMF.L",
|
|
];
|
|
|
|
const EXCHANGES = [
|
|
{name: 'Nasdaq Stock Market', symbol: 'NASDAQ', supportedStocks: NASDAQ_SYMBOLS},
|
|
{name: 'London Stock Exchange', symbol: 'LSE', supportedStocks: LSE_SYMBOLS},
|
|
{name: 'Japan Exchange Group', symbol: 'JSE', supportedStocks: JSE_SYMBOLS},
|
|
{name: 'Deutsche Börse', symbol: 'DE', supportedStocks: DE_SYMBOLS}
|
|
];
|