Trading from a tile
This tutorial aims to teach you how to configure your application to trade using the hooks trading library, and how to submit a trade message from the Tile component.
First we have to prepare our application to enable the button click and pass on the action. Then we will combine it with the hooks trading library to interact with the backend and execute trades.
Objectives
-
Enable your application to trade using the hooks trading library.
-
Enable your Tile component to submit a trade message.
Handling button clicks in the Tile and Rate components
React provides an easy way for us to handle button clicks via the onClick
prop that can be passed to button elements.
<button onClick={() => {/* click handler */}}>Click me</button>
We need to consider what we want to happen when the button is clicked. We need to access the Trading API and execute a trade.
The executed trade needs to be populated with the data currently displayed in the tile such as the currency pair, side, and current price. That data comes from the StreamLink API.
Passing click handler props down
We don’t want this logic in the Tile
or Rate
components as we want these to be concerned with presentation only. This behaviour should be handled in the SLJSTile
component instead.
One common way to have events in a child component (like the button click in Rate
) handled by a parent component (like SLJSTile
) is to pass the handler function down as a prop through the component tree.
Tile
and Rate
can be modified to have this additional handler prop, which SLJSTile
can pass down to them. Rate
needs to accept a new executeTrade
function as a prop which is called when the button is clicked. This is called with the current rate and the side clicked as function parameters.
import React from "react";
export default function Rate({side, rate, executeTrade}) {
return (
<div className={`Tile-rate Tile-rate-${side}`}>
<label>{side}</label>
<button onClick={executeTrade}>{rate}</button>
</div>
);
}
Tile
also needs to accept an executeTrade
prop for both bid and ask sides, which are passed on to Rate
. The Tile
also needs to add an additional parameter, the currency pair, which is used to generate the submit message.
import React from "react";
import "./Tile.less";
import Rate from "./Rate";
export default function Tile({currencyPair, bidRate, askRate, executeTradeBid, executeTradeAsk}) {
return (
<div className="Tile">
<h1 className="Tile-currency">{currencyPair}</h1>
<Rate side="Bid" rate={bidRate} executeTrade={executeTradeBid}/>
<Rate side="Ask" rate={askRate} executeTrade={executeTradeAsk}/>
</div>
);
}
SLJSTile
can now create the handler functions and pass them down to Tile
, so it is notified when a user clicks a trade button.
import React, { useEffect, useState } from "react";
import Tile from "./Tile";
export default function SLJSTile({currencyPair, streamLink}) {
const [rate, setRate] = useState({
bidRate: "-",
askRate: "-",
});
useEffect(() => {
const subscription = streamLink.subscribe(`/FX/${currencyPair}`, {
onRecordUpdate: (subscription, event) => {
const fields = event.getFields();
setRate({
bidRate: fields.BidPrice,
askRate: fields.AskPrice,
});
},
onSubscriptionStatus(event) {
console.log("subscribed:" + event);
},
onSubscriptionError(event) {
console.log("error:" + event);
},
});
// Specify how to clean up after this effect:
return () => {
if (subscription) {
subscription.unsubscribe();
console.log("unsubscribe");
}
};
}, []);
function onExecuteTrade( side, rate) {
console.log(`Execute trade for: ${currencyPair} - ${side} @ ${rate}`);
}
return (
<Tile
currencyPair={currencyPair}
bidRate={rate.bidRate}
askRate={rate.askRate}
executeTradeBid={()=> onExecuteTrade("Bid", rate.bidRate)}
executeTradeAsk={()=> onExecuteTrade("Ask", rate.askRate)}
/>
);
}
Reload your application and click the buttons. When you do, you should see messages logged in the console telling you which button was clicked and at what price. For example:
> Execute trade for: EURUSD - Bid @ 1.54211 > Execute trade for: USDJPY - Bid @ 110.54057 > Execute trade for: GBPUSD - Bid @ 1.68731 > Execute trade for: GBPUSD - Ask @ 1.69824
Executing a trade
Now we will add all the code needed to execute a trade tile using the Redux trading library to interact with the backend.
Defining a trading model
Create a new folder named trading under apps/tile/src/tile
.
Create and save a new file called ESPTradeModel.js
in apps/tile/src/Tile/trading
with the following content:
import { SOURCE } from "caplin-constants";
const { CLIENT, SERVER } = SOURCE;
export const INITIAL = "Initial";
export const EXECUTING = "Executing";
export const TRADE_COMPLETE = "TradeComplete";
export const OPEN = "Open";
export const CONFIRM = "Confirm";
export const SUBJECT = "/PRIVATE/TRADE/FX";
export default {
name: "ESP",
initialState: INITIAL,
states: {
[INITIAL]: {
transitions: {
[OPEN]: {
target: EXECUTING,
source: CLIENT,
},
},
},
[EXECUTING]: {
transitions: {
[CONFIRM]: {
target: TRADE_COMPLETE,
source: SERVER,
},
},
},
[TRADE_COMPLETE]: {},
},
};
Creating the submit event
Now we’re going to create the submit event handler
import { OPEN } from "./ESPTradeModel";
import {useTradeEvent} from "common-library-react";
export const useSubmitEvent = (
currencyPair,
dealtCurrency,
termCurrency,
buySell,
amount,
rate
) => {
const sendSubmit = useTradeEvent(OPEN);
return () => {
console.log("Submitting trade currencyPair:", currencyPair, "side:", buySell, "rate:", rate)
return sendSubmit({
TradingProtocol: "ESP",
CurrencyPair: currencyPair,
DealtCurrency: dealtCurrency,
BaseCurrency: dealtCurrency,
TermCurrency: termCurrency,
BuySell: buySell,
Amount: amount,
Price: rate,
});
};
};
In events.js
we define the OPEN action as the client sends an OPEN message to the server as part of the transaction from INITIAL to EXECUTING state.
useTradeEvent
returns a function that will publish a message to the server with the provided fields with the action from the specified from the trade model.
Setting up streamlink provider
The trading library accesses the streamlink instance through the streamlink context. Normally this would be provided by the caplin context. For now we can create the streamlink provider ourselves. Add the following to your index.js
:
import React from "react";
import ReactDOM from "react-dom";
import SLJSTile from "./tile/SLJSTile";
import "./index.less";
import {
StreamLinkFactory
} from "sljs";
import {StreamLinkProvider} from "@caplin/core";
const streamLink = StreamLinkFactory.create({
username: "admin",
password: "admin",
liberator_urls: "http://localhost:18080"
});
streamLink.connect();
function startApp() {
ReactDOM.render
(
<StreamLinkProvider value={streamLink}>
<div>
<SLJSTile ... />
</div>
</StreamLinkProvider>,
document.getElementById("root")
);
}
startApp();
if (module.hot) {
module.hot.accept();
}
The StreamLinkProvider
takes our streamlink instance and makes it accessible anywhere in it’s child components. This allows the trading provider to access it directly.
Adding the Trading provider
In order to use the hooks trading api we need to wrap our tile in the trading provider. This sets up a context to store information relating to the trade, such as the RequestID and the current trade state.
Remove the default keyword from the SLJSTile component and add the following code.
import {TradeProvider} from "common-library-react";
import ESPTradeModel from "./trading/ESPTradeModel";
export default function SLJSTileWithTrading(props) {
return (
<TradeProvider tradeModel={ESPTradeModel}>
<SLJSTile {...props}/>
</TradeProvider>
)
}
The trading provider takes our trade model as a parameter to setup the trade state.
Executing a trade
Now we’ll attach the logic to our tile to execute the trade.
-
We need to import the following dependencies:
import React, {useContext, useEffect, useState} from "react"; import Tile from "./Tile"; import {TradeProvider} from "common-library-react"; import {useSubmitEvent} from "./trading/events"; import ESPTradeModel from "./trading/ESPTradeModel"; import useSubscriptionLogging from "./utils/useSubscriptionLogging"; import {RequestIdContext} from "common-library-react/src/hooks/trading/TradeProvider";
-
Then we use our new submit builder to create the execute handler.
const baseCurrency = currencyPair.substr(0, 3); const termCurrency = currencyPair.substr(3, 3); const bidSubmit = useSubmitEvent(currencyPair, baseCurrency, termCurrency, "BUY", "500", rate.bidRate); const askSubmit = useSubmitEvent(currencyPair, baseCurrency, termCurrency, "SELL", "500", rate.askRate);
Take a look at the information we’re passing to the useSubmitEvent hook.
Bid
uses the sideBUY
andAsk
uses the sideSELL
. And each use their respective rates.We’ve had to break up the currency pair into dealt and term currency for the extra fields the backend needs.
We’ve set the amount to always be 500. How could we make this selectable by the user?
-
Then we change the tile’s execute handlers to be the ones we’ve just created
return ( <Tile currencyPair={currencyPair} bidRate={rate.bidRate} askRate={rate.askRate} executeTradeBid={bidSubmit} executeTradeAsk={askSubmit} /> );
-
This is enough to have our trade execute successfully, but we don’t currently have a way of seeing when that happens. Use the logging util at the end of the SLJSTile component so that it’s trade events are printed to the console.
const requestId = useContext(RequestIdContext); useSubscriptionLogging(ESPTradeModel.subject, streamLink, requestId)
-
Refresh the front end and execute a trade. What do you see?
Check your console logs for the following:
// submit a trade events.js:32 Submitting trade currencyPair: GBPUSD side: BUY rate: 1.69826 // response useSubscriptionloggin.js:79 Streamlink event: /PRIVATE/TRADE/FX {MsgType: 'Confirm'...}
Resetting the trade
Currently the trade isn’t reset after completion. Try submitting two trades in a row. You should see an error message when doing so.
The hooks trading library provides a useResetTrade hook to help us reset the trade ready for the next one. Add the following code to the SLJSTile component to reset the trade when it’s completed.
import {TradeProvider, useResetTrade, useTradeState} from "common-library-react";
import ESPTradeModel, {TRADE_COMPLETE} from "./trading/ESPTradeModel";
const askSubmit = useSubmitEvent(currencyPair, baseCurrency, termCurrency, "SELL", "500", rate.askRate);
const tradeState = useTradeState();
const resetTrade = useResetTrade();
useEffect(()=> {
if(tradeState === TRADE_COMPLETE){
console.log("Trade completed resetting trade")
resetTrade();
}
}, [tradeState])
const requestId = useContext(RequestIdContext);
useSubscriptionLogging(ESPTradeModel.subject, streamLink, requestId)
return (
<Tile
currencyPair={currencyPair}
bidRate={rate.bidRate}
askRate={rate.askRate}
executeTradeBid={bidSubmit}
executeTradeAsk={askSubmit}
/>
)
-
The useTradeState returns the current state that the trade model is in
-
We use the use effect hook with a dependency array of
[tradeState]
so it is only called when the trade changes state. -
We check that the current trade state is Complete and if it is, we call the resetTrade function
Try submitting two trades again. They should now work.
Next we want to add another useEffect
method that creates recycleTrade
when we received a TradeConfirmation
for an executed trade. See in the dependencies we only look at changes for tradeState
and tradeId
in this useEffect
call.
Full code example
import React, {useContext, useEffect, useState} from "react";
import Tile from "./Tile";
import {TradeProvider, useResetTrade, useTradeState} from "common-library-react";
import ESPTradeModel, {TRADE_COMPLETE} from "./trading/ESPTradeModel";
import {useSubmitEvent} from "./trading/events";
import useSubscriptionLogging from "./utils/useSubscriptionLogging";
import {RequestIdContext} from "common-library-react/src/hooks/trading/TradeProvider";
function SLJSTile({currencyPair, streamLink}) {
const [rate, setRate] = useState({
bidRate: "-",
askRate: "-",
});
useEffect(() => {
const subscription = streamLink.subscribe(`/FX/${currencyPair}`, {
onRecordUpdate: (subscription, event) => {
const fields = event.getFields();
setRate({
bidRate: fields.BidPrice,
askRate: fields.AskPrice,
});
},
onSubscriptionStatus(event) {
console.log("subscribed:" + event);
},
onSubscriptionError(event) {
console.log("error:" + event);
},
});
// Specify how to clean up after this effect:
return () => {
if (subscription) {
subscription.unsubscribe();
console.log("unsubscribe");
}
};
}, []);
const baseCurrency = currencyPair.substr(0, 3);
const termCurrency = currencyPair.substr(3, 3);
const bidSubmit = useSubmitEvent(currencyPair, baseCurrency, termCurrency, "BUY", "500", rate.bidRate);
const askSubmit = useSubmitEvent(currencyPair, baseCurrency, termCurrency, "SELL", "500", rate.askRate);
const tradeState = useTradeState();
const resetTrade = useResetTrade();
useEffect(()=> {
if(tradeState === TRADE_COMPLETE){
console.log("Trade completed resetting trade")
resetTrade();
}
}, [tradeState])
const requestId = useContext(RequestIdContext);
useSubscriptionLogging(ESPTradeModel.subject, streamLink, requestId)
return (
<Tile
currencyPair={currencyPair}
bidRate={rate.bidRate}
askRate={rate.askRate}
executeTradeBid={bidSubmit}
executeTradeAsk={askSubmit}
/>
);
}
export default function SLJSTileWithTrading(props) {
return (
<TradeProvider tradeModel={ESPTradeModel}>
<SLJSTile {...props}/>
</TradeProvider>
)
}