Trading system 004 --- trading signal pub/sub system

 

Introduction

This is a private project for me to practice how to set up a web service in a micro-service architecture. In this article, I will reveal how do I create a 2-tiers web service on AWS managed by k8s. I have already show you how I construct the k8s cluster and services stack on AWS in those previous articles. Now, it is time to focus on the trading system itself.

Due to the complexity and the use case, I will leverage the functionality of another online trading platform --- QuantConnect and build a general trading signal publish and subscribe platform instead of building a trading system from scratch. In the following paragraphs, you will learn the reason behind this project, difficulties of this project and the implementation details of some key features.

Background

What is QuantConnect

Quantconnect is a web service providing basic trading abilities. Although it is a nice online platform providing:

  • market data accessibility
  • broker connections
  • back-testing environment
  • and cloud hosting

, I hit the limitations of the platform when I want to implement some serious strategies including:

  • trade reasoning — event sourcing
  • strategy performance measuring
  • back-testing with different strategies combinations

This makes me try to build a trading platform on top of QC over a micro service architecture. In the following paragraphs, I will explain in details about the system architecture, implementation details and the difficulties.

What is the final outcome of the system

From the above diagram, one can see a strategy network. The trading decisions are juggled by multiple level 1 strategies. The number of levels of the strategy network could be more than 1. The lower level will distill the trading decisions from the upper level and create their own decisions and pass them to another lower level. Finally, the trading decisions will hit the brokers and the communication will be handled by the strategy in the bottom level.

The limitation of the platform

Here I hit a couple of limitations which are beyond the abilities of QC the online trading platform.

  • No way to combine the output of strategies from one level to the other level
  • No way to back-test the performance of combined strategy
  • No way to trace the trades, it makes us no way to get the trades reasons and debug the strategy

How to resolve the limitations

To get the features I needed, I will add components on top of the QC trading platform. Those new components should provide me:

  • The abilities of trades recording
  • Play back trade records to QC platform for back-testing
  • Included trade reasons for each trades so that I can analysis the contribution of different strategies

In order words, I will need a transaction chain in a database contained all the trade records.

Implementation

System overview 👀

It is a 2 tiers web application managed by Kops on AWS. It can be divided into two parts by the GWs, the outside and inside of the system. Among all the services, API GW is the communication hub, which handles stateful and stateless communication. The technologies it uses include:

  • grpc api — RR pattern for internal communication
  • restful api — RR pattern
  • zmq — streaming data

The contents of the message include:

  • trades
  • orders
  • strategy insights
  • calculation insights
  • configuration
  • customized data

Transaction chain

To provide the ability for reasoning the trades, a transaction chain is needed.

  • CI — It is an insight provided by user within their strategy. User can put all the necessary information for signal calculation in this json file.
  • SI — It is an insight generated by the strategy. It provide a standardized interface for different trading platforms.
  • Order — It is a record sending to broker for trade execution.
  • Trade — It is a record receiving from broker after a transaction is made.

There is an id for each record to link them together in the database. As a result, researcher can recursively go through the whole path of the records for trade reasoning — a methodology called event sourcing. For example, given a trade record, researcher or trader can always trace back the related calculation insight so that they can understand how that trade is created.

Data layer

Behind the API GW, there are several services set to link up the application layer and the data layer.

In short, Eagle is a service stacks up to getting data from databases. Falcon, Magpie, Tweety are services putting data into databases.

How to identify the stock symbol — the immutable ID

To process stock market data, we need a way to identify the underlying instrument. It is one of the hardest and most fundamental problem. To identify a stock, people are using Bloomberg code, Reuters code (Refinitiv / ric), ISIN, CUSIP, Figi, ticker and exchange etc.. The problem is that those codes could be reused or changed from time to time. It is not suitable for program or database to manage the data especially for a trading system that can be used for back-testing.

We need an immutable identifier to resolve this issue. According to the online trading platform, QC, they use a symbol ID to overcome this problem. Putting the ticker name in the symbol ID is not a very nice solution but at least they provided a key for me to leverage on their solution as a starting point.

Distributed cache 👀

In this system, a distributed data cache is used. Here is the data cache structure in the following diagram.

Here is a simplified version of data cache I am using. There is a manager sitting at the root of the tree to control and direct the data into the correct data cache. From the cop header, the sender is the key to the data cache.

There is 2 types of page — strategy and security. With the separation of them, we are able to record the data on strategy (account) level and instrument level.

To point to the correct security page, the immutable id is needed, it is very important to be an immutable id since we need to use it for live trading and back-testing.

Cops — the smallest communication unit 👀

The data between those cache are flowing between different applications through cops. Each application has its own data cache and the data stream links everything together and keep the data in the latest snapshot. This forms a data / event driven distributed system.

Cops is a structure data with all the information that the data that can be stored in the data cache. It is the smallest unit in our system. With the fid number living in the cops, this encapsulate the data protocol from the business logic. As a result, we can keep the protocol while changing business logic. The idea is similar to the Fix protocol.

A cop consists a header and a body. Multiple fid numbers exist in the cop body to carry more data and reduce the overhead introduced by the header.

We have generic data type — int, double, string. There is timestamp following with each of them so that those data can be used for backtesting also. Fid number represents the meaning of the data, instead of adding new variable to the cop, we can dynamically update the data meaning by adding fid number. In this way, we can expand our system without changing the underlying protocol.

Immutable ID continue — When stock name is changed👀

With the immutable id, we are not only able to link an instrument with different names but also able to convert instrument into different codes for example from BBG to ric or ISIN.

Let’s have a closer look to the immutable id to understand how important it is and how to use it. The following example is used to show how the immutable id link the same instrument with different stock name.

There are 3 applications on the above diagram. Application A will get the latest snapshot of the stock codes relationships from the database. Updated data will be selected by comparing with the local cache and will be sent out from A to B and C.

Here is an example:

Application A will get these data frame from the database

+---------------+-----------+---------------+------------+------------+
|   system id   | code type |     code      | start date |  end date  |
+---------------+-----------+---------------+------------+------------+
| eq.jdivhChd9v | ticker    | aaa           | 2000-01-01 | 2099-01-01 |
| eq.jdivhChd9v | ticker    | aaab          | 1995-01-01 | 1999-12-31 |
| eq.ardchChd9a | bbg       | ccc us equity | 2000-01-01 | 2099-01-01 |
+---------------+-----------+---------------+------------+------------+

Assume the data for aaab is already inside the cache of application A, here is the data sent out from application A

+---------------+-----------+---------------+------------+------------+
|   system id   | code type |     code      | start date |  end date  |
+---------------+-----------+---------------+------------+------------+
| eq.jdivhChd9v | ticker    | aaa           | 2000-01-01 | 2099-01-01 |
| eq.ardchChd9a | bbg       | ccc us equity | 2000-01-01 | 2099-01-01 |
+---------------+-----------+---------------+------------+------------+

The other applications will update this information in their cache. Once we have the cache, we can convert stock from BBG to ric or isin and different components inside our system can communicate with each other with a common identifier.

system_code = local_cache.get_system_code("5 HK Equity", today_date)
# system_code is "0005.HK"

At least one communication — background cycle

This distributed system is an at least one communication system.

With the background cycle created from the sender, streaming data in the system is at least once communication.

Final

Related to the software development cycle and tools, for example CI/CD pipeline, K8s configurations are not included in this story. You may find some information from the list below.

This is just a private project and there is no intention to ask for investment. If you find that this is useful, you may need to create it by yourself. 😃

Comments