Introduction
Here, I present the design of a standalone desktop application that contains independently running services and a responsive user interface aimed at running a trading strategy. The specific use of the Mediator pattern is the keystone in this construction and could be recycled for building other types of concurrent systems.
Features
- Receive and process live quotes
- Develop buy and sell signals according to a trading strategy
- Initiate buy and sell orders with trading venue
- Listen for and receive confirmation of trade execution
- Monitor trading activity with a responsive user interface
- Archive quotes, trades and logs for post trade analysis
Background
This project was started with the idea of running a trend-following trading strategy on the Bitcoin/Euro exchange rate. It was geared to use the trading venue Kraken.com which at the time offered the most advanced types of orders. Since no profits were ever recorded from running this program, I added a mock Exchange client so that people could run it without having to open an account at Kraken. The original Kraken API client is also provided and if you already have an account, you can add the security keys in the application's configuration file and try some real trading.
The trading game plan applied is a trend following strategy developed by George Kleinman and baptized the Natural Numbers Method (N#M). It uses the Weighted Moving Average (WMA) of the price to identify trends and relies on the empirical observation that markets behave differently at prices that end with zero; what Kleinman calls Natural Numbers. When the price crosses the moving average (Setup), we expect that a new trend is forming. A Setup is confirmed if the market then opens above (below) the highest (lowest) price of the Setup bar. Upon confirmation, we place an order at the next Natural Number. This process works in both directions.
Design
The base for the project is a WPF application that leverages the Mediator pattern to build loosely coupled and independently running components that are not aware of each other, but that can communicate in real time. They communicate by sending messages to the Mediator which proceeds to call all the methods that registered for these messages. In this regard, much was taken from the MVVM Disciples (http://wpfdisciples.wordpress.com/).
The main screen of the application is itself fragmented into independent panels that communicate with the other services through the Mediator. We will refer to this ensemble of panels as a single UI Component.
The other components of the application each serve a specific role in the trading system. The QuoteService
fetches trade data from the exchange, packages it at regular time intervals and sends a message to the mediator notifying that data has arrived. The StrategyService
, which has registered to that specific message, processes the data and decides when to open or close positions and when to place or shift stoploss orders. When such events occur, the StrategyService
sends the appropriate message to the Mediator where the BrokerageService
registered its interest. The BrokerageService
places orders with the exchange, listens until they are canceled or closed, and informs the Mediator whenever the status of an order is altered. The StrategyService
registers to messages sent from the BrokerageService
to keep track of the number of contracts that are effectively trading. Throughout this process, the UI Component listens to messages originating from all the trading services and updates tables, graphs and a log console for the user to follow the trading activity.
Mediator and ObservableObject
The components which are linked to the Mediator
are wrapped in an ObservableObject
class which carries a static
instance of the Mediator
. Methods to be registered for a given message are decorated with an attribute which specifies which message to subscribe to and the type of parameter the method expects. When a component needs to notify other components of an event, it calls the method NotifyColleagues
of the Mediator
with the message in question and the data object attached with it. Upon reception of the message, the Mediator
proceeds to call all the methods registered for that message.
For example:
The StrategyService
registers a method for the NewPriceData
message that runs the new data through the core algorithm and eventually alters the trading position.
[MediatorMessageSink(MediatorMessages.NewPriceData, ParameterType = typeof(PriceData))]
public void NewPriceDataReceived(PriceData priceData)
{
TreatNewPriceData(priceData);
}
The UIComponent
also registers a method for the NewPriceData
message in order to update the graph when new price points arrive.
[MediatorMessageSink(MediatorMessages.NewPriceData, ParameterType = typeof(PriceData))]
public void UpdateGraphData(PriceData chartData)
{
App.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(
() =>
{
candleStickCollection.Add(chartData.CandleStick);
wmaCollection.Add(chartData.WmaPoint);
}));
}
The message originates from the QuoteService
when it has processed a new batch of trade data.
Mediator.NotifyColleagues<PriceData>(MediatorMessages.NewPriceData,
new PriceData(candleStick, WMAPoints.Last(), true));
Composable Parts and MEF
The parts of the system that communicate with the trading venue and with the backend data store have been abstracted behind corresponding interfaces. This allows for greater testability and to eventually extend the system to use other third party services. The dependency injection is done with Managed Extensibility Framework (MEF).
Exchange Clients
The IExchangeClient
interface represents a point of entry to the trading venue. It defines a set of functions that need to be called by other components in the system but hides the actual details. So far, there are two client implementations: KrakenClient
which is the real client for the Kraken
API and KrakenClientMock
which simulates the trading activity.
The use of Managed Extensibility Framework (MEF) allows handling different clients like plugins. Any class that implements IExchangeClient
and which is decorated with the appropriate attribute will be identified and featured in the list of possible clients.
The mapping and matching work is done in the MEFLoader
class. The ControlViewModel
uses MEFLoader
to populate the list of exchanges. When an exchange is selected, an instance of a corresponding exchange-client is created and passed to the parts of the application which communicate with the trading venue (QuoteService
and BrokerageService
).
Data Repositories
In order to allow for post-trading analysis, the application was initially set up to store objects in a database. It has been extended to add the option of mock repositories that do not actually do any database operations. The use of either method can be configured in the configuration file by specifying mock or database in RepositoryConfigurationType
setting:
<appSettings>
<add key="KrakenBaseAddress" value="https://api.kraken.com" />
<add key="KrakenApiVersion" value="0" />
<add key="KrakenKey" value="xxx" />
<add key="KrakenSecret" value="xxx"/>
<add key="KrakenClientMaxRetries" value="5" />
<add key="PositionEpsilon" value="0.0000025"/>
<add key="RepositoryConfigurationType" value="mock"/>
</appSettings>
Services which need to access the data repositories get them indirectly through the MEFLoader
which looks at the configuration file to know which implementation to choose from.
QuoteService
The QuoteService
is a real-time component that fetches trade data from the venue, packages it into candlesticks (open, high, low, and close), calculates the WMA and relays this data to the Mediator
. The venue in question is an instance of IExchangeClient
. It can be the real KrakenClient
or the KrakenClientMock
which emulates the behavior of the online exchange.
The Natural Numbers Method relies on the Weighted Moving Average (WMA) which is calculated using past data. For the N period WMA, it is necessary to know the last N points. The CatchUpWithConnector
method of the QuoteService
instructs the exchange client to fetch trade data that goes back far enough to calculate the WMA. This allows starting the strategy right away instead of having to wait N periods. However, Kraken
does not allow querying trade data that goes back too far. Upon starting the application, one would have to wait N periods before being able to calculate the first WMA point and initiate trading. If N is high and if the period is long, this is a problem. I have written a bot, separate from this trading application, which archives trades from Kraken
along with a WebService
that allows querying this archive. The KrakenClient
fetches data from this WebService
while KrakenClientMock
fetches it from a local cache of simulated trades.
The QuoteService
also registers a method for the StartQuoteBot
message which is triggered from the ControlViewModel
after CatchUpWithConnector
has completed. The registered method launches the core functionality of the QuoteService
(Run()
) in a separate thread. The Run
method instantiates a timer and the callback function to execute when the timer elapses. When the timer elapses, the callback function will be executed in a separate thread. The callback function loads recent prices from the trading venue, calculates the weighted moving average, notifies other components that new data has arrived and resets the timer.
Finally, the Stop
method, which registers for the StopQuoteBot
message, stops the timer and sends the StopStrategyService
message to the Mediator
.
StrategyService
The StrategyService
contains the core algorithm behind the trading strategy. It listens to incoming data from the QuoteService
, runs this data through the algorithm and eventually signals the BrokerageService
to take action. By also listening to messages from the BrokerageService
, it keeps track of the trading position with the exchange. When signaled to stop, it will order the BrokerageService
to close any open position.
Avoiding Message Acknowledgment Loops
The trading method only allows for one position at a time, be it long or short. Until this limit is reached, the trading algorithm signals orders in response to market conditions. Therefore, it is important to keep track of the actual position with the exchange. However, trade orders are placed asynchronously by the BrokerageService
which is completely independent from the StrategyService
. The variable OngoingContracts
keeps track of the position and can only be modified by the function which registers for the Mediator Message UpdateOngoingContracts
. This message is sent by the BrokerageService
whenever an order goes through with the exchange.
[MediatorMessageSink(MediatorMessages.UpdateOngoingContracts, ParameterType = typeof(decimal))]
public void UpdateOngoingContracts(decimal ongoingContractsIncrement)
{
lock (OngoingContractsLock)
{
OngoingContracts += ongoingContractsIncrement;
if (Math.Abs(OngoingContracts) < PositionEpsilon)
{
OngoingContracts = 0;
}
}
}
Time elapses between the moment the StrategyService
sends an order to the moment it receives an order execution acknowledgment in the form of the UpdateOngoingContracts
message. If the position counter were adjusted only upon receiving execution acknowledgments, the system would keep spewing out orders during the time the order is being executed, potentially resulting in too many orders being placed. One solution would be to keep two counters: one for sent in orders and one for executed orders. The solution used in the system at hand is to enforce the limit directly in the BrokerageService
by guaranteeing that only one opening order be placed at a given time. Placing an opening order while another one is already placed requires canceling the first one before sending out the new one.
BrokerageService
The BrokerageService
monitors the position of the trading system and places orders through the IExchangeClient
. It is able to follow the status of an order until it is executed or canceled at which points it notifies the Mediator
. It responds to the three instructions OpenPosition
, ClosePosition
and ShiftStopLoss
.
Position
In our system, a position is the combination of an opening order, a closing order and an emergency order. Two opening orders cannot coexist and a closing order cannot be placed until the corresponding opening order is closed. This solves the common Message Acknowledgment Loop pitfall. The emergency order is a market order that mirrors the opening order and that can be placed to exit a position at any time.
The private Position
class, defined inside the BrokerageService
class, exposes three properties for the OpeningOrder
, the ClosingOrder
and the EmergencyOrder
. In the set methods of these properties, we call the UpdateOngoingContracts
method.
Order openingOrder;
public Order OpeningOrder
{
get
{
return openingOrder;
}
set
{
openingOrder = value;
if(value!=null)
{
_brokerageService.Mediator.NotifyColleagues<Order>
(MediatorMessages.UpdateOrder, openingOrder);
}
UpdateOngoingContracts(openingOrder);
}
}
UpdateOngoingContracts
sends a message to the Mediator signifying that the number of ongoing contracts has changed.
private void UpdateOngoingContracts(Order order)
{
if (order != null && order.VolumeExecuted.HasValue)
{
int sign = 0;
switch (order.Type)
{
case "buy":
sign = 1;
break;
case "sell":
sign = -1;
break;
}
decimal ongoingContractsIncrement = sign * order.VolumeExecuted.Value;
_brokerageService.Mediator.NotifyColleagues<decimal>
(MediatorMessages.UpdateOngoingContracts, ongoingContractsIncrement);
}
}
OpenPosition
The BrokerageService
registers the method OpenPositionReceived
for the Mediator message OpenPosition
. This method spins a new thread and tries to cancel the current opening order before opening a new one.
[MediatorMessageSink(MediatorMessages.OpenPosition, ParameterType = typeof(OpenPositionData))]
public void OpenPositionReceived(OpenPositionData openPositionData)
{
Task task = new Task(() =>
{
Log(LogEntryImportance.Info, "OpenPosition message received", true);
if (openPositionData != null)
{
bool cancelOpeningOrderRes = CancelOpeningOrder();
if (!cancelOpeningOrderRes)
{
Log(LogEntryImportance.Error, "Unable to cancel current opening order.
Cannot open new Position", true);
}
else
{
Log(LogEntryImportance.Info, "Opening Position...", true);
OpenPosition(openPositionData);
}
}
else
{
Log(LogEntryImportance.Info, "OpenPositionData is null.
Cannot open new Position...", true);
}
});
task.Start();
}
OpenPosition()
uses the OrderFactory
to create an order with the data sent by the StrategyService
. It then sets that order as the OpeningOrder
of the position. Thus if another message arrives while the order is being placed, the BrokerageService
will know that there is already an OpeningOrder
in the chamber. It then proceeds to actually place the order by calling the client’s PlaceOrder
method. Regardless of the implementation (real or mock), PlaceOrder
will return when the order is either closed, canceled or when an exception occurred. As soon as the method PlaceOrder
returns, the OpeningOrder
of the Position
is updated. If the opening order was successfully placed, the method then proceeds to place the closing or stoploss order. Again, this will keep spinning asynchronously until the order reaches a stable status. Before and after placing an opening or a closing order, the corresponding field in the BrokerageService
’s Position
is always updated.
private void OpenPosition(OpenPositionData openPositionData)
{
try
{
BrokerPosition = new Position(this);
BrokerPosition.Direction = openPositionData.Direction;
Order openingOrder = _orderFactory.CreateOpeningOrder
(openPositionData.Direction, KrakenOrderType.stop_loss,
openPositionData.EnteringPrice, openPositionData.Volume,
openPositionData.CandleStickId, openPositionData.ConfirmationId,
validateOnly: openPositionData.ValidateOnly);
UpdateOpeningOrder(openingOrder);
Log(LogEntryImportance.Info, "Placing opening order...", true);
PlaceOrderResult openingOrderResult = _client.PlaceOrder(openingOrder, true);
openingOrder = openingOrderResult.Order;
UpdateOpeningOrder(openingOrder);
bool ok = false;
... Handle opening-order result ...
if (!ok) return;
Order closingOrder = _orderFactory.CreateStopLossOrder
(openingOrder, openPositionData.ExitingPrice, openPositionData.ValidateOnly);
UpdateClosingOrder(closingOrder);
Log(LogEntryImportance.Info, "Placing closing order...", true);
PlaceOrderResult closingOrderResult = _client.PlaceOrder(closingOrder, true);
closingOrder = closingOrderResult.Order;
UpdateClosingOrder(closingOrder);
... Handle closing-order result...
}
catch (Exception ex)
{
Log(LogEntryImportance.Error, string.Format("An exception occurred in OpenPosition at line {0}. {1} {2}", ex.LineNumber(), ex.Message, ((ex.InnerException != null) ? ex.InnerException.Message : "")), true);
}
}
ClosePosition
The BrokerageService
registers the method ClosePositionReceived
for the Mediator
message ClosePosition
. This method runs asynchronously and eventually cancels the opening or closing order and places the emergency order.
[MediatorMessageSink(MediatorMessages.ClosePosition, ParameterType = typeof(string))]
public void ClosePositionReceived(string message)
{
Task task = new Task(() =>
{
Log(LogEntryImportance.Info, "Closing Position...", true);
bool cancelOpeningOrderRes = CancelOpeningOrder();
bool cancelClosingOrder = CancelClosingOrder();
if (BrokerPosition != null && BrokerPosition.EmergencyExitOrder != null)
{
Order emergencyExitOrder = BrokerPosition.EmergencyExitOrder;
PlaceOrderResult emergencyExitOrderResult = _client.PlaceOrder(emergencyExitOrder, true);
emergencyExitOrder = emergencyExitOrderResult.Order;
UpdateEmergencyOrder(emergencyExitOrder);
... Handle emergency order result ...
}
else
{
Log(LogEntryImportance.Info, "No emergency order to execute.", true);
}
Log(LogEntryImportance.Info, "Position closed.", true);
BrokerPosition = null;
});
task.Start();
}
ShiftPositionLimits
The ShiftPositionLimits
method cancels the current closing order and places a new one either higher or lower. It is called asynchronously by the method that registers for the Mediator
message ShiftPositionLimits
.
private void ShiftPositionLimits(ShiftPositionLimitsData shiftPositionLimitsData)
{
Log(LogEntryImportance.Info, "In ShiftPositionLimits", true);
try
{
bool cancelClosingOrderRes = CancelClosingOrder();
if (cancelClosingOrderRes)
{
Order newStopLossOrder = _orderFactory.CreateStopLossOrder
(BrokerPosition.OpeningOrder, shiftPositionLimitsData.NewLimitPrice,
shiftPositionLimitsData.ValidateOnly);
UpdateClosingOrder(newStopLossOrder);
Log(LogEntryImportance.Info, "Placing new closing order...", true);
PlaceOrderResult placeOrderResult = _client.PlaceOrder(newStopLossOrder, true);
newStopLossOrder = placeOrderResult.Order;
UpdateClosingOrder(newStopLossOrder);
... Handle place-order result ...
}
else
{
Log(LogEntryImportance.Error,
"Unable to cancel current closing order. Cannot shift limits", true);
}
}
catch (Exception ex)
{
Log(LogEntryImportance.Error, string.Format
("An exception occurred in ShiftPositionLimits at line {0}. {1} {2}",
ex.LineNumber(), ex.Message,
((ex.InnerException != null) ? ex.InnerException.Message : "")), true);
}
}
UI Component
The main window is composed of independent Views that each has its own ViewModel
. With a given set of Views, composing the MainWindow
is only a matter of fitting them into a grid in XAML. With this design, it is possible to work on one part of the interface without modifying the overall frame. It also allows for greater testability and integration with Blend.
The ViewModels
, which contain the underlying functionality of a View
, are wrapped in the ObservableObject
class linking them to the Mediator
. User actions are transmitted to the trading services by sending specific messages for which the services register methods. Reciprocally, messages coming from the trading services may be translated into visual updates of a View
.
MVVM
As preconized by the MVVM pattern, the underlying functionality of a View
is handled in the ViewModel
with which it communicates using the WPF data binding mechanism. The correspondence between a View
and its ViewModel
is declared in the app.xaml file:
<img src="file:///C:\Users\ARRIV_~1\AppData\Local\Temp\msohtmlclip1\01\clip_image001.emz" />
<Application x:Class="TradingApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TradingApp.ViewModel"
xmlns:v="clr-namespace:TradingApp.View">
<Application.Resources>
<DataTemplate DataType="{x:Type vm:ControlViewViewModel}">
<v:ControlView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:GraphViewViewModel}">
<v:GraphView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:LogViewViewModel}">
<v:LogView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:PanelViewViewModel}">
<v:PanelView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:PositionViewViewModel}">
<v:PositionView />
</DataTemplate>
</Application.Resources>
</Application>
Given a set of View
s, composing the MainWindow
is just a matter of fitting them into a grid in XAML. The following extract shows how instances of the ControlViewModel
and PositionViewModel
are passed to ContentPresenters
and fitted directly within a Grid
to form the left column of the MainWindow
.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="55*"/>
<RowDefinition Height="45*"/>
</Grid.RowDefinitions>
<ContentPresenter Content="{Binding ControlVM}" Margin="3,0,3,0" />
<GridSplitter .../>
<ContentPresenter Content="{Binding PositionVM}" Margin="3,0,3,0" Grid.Row="1" />
</Grid>
ViewModels as ObservableObjects
ViewModels
inherit from ViewModelBase
which inherits from ObservableObject
, allowing them to register methods with the Mediator
and indirectly communicate with the other components of the application.
Dynamic properties are linked to the ViewModel
by WPF bindings.
<TextBox Grid.Row="3" Grid.Column="1" Height="23" TextWrapping="Wrap"
Text="{Binding WmaPeriod}" IsReadOnly="{Binding Busy}" Width="100"/>
In the setter function of the underlying ViewModel
property, a message is sent to the Mediator
notifying that the value has changed along with the new value.
public int WmaPeriod
{
get
{
return wmaPeriod;
}
set
{
OldPeriod = wmaPeriod;
wmaPeriod = value;
Mediator.NotifyColleagues<int>(MediatorMessages.WmaPeriodChanged, wmaPeriod);
base.RaisePropertyChanged(() => this.WmaPeriod);
}
}
Services that are interested in this change of value may register a method for the corresponding message.
[MediatorMessageSink(MediatorMessages.WmaPeriodChanged, ParameterType = typeof(int))]
public void SetWmaPeriod(int wmaPeriod)
{
WmaPeriod = wmaPeriod;
}
This work is tedious because one needs to create a message for every editable property and register methods to the Mediator
from any class that uses that value. This is one of the shortcomings of this design.
Using the Application
Data Repositories
The application can be configured to use different sets of repositories in the data access layer. As seen in a previous section, there are mock repositories and database repositories. Using the database repositories requires actually creating the database (SQL script provided) and updating the connection string. However, in a first attempt to run the app, I would recommend using the mock configuration which doesn’t require any prior operations.
Exchange Clients
If you already have an account with Kraken
, you can obtain a pair of public and private keys which you could specify in the configuration file. However, using the mock client is probably a good idea while getting familiar with the app.
<appSettings>
<add key="KrakenBaseAddress" value="https://api.kraken.com" />
<add key="KrakenApiVersion" value="0" />
<add key="KrakenKey" value="xxx" />
<add key="KrakenSecret" value="xxx"/>
<add key="KrakenClientMaxRetries" value="5" />
<add key="PositionEpsilon" value="0.0000025"/>
<add key="RepositoryConfigurationType" value="mock"/>
</appSettings>
The mock client models trading activity in a simplistic manner. The arrival of new trades is modeled as a non-homogeneous Poisson process. Trade volumes are modeled as independent random variables following a log normal distribution where the mean and standard deviation are estimated from historic data depending on day of week and hour of day. The price process is modeled as a geometric Brownian motion where mean and standard deviation are set arbitrarily. Indeed, the geometric Brownian motion (often used to model financial assets) is a very bad fit for the price of bitcoin which is very erratic and highly volatile.
It would be interesting to try to fit other models. I can make the trade data available upon request (more than a year of trades from Kraken).
Variables
- Interval: Frequency of the algorithm. At the end of every time interval, price data will be fetched from the exchange, aggregated into a candlestick and passed to the strategy service for further processing.
- WMA period: The number of candlesticks to include in the calculation of the Weighted Moving Average.Example with Interval = 5 minutes and WMA period = 180; The WMA will be calculated with data reaching back 180 * 5 = 900 minutes = 15 hours. Each point will be separated by a 5 min interval. The QuoteBot timer is set to 5 minutes.
- NN Interval: Modulo that will be applied to the price to find the next ‘Natural Number’. Example with NN interval = 10; Setup confirmed at 392 in an up-trend , the next natural number is 400. And we move the limits by increments of 10.
- Position Size: size of each new position expressed Euro. For example, if Position Size = 20 and we want to open a long position, we will place an order for 20 Euro worth of bitcoin.
- Enable orders: If selected, the trading strategy will instruct the brokerage service to place orders with the exchange (mock or real). If not selected, nothing happens.
Debugging
The application uses log4net and a rolling file appender to record events and errors. Since the application leverages multithreading to run concurrent trading services, it can be quite complicated to follow the sequence of events. In the file tradingapp.log, the thread ID is indicated in front of each log line which can be very useful for debugging.
Conclusion
Most systematic trading platforms are comprised of independent components that handle quote-processing, run-time algorithmic processing, brokerage activity and run-time performance monitoring. In this paper, we described the design of a lightweight monolithic WPF application that handles these features and allows running an automated trading strategy. Bigger organizations might use service oriented architecture to physically separate each of these constituents and reduce the number of failing points in the system.