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.

HomeDownloadsfrontend-training-mainmyTradingApplicationappstilesrcTile trading

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.

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.

  1. 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";
  2. 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 side BUY and Ask uses the side SELL. 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?

  3. 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}
        />
      );
  4. 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)
  5. 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>
  )
}