I created this technical article for Signals company in cooperation with their marketing team and the original post is published on Signals Tech Blog.
Signals is a platform where developers and professional traders can write their trading models in a cloud environment, using our own framework based in C#. Because the technological aspects of the platform are important for the user base, I wrote this article to clear out the design and used technologies.
Since we started developing Signals Platform, we have continued to dedicate extra focus to the core component used for defining your trading models — the Signals Framework. Our goal was to make the library so stable that a new version of the library would only need to be released when we extend the API with some features beneficial to clients. We knew we couldn’t force our traders to upgrade their framework version every time we improve the performance of some method or fix some bug. On the other hand, we also wanted the framework to be so flexible, that it would support various data streams and indicator combinations without modification of the library code itself. This article shows how we applied hexagonal architecture principles to solve the described challenges.
The key to achieving stability and flexibility was to make the Signals Framework library as lightweight as possible. In hexagonal architecture terms, the library basically represents the Signals Network’s domain with models like strategy, data series, market, exchange, currency pair, various types of orders, data marketplace and indicators marketplace. Regarding the domain logic, we have only added methods which are related to building automated trading strategies. There are functions for setting up the strategy data and indicators, registering callbacks executed on data stream updates, entering and exiting market positions, logging, etc. An important part of the domain is also strategy properties like actual market time, current positions and pending orders, which are used for making trading decisions.
You may wonder how we could claim that the library is lightweight when there are methods like EnterLongLimit or properties like Time in the strategy base class.
Surely, there must be some code responsible for creating order and publishing it all the way to the UI, as well as some logic behind the Time property,
which synchronizes strategy time with all the data streams. You are right, of course, but the actual code executing the logic lives outside of the Signals Framework library
code base. In the library, we just depend on ports’ interfaces, as you can see in the code snippet below.
In this preview of strategy base class, IStrategyTimeProvider
, IStrategyOrdersManager
, and IStrategyLogger
are all just ports held in the Signals Framework codebase. The classes implementing the interfaces (the adapters) are located outside of the library, inside the Strategy Execution Service codebase. Basically, we are using Inversion of Control to keep the framework library clean from all the infrastructure dependencies, as the ports are just interfaces declared within the Signals Framework.
Adapters are the ones implementing the actual business logic and referencing database drivers, messaging broker client, logging providers, etc.
Ports and adapters keep the domain functions inside Signals Framework really small, as they just contain code passing the parameters to the ports. This approach was suitable for methods which are already part of the Signals Framework API. But we needed a different solution to support various subscription models on top of data and indicators marketplaces. We didn’t want to release a new version of the Signals Framework library every time a new technical indicator or a new type of data stream is introduced to the marketplaces.
More importantly, we wanted to avoid dependency from the framework’s package to various data streams and indicators modules. On the contrary, we needed to reference the Signals Framework library from those packages, to use DataSeries, Markets, and other domain models. For that reason, we decided that each indicator and data stream will have its own library, containing its domain models definitions. When the user wants to use it, they must add a reference to the library in the strategy code editor.
In the following code, you can see a hypothetical implementation of a Google Trends data stream, which could be used for sentiment analysis on the given market.
It contains definitions of GoogleTrend
and GoogleTrendOptions
domain models.
There is also an implementation of DataMarketPlace
extension method GoogleTrends(TimeSpan trendStartOffset, string currency)
,
which provides a nice interface for strategy developer to specify the Google Trends stream in one marketPlace.GoogleTrends(5 years, “BTC”)
API call.
As you probably noticed, again, there is no implementation of connecting to a real data stream source.
The code that depends on third-party providers lies in the data stream driver assembly, which is directly referenced by the Strategy Execution Service.
This way, strategies just contain references to data streams’ domain models and we can upgrade the data stream driver implementation any time,
without worrying if there is some change which will negatively affect implemented strategies.
In the end, the architecture of Signals Framework toolkit has come to look like this.
As you can see, the Signals Framework assembly, data streams assemblies, and indicators assemblies do not depend on any infrastructure components. This keeps them lightweight, without dependencies on other packages, with just domain models code. The data stream drivers and adapters inside the Strategy Execution Service are the ones which cope with the infrastructure code and reference all the modules needed for messaging, database and API handling.
The main benefits of this design are:
I created this technical article for Signals company in cooperation...
I created this technical article for Signals company in cooperation...
Many of us use the async/await feature in C# projects,...