Skip to content

Commit

Permalink
Merge pull request #20 from justinkx/development
Browse files Browse the repository at this point in the history
Trades
  • Loading branch information
justinkx authored May 10, 2021
2 parents 2aa09ba + 96a0443 commit 85e4486
Show file tree
Hide file tree
Showing 18 changed files with 334 additions and 45 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A React-Native crypto trade platform implementation making use of [binance-api](

iOS ✔️ ❤️ Android ✔️

<img src="https://user-images.githubusercontent.com/28846043/117526509-5006ec00-afe3-11eb-9b23-d53e0f23145e.gif" width="400" />
[![Watch the video](https://user-images.githubusercontent.com/28846043/117689156-1b5b8600-b1d7-11eb-98f4-3a77a54f39e3.jpeg)](https://user-images.githubusercontent.com/28846043/117688729-adaf5a00-b1d6-11eb-840a-7ba69916667b.mp4)

## How To Run

Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"main": "node_modules/expo/AppEntry.js",
"name": "crypto-connect",
"version": "0.1.5",
"version": "1.0.0",
"scripts": {
"start": "expo start",
"android": "expo start --android",
Expand Down Expand Up @@ -29,7 +29,6 @@
"react-native-reanimated": "~2.1.0",
"react-native-safe-area-context": "^3.2.0",
"react-native-screens": "~3.0.0",
"react-native-skeleton-placeholder": "^4.0.0",
"react-native-svg": "^12.1.1",
"react-native-tab-view": "^2.16.0",
"react-native-toast-message": "^1.4.9",
Expand Down
40 changes: 20 additions & 20 deletions src/components/Tickers/TickerItemLoader.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import React, { memo } from "react";
import { StyleSheet, View } from "react-native";
import SkeletonPlaceholder from "react-native-skeleton-placeholder";
import { StyleSheet, View, useWindowDimensions } from "react-native";
import ContentLoader, { Rect } from "react-content-loader/native";

import { colors } from "../../style/GlobalStyle";

const TickerItemLoader = ({}) => {
const { width } = useWindowDimensions();
return (
<View style={styles.wrapper}>
<SkeletonPlaceholder>
<View style={styles.container}>
<View style={styles.icon} />
<View style={styles.nameContainer}>
<View style={styles.title} />
<View style={styles.volume} />
<View style={styles.value} />
</View>
<View style={styles.mainContainer}>
<View style={[styles.title, { width: 120 }]} />
<View style={[styles.volume, { width: 75 }]} />
<View style={styles.value} />
</View>
<View style={styles.changeView}>
<View style={styles.changeBtn} />
</View>
</View>
</SkeletonPlaceholder>
<ContentLoader
speed={1}
width={width}
height={50}
viewBox={`0 0 ${width} 50`}
backgroundColor={colors.tabIndicator}
foregroundColor="#ecebeb"
>
<Rect x="0" y="5" rx="5" ry="5" width="35" height="35" />
<Rect x="50" y="3" rx="3" ry="3" width="70" height="14" />
<Rect x="50" y="25" rx="3" ry="3" width="55" height="13" />
<Rect x={"35%"} y="3" rx="3" ry="3" width="100" height="14" />
<Rect x={"35%"} y="25" rx="3" ry="3" width="70" height="13" />
<Rect x={width - 80} y="15" rx="4" ry="4" width="55" height="28" />
</ContentLoader>
</View>
);
};
Expand Down
67 changes: 67 additions & 0 deletions src/components/Trades/Trades.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React, { memo } from "react";
import { StyleSheet, Text, View } from "react-native";
import { format } from "date-fns";

import { colors } from "../../style/GlobalStyle";
import { fixedDecimals } from "../../helpers/number.helpers";

const Trades = ({ trade }) => {
const { price, quantity, tradeTime, isMarketOrder } = trade;
return (
<View
style={[
styles.row,
isMarketOrder ? styles.bidBackground : styles.askBackground,
]}
>
<View style={styles.contentView}>
<Text style={[styles.value, isMarketOrder ? styles.bid : styles.ask]}>
{`${isMarketOrder ? "\u2191" : "\u2193"} ${price}`}
</Text>
</View>
<View style={[styles.contentView, styles.alignEnd]}>
<Text style={styles.value}>{fixedDecimals(quantity, 3)}</Text>
</View>
<View style={[styles.contentView, styles.alignEnd]}>
<Text style={styles.value}>{format(tradeTime, "H:mm:ss")}</Text>
</View>
</View>
);
};

export default memo(Trades);

const styles = StyleSheet.create({
row: {
flexDirection: "row",
width: "100%",
justifyContent: "space-between",
alignItems: "center",
height: 30,
paddingHorizontal: 10,
borderBottomWidth: StyleSheet.hairlineWidth,
borderBottomColor: colors.white,
},
bidBackground: {
backgroundColor: colors.bidBackground,
},
askBackground: {
backgroundColor: colors.askBackground,
},
contentView: { width: "30%" },
value: {
fontSize: 13,
fontWeight: "bold",
color: "#474d57",
},

alignEnd: {
alignItems: "flex-end",
},
bid: {
color: colors.tradeGreen,
},
ask: {
color: colors.tradeRed,
},
});
2 changes: 2 additions & 0 deletions src/helpers/number.helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const fixedDecimals = (value, decimalPlaces = 2) =>
parseFloat(value).toFixed(decimalPlaces);
11 changes: 11 additions & 0 deletions src/redux/action/trades.action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import {
INITIALIZE_TRADES_CHANNEL,
SAVE_TRADES,
RESET_TRADES_CHANNEL,
} from "./types";

export const initializeChannel = () => ({ type: INITIALIZE_TRADES_CHANNEL });

export const saveTrades = (trades) => ({ type: SAVE_TRADES, trades });

export const resetTradesChannel = () => ({ type: RESET_TRADES_CHANNEL });
4 changes: 4 additions & 0 deletions src/redux/action/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ export const HIDE_TOAST = "HIDE_TOAST";
export const INITIALIZE_BOOK_CHANNEL = "INITIALIZE_BOOK_CHANNEL";
export const RESET_BOOK_CHANNEL = "RESET_BOOK_CHANNEL";
export const SAVE_BOOK = "SAVE_BOOK";
// trades
export const INITIALIZE_TRADES_CHANNEL = "INITIALIZE_TRADES_CHANNEL";
export const SAVE_TRADES = "SAVE_TRADES";
export const RESET_TRADES_CHANNEL = "RESET_TRADES_CHANNEL";
59 changes: 59 additions & 0 deletions src/redux/adaptor/trades.adaptor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export const tradesAdaptor = (data) => {
const {
e: eventType, // Event type
E: eventTime, // Event time
s: symbol, // Symbol
t: tradeId, // Trade ID
p: price, // Price
q: quantity, // Quantity
b: buyerOrderId, // Buyer order ID
a: sellerOrderId, // Seller order ID
T: tradeTime, // Trade time
m: isMarketOrder, // Is the buyer the market maker?
M: ignore, // Ignore
} = data;
return {
eventType,
eventTime,
symbol,
tradeId,
price,
quantity,
buyerOrderId,
sellerOrderId,
tradeTime,
isMarketOrder,
ignore,
};
};

export const transformedTrade = (data) => {
const {
eventType,
eventTime,
symbol,
tradeId,
price,
quantity,
buyerOrderId,
sellerOrderId,
tradeTime,
isMarketOrder,
ignore,
} = tradesAdaptor(data);
return {
[tradeTime]: {
eventType,
eventTime,
symbol,
tradeId,
price,
quantity,
buyerOrderId,
sellerOrderId,
tradeTime,
isMarketOrder,
ignore,
},
};
};
17 changes: 17 additions & 0 deletions src/redux/reducer/trades.reduces.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import _assign from "lodash/assign";

import { SAVE_TRADES, RESET_TRADES_CHANNEL } from "../action/types";

const initialState = {};

export default function tradesReducer(state = initialState, action) {
const { trades, type } = action;
switch (type) {
case SAVE_TRADES:
return trades;
case RESET_TRADES_CHANNEL:
return initialState;
default:
return state;
}
}
2 changes: 2 additions & 0 deletions src/redux/rootReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import tickerReducer from "./reducer/ticker.reducer";
import tickerPairReducer from "./reducer/tickerPair.reducer";
import ToastReducer from "./reducer/toast.reducer";
import bookReducer from "./reducer/book.reducer";
import tradesReducer from "./reducer/trades.reduces";

const rootReducer = combineReducers({
ticker: tickerReducer,
tickerPair: tickerPairReducer,
toast: ToastReducer,
book: bookReducer,
trades: tradesReducer,
});

export default rootReducer;
2 changes: 2 additions & 0 deletions src/redux/rootSaga.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import tickerPairSaga from "./saga/tickerPair.saga";
import websocketConnectionSaga from "./saga/ws.connection.saga";
import wsMessageSaga from "./saga/ws.message.saga";
import bookSaga from "./saga/book.saga";
import tradesSaga from "./saga/trades.saga";

export default function* rootSaga() {
yield spawn(websocketConnectionSaga);
yield spawn(wsMessageSaga);
yield spawn(tickerSaga);
yield spawn(tickerPairSaga);
yield spawn(bookSaga);
yield spawn(tradesSaga);
}
5 changes: 3 additions & 2 deletions src/redux/saga/tickerPair.saga.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { put, takeLatest, select } from "redux-saga/effects";
import { put, takeLatest, select, all } from "redux-saga/effects";
import { send } from "@giantmachines/redux-websocket";

import { SET_TICKER_PAIR, RESET_TICKER_PAIR } from "../action/types";
import { startTickerPairSocket } from "../action/tickerPair.action";
import { getSelectedPair } from "../selectors/tickerPair.selector";
import { initializeBook } from "../action/book.action";
import { initializeChannel } from "../action/trades.action";

function* tickerPairListenerSaga() {
yield put(startTickerPairSocket());
Expand All @@ -17,7 +18,7 @@ function* tickerPairListenerSaga() {
id: 2,
})
);
yield put(initializeBook());
yield all([put(initializeBook()), put(initializeChannel())]);
}
}
function* tickerResetSaga() {
Expand Down
40 changes: 40 additions & 0 deletions src/redux/saga/trades.saga.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { put, takeLatest, select } from "redux-saga/effects";
import { send } from "@giantmachines/redux-websocket";

import {
INITIALIZE_TRADES_CHANNEL,
RESET_TRADES_CHANNEL,
RESET_TICKER_PAIR,
} from "../action/types";
import { getSelectedPair } from "../selectors/tickerPair.selector";

function* tradesListenerSaga() {
const pair = yield select(getSelectedPair);
if (pair) {
yield put(
send({
method: "SUBSCRIBE",
params: [`${pair}@trade`],
id: 10,
})
);
}
}

function* tradesResetSaga() {
const pair = yield select(getSelectedPair);
if (pair) {
yield put(
send({
method: "UNSUBSCRIBE",
params: [`${pair}@trade`],
id: 102,
})
);
}
}

export default function* tradesSaga() {
yield takeLatest(INITIALIZE_TRADES_CHANNEL, tradesListenerSaga);
yield takeLatest([RESET_TRADES_CHANNEL, RESET_TICKER_PAIR], tradesResetSaga);
}
27 changes: 21 additions & 6 deletions src/redux/saga/ws.message.saga.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { put, select, takeEvery, all } from "redux-saga/effects";
import _assign from "lodash/assign";
import _identity from "lodash/identity";
import _pickBy from "lodash/pickBy";
import _keys from "lodash/keys";
import _findLast from "lodash/findLast";

import {
MESSAGE,
Expand All @@ -20,6 +22,9 @@ import { savePairData } from "../action/tickerPair.action";
import { showSuccessToast, showErrorToast } from "../action/toast.action";
import { transformBook } from "../adaptor/book.adaptor";
import { getReducer } from "../selectors/book.selector";
import { transformedTrade } from "../adaptor/trades.adaptor";
import { getTradesReducer } from "../selectors/trades.selector";
import { saveTrades } from "../action/trades.action";

function* reduxWebsocketMessage(action) {
const { payload } = action;
Expand All @@ -30,18 +35,16 @@ function* reduxWebsocketMessage(action) {
switch (parsedMessage?.stream) {
case "!miniTicker@arr":
const tickers = tickerTransform(parsedMessage.data);
yield put(saveTickers(tickers));
break;
return yield put(saveTickers(tickers));
case `${pair}@ticker`:
const data = tickerPairAdaptor(parsedMessage.data);
yield put(savePairData(data));
break;
return yield put(savePairData(data));
case `${pair}@depth@1000ms`:
const { ask, bid } = yield select(getReducer);
const book = transformBook(parsedMessage.data);
const _ask = _pickBy(_assign({}, ask, book.ask), _identity);
const _bid = _pickBy(_assign({}, bid, book.bid), _identity);
yield put(
return yield put(
saveBook({
bid: _bid,
ask: _ask,
Expand All @@ -50,7 +53,19 @@ function* reduxWebsocketMessage(action) {
finalUpdateId: book.finalUpdateId,
})
);
break;
case `${pair}@trade`:
const newTrades = transformedTrade(parsedMessage.data);
const currTrades = yield select(getTradesReducer);
const tradeKeys = _keys(currTrades);
if (tradeKeys.length === 25) {
const replaceKey = _findLast(tradeKeys);
delete currTrades[replaceKey];
const nextTrades = _assign({}, newTrades, currTrades);
return yield put(saveTrades(nextTrades));
} else {
const nextTrades = _assign({}, newTrades, currTrades);
return yield put(saveTrades(nextTrades));
}
}
} catch (error) {
console.log("error", error);
Expand Down
Loading

0 comments on commit 85e4486

Please sign in to comment.