Skip to content

Commit

Permalink
Merge pull request #103 from Lercerss/riya/messagelist/54
Browse files Browse the repository at this point in the history
Message List #54
  • Loading branch information
chimano authored Nov 11, 2019
2 parents bf44413 + f0f0a9d commit e650263
Show file tree
Hide file tree
Showing 23 changed files with 496 additions and 160 deletions.
Binary file added CourseAdmin/GraphelierMilestone1Document.pdf
Binary file not shown.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ Displays detailed exchange order book contents
docker-compose build
docker-compose up
```

## README
- [Front-end README](https://github.com/Lercerss/graphelier/blob/master/app/README.md)
- [Back-end README](https://github.com/Lercerss/graphelier/blob/master/core/README.md)
88 changes: 21 additions & 67 deletions app/package-lock.json

Large diffs are not rendered by default.

201 changes: 201 additions & 0 deletions app/src/components/MessageList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
/* eslint-disable camelcase */
import React, { Component } from 'react';
import { withStyles } from '@material-ui/core/styles';
import { Box } from '@material-ui/core';
import classNames from 'classnames';
import OrderBookService from '../services/OrderBookService';
import {
SNAPSHOT_INSTRUMENT,
MESSAGE_LIST_DEFAULT_PAGE_SIZE,
} from '../constants/Constants';
import MultiDirectionalScroll from './MultiDirectionalScroll';
import { Styles } from '../styles/MessageList';
import { roundNumber } from '../utils/number-utils';
import { getMessageDirection } from '../utils/order-book-utils';
import { MESSAGE_TYPE_ENUM } from '../constants/Enums';
import { nanosecondsToString, splitNanosecondEpochTimestamp, epochToDateString } from '../utils/date-utils';

class MessageList extends Component {
constructor(props) {
super(props);

this.state = {
messages: [],
lastSodOffsetTop: BigInt(0),
lastSodOffsetBottom: BigInt(0),
};
}

componentDidMount() {
this.fetchInitialMessages();
}

componentDidUpdate(prevProps, prevState, snapshot) {
const { lastSodOffset } = this.props;
const { messages } = this.state;

if (typeof prevProps.lastSodOffset !== 'bigint' || (typeof lastSodOffset === 'bigint'
&& typeof prevProps.lastSodOffset === 'bigint'
&& lastSodOffset.toString() !== prevProps.lastSodOffset.toString())) {
const messageIndex = this.getPotentialIndexOfLastSodOffsetFromProps();
if (messageIndex !== -1) {
const messagesLength = messages.length;
const halfway = messagesLength / 2;
const directionOfPotentialPaging = (messageIndex > halfway) ? 'bottom' : 'top';
const diffFromEdge = directionOfPotentialPaging === 'bottom'
? messagesLength - messageIndex : messageIndex;

if (diffFromEdge < 5) {
this.handleHitEdge(directionOfPotentialPaging);
}
} else {
this.fetchInitialMessages();
}
}
}

/**
* @desc Helper that checks for existence of a message in the array of messages (from state)
* that equals the latest lastSodOffset passed in props.
* @returns {Number} returns -1 if not found
*/
getPotentialIndexOfLastSodOffsetFromProps = () => {
const { messages } = this.state;
const { lastSodOffset } = this.props;

if (!messages) return -1;

return messages
.map(message => message.sod_offset)
.findIndex(sodOffset => sodOffset === lastSodOffset.toString());
};

/**
* @desc function called to load initial message list; when there is no messages or the sodOffset is
* significantly different from the current one
*/
fetchInitialMessages = async () => {
const { lastSodOffset } = this.props;
const nMessages = MESSAGE_LIST_DEFAULT_PAGE_SIZE * 2 + 1;
const lowerSodOffset = lastSodOffset - BigInt(MESSAGE_LIST_DEFAULT_PAGE_SIZE - 1);

try {
const reverseMessagesResponse = await OrderBookService.getMessageList(
SNAPSHOT_INSTRUMENT,
lowerSodOffset.toString(),
nMessages,
);

const { messages, pageInfo } = reverseMessagesResponse.data;

this.setState({
messages,
lastSodOffsetTop: lowerSodOffset,
lastSodOffsetBottom: BigInt(pageInfo.sod_offset),
});
} catch (e) {
console.log(e);
}
};

/**
* @desc Paging handler for upwards and downwards hitting of the multidirectional scroll
* @param direction
*/
handleHitEdge(direction) {
const { lastSodOffsetTop, lastSodOffsetBottom } = this.state;
// eslint-disable-next-line react/destructuring-assignment
const existingMessages = this.state.messages;

if (direction === 'top') {
const nMessages = -MESSAGE_LIST_DEFAULT_PAGE_SIZE;
OrderBookService.getMessageList(SNAPSHOT_INSTRUMENT, lastSodOffsetTop.toString(), nMessages)
.then(response => {
const { pageInfo, messages } = response.data;
this.setState({
messages: messages ? messages.concat(existingMessages) : existingMessages,
lastSodOffsetTop: BigInt(pageInfo.sod_offset),
});
})
.catch(err => {
console.log(err);
});
} else if (direction === 'bottom') {
OrderBookService.getMessageList(SNAPSHOT_INSTRUMENT, lastSodOffsetBottom.toString())
.then(response => {
const { pageInfo, messages } = response.data;
this.setState({
messages: messages ? existingMessages.concat(messages) : existingMessages,
lastSodOffsetBottom: BigInt(pageInfo.sod_offset),
});
})
.catch(err => {
console.log(err);
});
}
}

/**
* @desc Renders every row of the message list table. every row corresponds to a message
* @returns {*}
*/
renderTableData() {
const { classes, lastSodOffset } = this.props;
const { messages } = this.state;
return messages.map(message => {
const {
timestamp, message_type, order_id, share_qty, price, direction, sod_offset,
} = message;

const { timeNanoseconds, dateNanoseconds } = splitNanosecondEpochTimestamp(timestamp);
const date = epochToDateString(dateNanoseconds);
const time = nanosecondsToString(timeNanoseconds);

return (
<Box
key={`${sod_offset} ${timestamp}`}
className={lastSodOffset.toString() === sod_offset
? classes.currentMessageRow
: classes.tableDataRow}
>
<Box className={classNames(classes.tableColumn, classes.overrideTimestampColumn)}>
{`${date} ${time}`}
</Box>
<Box className={classes.tableColumn}>{MESSAGE_TYPE_ENUM[message_type].name}</Box>
<Box className={classes.tableColumn}>{order_id}</Box>
<Box className={classes.tableColumn}>{share_qty}</Box>
<Box className={classes.tableColumn}>{roundNumber(price, 2)}</Box>
<Box className={classes.tableColumn}>{getMessageDirection(direction)}</Box>
</Box>
);
});
}

render() {
const { classes } = this.props;

return (
<div className={classes.scrollContainer}>
<Box className={classes.tableHeaderRow}>
<Box className={classNames(classes.tableColumn, classes.overrideTimestampColumn)}>
<div>{'Timestamp'}</div>
</Box>
<Box className={classes.tableColumn}><div>{'Type'}</div></Box>
<Box className={classes.tableColumn}><div>{'OrderID'}</div></Box>
<Box className={classes.tableColumn}><div>{'Quantity'}</div></Box>
<Box className={classes.tableColumn}><div>{'Price'}</div></Box>
<Box className={classes.tableColumn}><div>{'Direction'}</div></Box>
</Box>
<MultiDirectionalScroll
onReachBottom={() => this.handleHitEdge('bottom')}
onReachTop={() => this.handleHitEdge('top')}
position={50}
>
{this.renderTableData()}
</MultiDirectionalScroll>
</div>
);
}
}

export default withStyles(Styles)(MessageList);
20 changes: 18 additions & 2 deletions app/src/components/OrderBookSnapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
epochToDateString,
splitNanosecondEpochTimestamp,
convertNanosecondsToUTC,
convertNanosecondsUTCToCurrentTimezone,
} from '../utils/date-utils';
import TimestampOrderBookScroller from './TimestampOrderBookScroller';

Expand All @@ -28,6 +29,7 @@ import {
NANOSECONDS_IN_SIXTEEN_HOURS,
} from '../constants/Constants';
import { processOrderBookFromScratch, processOrderBookWithDeltas } from '../utils/order-book-utils';
import MessageList from './MessageList';


class OrderBookSnapshot extends Component {
Expand Down Expand Up @@ -147,6 +149,10 @@ class OrderBookSnapshot extends Component {
}
};

/**
* @desc handles the updates with deltas once a message is moved by a certain amount
* @param deltas
*/
handleUpdateWithDeltas = deltas => {
const { listItems } = this.state;
const {
Expand All @@ -161,8 +167,10 @@ class OrderBookSnapshot extends Component {
lastSodOffset: BigInt(last_sod_offset),
selectedDateNano: dateNanoseconds,
selectedDateString: epochToDateString(dateNanoseconds),
selectedTimeNano: BigInt(timeNanoseconds),
selectedTimeString: nanosecondsToString(timeNanoseconds),
selectedTimeNano: convertNanosecondsUTCToCurrentTimezone(BigInt(timeNanoseconds)),
selectedTimeString: nanosecondsToString(Number(convertNanosecondsUTCToCurrentTimezone(
BigInt(timeNanoseconds),
))),
selectedDateTimeNano: BigInt(timestamp),
listItems: newListItems,
maxQuantity: newMaxQuantity,
Expand Down Expand Up @@ -242,6 +250,7 @@ class OrderBookSnapshot extends Component {
type={'date'}
value={selectedDateString}
onChange={this.handleChangeDate}
InputProps={{ inputProps: { max: '2100-01-01' } }}
/>
</div>
<div className={classes.inline}>
Expand Down Expand Up @@ -294,6 +303,13 @@ class OrderBookSnapshot extends Component {
handleUpdateWithDeltas={this.handleUpdateWithDeltas}
/>
</Card>
{(lastSodOffset !== null) && (
<Card className={classes.messageListCard}>
<MessageList
lastSodOffset={lastSodOffset}
/>
</Card>
)}
</Typography>
);
}
Expand Down
25 changes: 14 additions & 11 deletions app/src/components/TimestampOrderBookScroller.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ class TimestampOrderBookScroller extends Component {
}

shouldComponentUpdate(nextProps, nextState, nextContext) {
const { listItems, maxQuantity } = this.props;

if (!listItems || !nextProps.listItems) return true;
return (!listItemsEquals(listItems, nextProps.listItems)
|| maxQuantity !== nextProps.maxQuantity);
const { lastSodOffset } = this.props;
if (lastSodOffset && nextProps.lastSodOffset) {
return (lastSodOffset.toString() !== nextProps.lastSodOffset.toString());
}
return true;
}

componentDidUpdate(prevProps, prevState, snapshot) {
Expand All @@ -43,10 +43,13 @@ class TimestampOrderBookScroller extends Component {
window.removeEventListener('keyup', this.onKeyUp);
}

/**
* @desc handles the key up action while moving messages
* @param e
*/
onKeyUp = e => {
const { handleGoToPreviousMessage, handleGoToNextMessage } = this.props;
if (e.keyCode === LEFT_ARROW_KEY_CODE) handleGoToPreviousMessage();
else if (e.keyCode === RIGHT_ARROW_KEY_CODE) handleGoToNextMessage();
if (e.keyCode === LEFT_ARROW_KEY_CODE) this.handleGoToPreviousMessage();
else if (e.keyCode === RIGHT_ARROW_KEY_CODE) this.handleGoToNextMessage();
};

/**
Expand Down Expand Up @@ -79,7 +82,6 @@ class TimestampOrderBookScroller extends Component {

/**
* @desc Gets the message for the given offset and updates the order book with the message's timestamp
*
* @param offset The number of messages to skip forward or backward to
*/
handleGoToMessageByOffset = offset => {
Expand All @@ -94,9 +96,10 @@ class TimestampOrderBookScroller extends Component {
};

render() {
const { listItems, maxQuantity, classes } = this.props;
const {
listItems, maxQuantity, classes, timeOrDateIsNotSet,
} = this.props;
const quantityBoxSize = maxQuantity + maxQuantity * (MIN_PERCENTAGE_FACTOR_FOR_BOX_SPACE);
const { timeOrDateIsNotSet } = this.props;

return (
<Box className={classes.container}>
Expand Down
1 change: 1 addition & 0 deletions app/src/constants/Constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const RIGHT_ARROW_KEY_CODE = 39;

export const SNAPSHOT_TIMESTAMP = '2012-06-21 13:32:40Z';
export const SNAPSHOT_INSTRUMENT = 'SPY';
export const MESSAGE_LIST_DEFAULT_PAGE_SIZE = 20;

export const NANOSECONDS_IN_NINE_AND_A_HALF_HOURS = 34200000000000;
export const NANOSECONDS_IN_SIXTEEN_HOURS = 57600000000000;
Expand Down
17 changes: 17 additions & 0 deletions app/src/constants/Enums.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const MESSAGE_TYPE_ENUM = {
1: {
name: 'New order',
},
2: {
name: 'Modify',
},
3: {
name: 'Delete',
},
4: {
name: 'Execute',
},
5: {
name: 'Ignore',
},
};
3 changes: 3 additions & 0 deletions app/src/services/OrderBookService.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ export default class OrderBookService {

static getPriceLevelsByMessageOffset = (instrument, timestamp, offset) => httpClient
.get(`/delta/${instrument}/${timestamp}/${offset}`);

static getMessageList = (instrument, sodOffset, nMessages = 20) => httpClient
.get(`/messages/${instrument}/${sodOffset}?nMessages=${nMessages}`);
}
2 changes: 2 additions & 0 deletions app/src/styles/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export const Colors = {
green: '#159027',
red: '#943e30',
darkGrey: '#343434',
black: '#000000',
yellow: '#ffff00',
};

export const LightThemeColors = {
Expand Down
Loading

0 comments on commit e650263

Please sign in to comment.