diff --git a/src-trader-dashboard/actions/fxDataActions.js b/src-trader-dashboard/actions/fxDataActions.js
new file mode 100644
index 0000000..4023207
--- /dev/null
+++ b/src-trader-dashboard/actions/fxDataActions.js
@@ -0,0 +1,16 @@
+import cloneDeep from "lodash/cloneDeep";
+
+export function fxDataUpdated(fxData) {
+ return {
+ type: 'FX_DATA_CHANGED',
+ fxData: cloneDeep(fxData)
+ };
+}
+
+export function fxTopMoversUpdated(fxTopMovers) {
+ return {
+ type: 'FX_TOP_MOVERS_CHANGED',
+ fxTopMovers: cloneDeep(fxTopMovers)
+ };
+}
+
diff --git a/src-trader-dashboard/actions/fxDataUpdated.js b/src-trader-dashboard/actions/fxDataUpdated.js
deleted file mode 100644
index 6f38f48..0000000
--- a/src-trader-dashboard/actions/fxDataUpdated.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import cloneDeep from "lodash/cloneDeep";
-
-export default function fxDataUpdatedAction(fxData) {
- return {
- type: 'FX_DATA_CHANGED',
- fxData: cloneDeep(fxData)
- };
-}
-
diff --git a/src-trader-dashboard/components/FxPanel.jsx b/src-trader-dashboard/components/FxPanel.jsx
new file mode 100644
index 0000000..db3582b
--- /dev/null
+++ b/src-trader-dashboard/components/FxPanel.jsx
@@ -0,0 +1,26 @@
+import React, {Component} from "react";
+
+import FxDataService from "../services/FxDataService.jsx";
+import FxQuoteMatrix from "./FxQuoteMatrix.jsx";
+import TopMoversGrid from "./TopMoversGrid.jsx";
+
+export default class extends Component {
+ constructor(props) {
+ super(props);
+
+ this.fxDataService = new FxDataService();
+ }
+
+ render() {
+ return (
+
+ );
+ }
+};
diff --git a/src-trader-dashboard/components/FxQuoteMatrix.jsx b/src-trader-dashboard/components/FxQuoteMatrix.jsx
index 6637b4e..74fd33c 100644
--- a/src-trader-dashboard/components/FxQuoteMatrix.jsx
+++ b/src-trader-dashboard/components/FxQuoteMatrix.jsx
@@ -5,18 +5,13 @@ import {AgGridReact} from "ag-grid-react";
import assign from "lodash/assign";
import uniq from "lodash/uniq";
-import cloneDeep from "lodash/cloneDeep";
-
-import FxDataService from "../services/FxDataService.jsx";
class FxQuoteMatrix extends Component {
constructor(props) {
super(props);
- this.fxDataService = new FxDataService();
-
this.state = {
- columnDefs: this.fxDataService.getFxMatrixHeaderNames()
+ columnDefs: this.props.fxDataService.getFxMatrixHeaderNames()
};
// grid events
@@ -28,48 +23,43 @@ class FxQuoteMatrix extends Component {
this.columnApi = params.columnApi;
}
+ componentDidMount() {
+ this.gridApi.setRowData(this.props.rowData);
+ }
+
componentWillReceiveProps(nextProps) {
- if (!this.gridApi) {
- return;
- }
+ const newRowData = nextProps.rowData;
- if (!this.props.rowData ||
- this.props.rowData.length === 0) {
- this.gridApi.setRowData(nextProps.rowData);
- } else {
- const newRowData = nextProps.rowData;
+ const updatedNodes = [];
+ const updatedCols = [];
- const updatedNodes = [];
- const updatedCols = [];
+ for (let i = 0; i < newRowData.length; i++) {
+ // note that for this use case we assume the existing and new row data have the same
+ // row and column order
+ let node = this.gridApi.getModel().getRow(i);
+ let newRow = newRowData[i];
- for (let i = 0; i < newRowData.length; i++) {
- // note that for this use case we assume the existing and new row data have the same
- // row and column order
- let node = this.gridApi.getModel().getRow(i);
- let newRow = newRowData[i];
+ const {data} = node;
+ let updated = false;
+ for (const def of this.state.columnDefs) {
+ if (data[def.field] !== newRow[def.field]) {
+ updatedCols.push(def.field);
- const {data} = node;
- let updated = false;
- for (const def of this.state.columnDefs) {
- if (data[def.field] !== newRow[def.field]) {
- updatedCols.push(def.field);
-
- updated = true;
- }
- }
- if(updated) {
- assign(data, newRow);
- updatedNodes.push(node);
+ updated = true;
}
}
-
- this.gridApi.refreshCells(updatedNodes, uniq(updatedCols));
+ if(updated) {
+ assign(data, newRow);
+ updatedNodes.push(node);
+ }
}
+
+ this.gridApi.refreshCells(updatedNodes, uniq(updatedCols));
}
render() {
return (
-
-
+
);
diff --git a/src-trader-dashboard/components/TopMoversGrid.jsx b/src-trader-dashboard/components/TopMoversGrid.jsx
new file mode 100644
index 0000000..61873fa
--- /dev/null
+++ b/src-trader-dashboard/components/TopMoversGrid.jsx
@@ -0,0 +1,105 @@
+import React, {Component} from "react";
+import {connect} from "react-redux";
+
+import {AgGridReact} from "ag-grid-react";
+
+class TopMoversGrid extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ columnDefs: [
+ {
+ field: 'symbol',
+ headerName: 'Symbol'
+ },
+ {
+ field: 'last',
+ headerName: 'Last',
+ headerClass: 'align-right',
+ cellRenderer: 'animateShowChange',
+ cellClass: 'align-right'
+ },
+ {
+ field: 'net',
+ headerName: 'Net',
+ headerClass: 'align-right',
+ cellRenderer: 'animateShowChange',
+ cellClass: 'align-right'
+ },
+ {
+ field: 'pct_net_change',
+ headerName: '% NC',
+ headerClass: 'align-right',
+ cellRenderer: 'animateShowChange',
+ cellClass: 'align-right'
+ },
+ ]
+ };
+
+ // grid events
+ this.onGridReady = this.onGridReady.bind(this);
+ }
+
+ onGridReady(params) {
+ this.gridApi = params.api;
+ this.columnApi = params.columnApi;
+ }
+
+ componentDidMount() {
+ this.gridApi.setRowData(this.props.rowData);
+ this.gridApi.sizeColumnsToFit();
+ }
+
+ componentWillReceiveProps(nextProps) {
+ let newRowData = nextProps.rowData;
+ let model = this.gridApi.getModel();
+
+ // remove nodes not in new data set
+ let nodesToRemove = [];
+ for (let rowIndex = 0; rowIndex < model.getRowCount(); rowIndex++) {
+ let rowNode = model.getRow(rowIndex);
+ if (rowNode.data.symbol !== newRowData[rowIndex].symbol) {
+ nodesToRemove.push(rowNode);
+ }
+ }
+ this.gridApi.removeItems(nodesToRemove);
+
+ // add new items in set
+ for (let rowIndex = 0; rowIndex < newRowData.length; rowIndex++) {
+ let model = this.gridApi.getModel();
+ let rowNode = model.getRow(rowIndex);
+
+ if (!rowNode ||
+ rowNode.data.symbol !== newRowData[rowIndex].symbol) {
+ this.gridApi.insertItemsAtIndex(rowIndex, [newRowData[rowIndex]])
+ }
+ }
+ }
+
+ render() {
+ return (
+
+ );
+ }
+}
+
+export default connect(
+ (state) => {
+ return {
+ rowData: state.fxTopMovers
+ }
+ }
+)(TopMoversGrid);
\ No newline at end of file
diff --git a/src-trader-dashboard/reducers/fxData.js b/src-trader-dashboard/reducers/fxData.js
deleted file mode 100644
index e0a9b1f..0000000
--- a/src-trader-dashboard/reducers/fxData.js
+++ /dev/null
@@ -1,10 +0,0 @@
-export default (state = {fxData: []}, action) => {
- switch (action.type) {
- case 'FX_DATA_CHANGED':
- return {
- fxData: action.fxData.slice(0)
- };
- default:
- return state;
- }
-};
\ No newline at end of file
diff --git a/src-trader-dashboard/reducers/fxDataReducer.jsx b/src-trader-dashboard/reducers/fxDataReducer.jsx
new file mode 100644
index 0000000..3dfb7f2
--- /dev/null
+++ b/src-trader-dashboard/reducers/fxDataReducer.jsx
@@ -0,0 +1,16 @@
+export default (state = {fxData: [], fxTopMovers: []}, action) => {
+ switch (action.type) {
+ case 'FX_DATA_CHANGED':
+ return {
+ ...state,
+ fxData: action.fxData.slice(0),
+ };
+ case 'FX_TOP_MOVERS_CHANGED':
+ return {
+ ...state,
+ fxTopMovers: action.fxTopMovers.slice(0),
+ };
+ default:
+ return action.state;
+ }
+};
\ No newline at end of file
diff --git a/src-trader-dashboard/services/FxDataService.jsx b/src-trader-dashboard/services/FxDataService.jsx
index af21292..f5beb5f 100644
--- a/src-trader-dashboard/services/FxDataService.jsx
+++ b/src-trader-dashboard/services/FxDataService.jsx
@@ -1,7 +1,8 @@
import sampleSize from "lodash/sampleSize";
+import cloneDeep from "lodash/cloneDeep";
import StoreService from "./StoreService";
-import fxDataUpdated from "../actions/fxDataUpdated";
+import {fxDataUpdated, fxTopMoversUpdated} from "../actions/fxDataActions";
import HorizontalBarComponent from "../components/renderers/HorizontalBarComponent.jsx";
@@ -9,14 +10,32 @@ export default class {
constructor() {
this.store = StoreService.STORE;
- this.fxData = this.createFxMatrixSnapshot();
+ this.createFxMatrixSnapshot();
+ this.calculateTopMovers();
this.store.dispatch(fxDataUpdated(this.fxData));
+ this.store.dispatch(fxTopMoversUpdated(this.fxTopMovers));
setInterval(() => {
this.applyDeltasToFxData();
+
this.store.dispatch(fxDataUpdated(this.fxData));
- }, 1500)
+ }, 1500);
+
+ setInterval(() => {
+ this.calculateTopMovers();
+
+ this.store.dispatch(fxTopMoversUpdated(this.fxTopMovers));
+ }, 2500);
+ }
+
+ calculateTopMovers() {
+ let fxData = cloneDeep(this.fxData);
+ fxData.sort((a, b) => {
+ return Math.abs(b.pct_net_change) - Math.abs(a.pct_net_change);
+ });
+
+ this.fxTopMovers = fxData.slice(0, 20);
}
applyDeltasToFxData() {
@@ -34,7 +53,7 @@ export default class {
row['pct_net_change'] = multiplier * (this.random(30, 100) / 100).toFixed(2);
for (let symbolToUpdate of fxSymbolsToUpdate) {
- if(row[symbolToUpdate] === null) {
+ if (row[symbolToUpdate] === null) {
continue;
}
@@ -42,7 +61,6 @@ export default class {
row[symbolToUpdate] = (multiplier * Math.random()).toFixed(2);
}
}
- return this.fxData;
}
random(min, max) {
@@ -75,7 +93,8 @@ export default class {
rowData.push(row);
}
- return rowData;
+
+ this.fxData = rowData;
}
}
@@ -169,251 +188,3 @@ const FX_CURRENCY_MATRIX = [
["CHFJPY", "0.60", "0.96", "0.22", "0.52", "0.63", "0.24", "0.76", "0.91", "0.80", "-0.14", "0.59", "-0.01", "0.60", "-0.08", "0.63", "0.39", "0.30", "-0.71", "0.18", "0.94", "-0.30", "0.28", "0.87", "0.92", "-0.19", "0.18", "-0.40", "0.44", "0.88", "0.52", "0.50", "0.44", "0.84", "0.24", "0.09", "0.15", "0.10", "0.30", "-0.05", "0.79", null, "0.99"],
["CHFCAD", "0.93", "0.24", "0.05", "0.40", "0.75", "0.12", "0.90", "0.56", "0.46", "0.62", "0.71", "0.01", "0.04", "0.02", "0.49", "0.51", "-0.94", "0.93", "0.80", "-0.40", "0.79", "0.82", "-0.04", "0.38", "-0.24", "-0.17", "0.78", "-0.42", "0.92", "-0.83", "-0.01", "0.54", "0.73", "0.52", "0.95", "0.44", "0.90", "0.81", "0.42", "0.26", "0.36", null]
];
-
-// for (i = 0; i < MAJOR_CURRENCIES.length; i++) {
-// for (j = 0; j < MAJOR_CURRENCIES.length; j++) {
-// if (i === j) {
-// continue;
-// }
-// console.log(`${MAJOR_CURRENCIES[i] + MAJOR_CURRENCIES[j]}`);
-// }
-// }
-
-// let rows = [];
-// let row = ['Symbol'];
-//
-// rows = [];
-// for (j = 0; j < FX_CURRENCY_CODE.length; j++) {
-// row.push(FX_CURRENCY_CODE[j]);
-// }
-// rows.push(row);
-//
-// for (i = 0; i < FX_CURRENCY_CODE.length; i++) {
-// let row = [];
-// row.push(FX_CURRENCY_CODE[i]);
-//
-// for (j = 0; j < FX_CURRENCY_CODE.length; j++) {
-// if (i === j) {
-// row.push(null);
-// } else {
-// var mutliplier = ((Math.random() * 10) > 8) ? -1 : 1;
-// row.push((mutliplier * Math.random()).toFixed(2))
-// // row.push(`${FX_CURRENCY_CODE[j]} value`)
-// }
-// }
-//
-// rows.push(row);
-// }
-//
-// JSON.stringify(rows);
-//
-// const FX_CURRENCY_CODE = [
-// 'USDGBP',
-// 'USDEUR',
-// 'USDAED',
-// 'USDJPY',
-// 'USDCAD',
-// 'USDCHF',
-// 'GBPUSD',
-// 'GBPEUR',
-// 'GBPAED',
-// 'GBPJPY',
-// 'GBPCAD',
-// 'GBPCHF',
-// 'EURUSD',
-// 'EURGBP',
-// 'EURAED',
-// 'EURJPY',
-// 'EURCAD',
-// 'EURCHF',
-// 'AEDUSD',
-// 'AEDGBP',
-// 'AEDEUR',
-// 'AEDJPY',
-// 'AEDCAD',
-// 'AEDCHF',
-// 'JPYUSD',
-// 'JPYGBP',
-// 'JPYEUR',
-// 'JPYAED',
-// 'JPYCAD',
-// 'JPYCHF',
-// 'CADUSD',
-// 'CADGBP',
-// 'CADEUR',
-// 'CADAED',
-// 'CADJPY',
-// 'CADCHF',
-// 'CHFUSD',
-// 'CHFGBP',
-// 'CHFEUR',
-// 'CHFAED',
-// 'CHFJPY',
-// 'CHFCAD'
-// ];
-//
-// const MAJOR_CURRENCIES = [
-// 'USD',
-// 'GBP',
-// 'EUR',
-// 'AED',
-// 'JPY',
-// 'CAD',
-// 'CHF'];
-//
-// const SECONDARY_CURRENCIES = [
-// 'AFN',
-// 'ALL',
-// 'AMD',
-// 'ANG',
-// 'AOA',
-// 'ARS',
-// 'AUD',
-// 'AWG',
-// 'AZN',
-// 'BAM',
-// 'BBD',
-// 'BDT',
-// 'BGN',
-// 'BHD',
-// 'BIF',
-// 'BMD',
-// 'BND',
-// 'BOB',
-// 'BRL',
-// 'BSD',
-// 'BTN',
-// 'BWP',
-// 'BYN',
-// 'BZD',
-// 'CDF',
-// 'CLP',
-// 'CNY',
-// 'COP',
-// 'CRC',
-// 'CUC',
-// 'CUP',
-// 'CVE',
-// 'CZK',
-// 'DJF',
-// 'DKK',
-// 'DOP',
-// 'DZD',
-// 'EGP',
-// 'ERN',
-// 'ETB',
-// 'FJD',
-// 'FKP',
-// 'GEL',
-// 'GGP',
-// 'GHS',
-// 'GIP',
-// 'GMD',
-// 'GNF',
-// 'GTQ',
-// 'GYD',
-// 'HKD',
-// 'HNL',
-// 'HRK',
-// 'HTG',
-// 'HUF',
-// 'IDR',
-// 'ILS',
-// 'IMP',
-// 'INR',
-// 'IQD',
-// 'IRR',
-// 'ISK',
-// 'JEP',
-// 'JMD',
-// 'JOD',
-// 'KES',
-// 'KGS',
-// 'KHR',
-// 'KMF',
-// 'KPW',
-// 'KRW',
-// 'KWD',
-// 'KYD',
-// 'KZT',
-// 'LAK',
-// 'LBP',
-// 'LKR',
-// 'LRD',
-// 'LSL',
-// 'LYD',
-// 'MAD',
-// 'MDL',
-// 'MGA',
-// 'MKD',
-// 'MMK',
-// 'MNT',
-// 'MOP',
-// 'MRO',
-// 'MUR',
-// 'MVR',
-// 'MWK',
-// 'MXN',
-// 'MYR',
-// 'MZN',
-// 'NAD',
-// 'NGN',
-// 'NIO',
-// 'NOK',
-// 'NPR',
-// 'NZD',
-// 'OMR',
-// 'PAB',
-// 'PEN',
-// 'PGK',
-// 'PHP',
-// 'PKR',
-// 'PLN',
-// 'PYG',
-// 'QAR',
-// 'RON',
-// 'RSD',
-// 'RUB',
-// 'RWF',
-// 'SAR',
-// 'SBD',
-// 'SCR',
-// 'SDG',
-// 'SEK',
-// 'SGD',
-// 'SHP',
-// 'SLL',
-// 'SOS',
-// 'SPL',
-// 'SRD',
-// 'STD',
-// 'SVC',
-// 'SYP',
-// 'SZL',
-// 'THB',
-// 'TJS',
-// 'TMT',
-// 'TND',
-// 'TOP',
-// 'TRY',
-// 'TTD',
-// 'TVD',
-// 'TWD',
-// 'TZS',
-// 'UAH',
-// 'UGX',
-// 'UYU',
-// 'UZS',
-// 'VEF',
-// 'VND',
-// 'VUV',
-// 'WST',
-// 'XAF',
-// 'XCD',
-// 'XDR',
-// 'XOF',
-// 'XPF',
-// 'YER',
-// 'ZMW',
-// 'ZWD',
-// 'ZAR'];
diff --git a/src-trader-dashboard/services/StoreService.js b/src-trader-dashboard/services/StoreService.js
index 909981d..a18d310 100644
--- a/src-trader-dashboard/services/StoreService.js
+++ b/src-trader-dashboard/services/StoreService.js
@@ -1,6 +1,6 @@
import {createStore} from "redux";
-import fxData from "../reducers/fxData";
+import fxData from "../reducers/fxDataReducer.jsx";
class StoreService {
}