65.9K
CodeProject 正在变化。 阅读更多。
Home

WPF FIX 自动交易客户端

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (17投票s)

2014年6月5日

CPOL

16分钟阅读

viewsIcon

46412

downloadIcon

1339

一个演示 WPF 合成订单簿客户端 UI,用于管理合成订单并通过 FIX 将固定订单提交到交易服务器。

Heathmill WPF FIX AT Client

引言

Heathmill 的自动交易 (AT) 客户端演示了合成订单管理 - 如何将 AT 规则组织到一个方便的“合成订单簿”用户界面中(我将从现在开始称其为 AT 订单簿客户端)。该客户端使用金融界的一种标准 FIX,与远程交易平台进行通信。

上一篇文章中,我们讨论了用于测试我们的 AT 订单簿客户端的模拟 FIX 自动匹配服务器。那篇文章简要介绍了 FIX,解释了我们如何使用 FIX 消息,并介绍了自动匹配服务器的设计和代码。

在这里,我们专注于 AT 订单簿客户端以及它如何使用 AT 规则创建“合成”订单。这些 AT 订单被称为合成订单,因为它们代表交易员创建的交易规则(通常仅在客户端),而不是通常的“固定”订单,即买卖一定数量的某物(指定价格)的约束性要约。这些合成订单本身通常没什么用,在我们的演示 AT 系统中,它们被客户端转换为固定订单并通过 FIX 发送到交易服务器。客户端还必须处理来自服务器的 FIX 消息,并更新合成订单以反映服务器上的活动。

当然,任何自动在实时交易服务器上生成固定订单的系统都必须经过彻底测试。有缺陷的或测试/非生产软件以疯狂的价格释放大量固定订单会很快造成巨大损失,正如 Knight Capital 惨痛的教训。这只是一个演示系统,因此未经生产代码级别的测试;但是,我们已经对其进行了设计,可以进行单元测试,我将提及我们为使客户端代码可测试所做的一些事情。

如果您觉得这很有趣,不妨下载 AT 订单簿客户端和模拟 FIX 服务器,并试玩整个演示 AT 系统。

那么客户端做什么?

  • 使用 FIX 4.4 连接到 FIX 交易服务器
    • 在本例中,是来自我们上一篇文章的模拟 FIX 服务器
  • 将服务器上的订单显示为每个合约的排序订单栈
  • 向服务器提交限价单
  • 允许订单的点击交易
  • 创建、激活和暂停冰山自动交易合成订单

我们编写的模拟 FIX 服务器实际上不支持点击交易,因此这是通过在市场另一侧添加与正在交易的订单匹配的订单来完成的;即使在真实的交易软件中,这也很常见。技巧/变通方法/ hack(根据需要删除)。

关于代码

该客户端使用 C# 4.0 和 .NET 4 在 Visual Studio 2010 中使用 WPF 和 MVVM 模式编写。客户端可执行文件的项目是Heathmill.FixAT.OrderBook

客户端应用程序的基本结构基于QuickFIX/n 团队的 UIDemo 项目,客户端中的一些类要么是大量借鉴,要么是完全从该项目中借用的。如果您有兴趣试用此客户端或模拟 FIX 服务器,建议阅读 QuickFIX/n 网站上的 UIDemo。请记住,这是一个演示客户端,因此它是原型代码,而不是生产质量的代码。

代码链接

AT 订单簿客户端的代码是模拟服务器文章解决方案的一部分,可以在该文章的浏览代码链接 1 中找到。

客户端

在我们深入研究示例或细节之前,这里是客户端主窗口的屏幕截图,我们可以稍后参考。

Heathmill WPF FIX AT Client

正如您所见,屏幕分为两部分:市场视图,显示系统中的订单作为每个符号的订单栈;以及 AT 订单部分,显示由自动交易规则创建的任何合成订单。

自动交易

在此上下文中,自动交易 (AT) 是指使用一个或多个规则来创建具有某些自动化行为的订单。这些订单称为合成订单。举个例子可能有助于理解。

假设您想要一个订单,该订单的价格总是比市场最佳价格差 5 个 Pip。如果您不必像猎鹰一样盯着屏幕,并在每次市场最佳价格变动时调整订单,并冒着在您去泡咖啡时错过市场大动的风险,那么如果订单能自动更新,事情会容易得多。这就是自动交易的作用:如果您可以选择创建一个订单,该订单将以bestPrice-0.0005的价格钉住市场最佳订单,那么使用这种交易策略将变得更加简单、耗时更少且错误更少。

这里的 AT 不指使用 AI、算法、超低延迟等来自动交易;这通常被称为算法交易,我们根本不会涉及。

冰山订单

我们用作演示的合成订单类型被称为冰山订单:之所以称为冰山订单,是因为像冰山一样,大部分交易量都隐藏起来。大宗订单通常会因供需关系而使交易员的市场行情不利,因此交易员的利益在于一次只向市场显示其总交易量的一小部分。

对于冰山订单,交易员提交订单时包含他们希望交易的总数量和将显示给市场其他交易员的数量(“订单量”)。例如,如果您有 100,000 股 MSFT 股票要卖出,您可能会提交一个数量为 100,000 股、订单量为 10,000 股的冰山订单。然后,合成冰山订单将向交易系统提交一个 10,000 股 MSFT 股票的实际订单,当该订单完全交易后,将提交另一个 10,000 股 MSFT 股票的订单(“重新填充订单”)。然后,它将继续重新填充订单,直到总共交易了 100,000 股 MSFT 股票。

我们的冰山订单还允许您指定价格差,正如大多数订单一样,这是在重新填充订单时订单价格变化的数量。假设您有一个价格为 1.50、价格差为 0.01 的订单,当第一次重新填充订单时,提交给交易系统的实际新订单价格将是 1.49(买单为 1.51,卖单为 1.51)。第二次重新填充将在 1.48,第三次将在 1.47,依此类推。这使得交易员可以考虑到,如果他们的订单被命中,那么市场就会朝着他们的方向移动,因此他们提交的下一个订单(重新填充)应该以更差的价格。

How an iceberg order refills itself

一个冰山场景示例

让我们通过一个示例场景来演示冰山订单的工作原理。

请注意,我们使用quantity@price形式的简写符号表示订单(例如,1000@1.3902表示价格为 1.3902,数量为 1000)。

在我们开始之前,系统中已经有许多订单,您可以看到下面的屏幕截图。首先,我们为 USDCHF 添加一个买入冰山订单,价格为 0.881,总数量为 10000,订单量为 1000,价格差为 0.001 并激活它。此时,市场看起来是这样的。

The market after inserting and activating the iceberg order but before any trades occur

我们新激活的冰山订单已经插入了一个常规订单(ClOrdID 5017),数量为1000@0.881,它位于买入侧订单栈的顶部。

接下来,有人(实际上是我,因为这是一个演示,但在现实生活中是另一个用户)在 USDCHF 卖出侧添加了一个匹配订单,数量为1000@0.881。这导致底层常规订单完全匹配,因此冰山订单会重新填充并以 (0.881 – 0.001) = 0.880 的价格提交新订单。正如您从下一个屏幕截图中看到的。

After trading at 0.881 the iceberg refills itself by inserting a new order for 1000@0.880

如果您查看 USDCHF 订单栈,可以看到我们的新底层订单(仍带有 ClOrdID 5017)不在栈顶;那个荣誉属于已经存在于市场中的100@0.880(ClOrdID 1013)的订单。新的底层订单位于栈的下一个位置。

因此,让我们再下一个匹配的卖单,但这次我们将其设为2100@0.880。现在发生的是,这个新的卖单导致了以下情况:

  • 完全匹配 ClOrdID 1013 100@0.880,剩余2000@0.880数量。
  • 完全匹配我们的底层订单1000@0.880,剩余1000@0.880
  • 冰山订单通过提交一个新的底层订单进行重新填充,但由于价格差,新订单为1000@0.879。由于这是唯一剩余的买入订单,且价格比剩余的卖出订单数量(1000@0.880)差,因此情况在此停止。

因此,我们现在在买入侧的顶部有一个1000@0.879,在卖出侧有第二个卖出订单的剩余数量1000@0.880

After trading at 0.880 the iceberg refills itself again at 1000@0.879, since this is worse than the top sell order matching stops

设计

AT 订单簿客户端是一个使用MVVM模式的 WPF 应用程序。Heathmill.FixAT.ATOrderBook程序集包含主应用程序,WPF 视图的xaml,并负责创建视图模型以及传递到视图模型和其他类的各种接口实现。视图模型和模型类包含在Heathmill.FixAT.Client程序集中,服务器通信类也包含在此程序集中。

Class diagram, excluding factory classes

可测试性设计

IServerFacadeIMessageSink的存在是为了帮助代码的单元测试。尽管这是一个原型,并且高覆盖率的测试套件不在路线图上,但这种东西已经深深植根于我的编码习惯中,我忍不住!

除了作为模拟/伪/存根注入点外,IServerFacade还充当外观模式,因此可以从 FIX 消息的细节中抽象出来,并允许我们以对调用者更友好的领域术语来表达服务器,例如CreateOrder而不是暴露创建和发送NewOrderSingle FIX 消息的实现。它还允许我们在未来连接到非 FIX 服务器,而无需替换硬编码的 FIX 代码。

public interface IServerFacade
{
    void Start();
    void Stop();

    bool CreateOrder(OrderRecord orderDetails);

    bool CancelOrder(string symbol,
                     string clOrdID,
                     MarketSide side,
                     string orderID);

    bool UpdateOrder(OrderRecord oldOrderDetails, OrderRecord newOrderDetails);

    string GetServerSessionID();
    
    event Action<OrderStatus, OrderRecord> OrderExecutionEvent;

    event Action LogonEvent;
    event Action LogoutEvent;
}

IMessageSink是我从 F# MVP Phil Trelford那里学到的一个想法,它允许对 VM 错误和消息处理进行单元测试,而无需弹出消息框和其他类似的破坏测试的行为。它还有助于强制执行 MVVM 的理想,即 VM 中不包含 UI 代码。

public interface IMessageSink
{
    void Trace(Func<string> message);
    void Message(Func<string> message);
    void Error(Func<string> message);
}

然后,您可以为测试实现此的一个模拟版本,以及为实际运行应用程序的另一个版本,该版本会愉快地弹出消息框。

public class StandardMessageSink : IMessageSink
{
    // ...

    public void Trace(Func<string> message)
    {
        System.Diagnostics.Trace.WriteLine(message());
    }

    public void Message(Func<string> message)
    {
        if (_messageCallback != null)
            _messageCallback(message());
    }

    public void Error(Func<string> message)
    {
        MessageBox.Show(message(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

另一个相当常见的技巧是让您的消息/日志记录代码接受Func<string>而不是字符串,这样您的代码就不会花费时间格式化,例如,Trace 消息字符串,这些字符串在生产环境中运行时将被忽略。

与模拟 FIX 服务器通信

与模拟 FIX 服务器的通信由IServerFacadeFixServerFacade实现处理,它使用封装在(命名相当糟糕的2ClientApplication类中的 FIX 客户端。ClientApplication很大程度上基于 QuickFIX/n UIDemo应用程序类UIDemo.QFApp,并遵循他们推荐的实现QuickFix.IApplication和继承自QuickFix.MessageCracker的模式。

我们也保留了 QuickFIX/n UIDemo项目中的IFixStrategy接口。我们实际上并不使用它(唯一的策略是EmptyFixStrategy),但是它提供了一个有用的扩展点,以防将来需要客户端与具有自定义 FIX 字段的服务器(许多商业交易服务器都有)通信。

业务逻辑

由于我们演示系统中的任何 AT 合成订单类型的业务逻辑完全存在于客户端中,因此它构成了模型的大部分。一个商业交易客户端可能更好地拥有一个本地 AT 订单服务器,以保持客户端尽可能精简,但对于原型来说,我们可以接受一点 UI 的臃肿。

ATOrderMediator是客户端中大部分非规则特定业务逻辑所在的地方。它订阅了服务器外观的OrderExecutionEvent。这意味着每当添加、取消或匹配订单时,AT 订单中介都会知道并采取相应的措施。

IcebergOrder类型本质上是一个状态机,它还跟踪冰山订单中剩余的数量,并在底层订单完全匹配时进行重新填充。状态跟踪冰山订单是暂停还是激活,并根据此处理在服务器上发送和取消底层订单(遵循冰山订单部分讨论的逻辑)。

SmartDispatcher

这是一个来自 QuickFIX/n UIDemo项目的一个方便的小类,用于简化绕过“UI 元素只能在其拥有的线程上更新”的问题。

您可以通过调用SetDispatcher来设置要使用的 Dispatcher 实例。

// Set the main UI dispatcher
SmartDispatcher.SetDispatcher(mainWindow.Dispatcher);

然后,后续的客户端代码可以调用SmartDispatcher.Invoke来避免任何不必要的跨线程相关异常。

FIX 消息

关于 Heathmill 模拟 FIX 服务器的Code Project 文章更详细地介绍了系统中支持用例之间在客户端和服务器之间发送的 FIX 消息。它还更多地讨论了我们如何通过 QuickFIX/n 和我们自己的库来处理 FIX 会话和消息转换。

但是,非常简而言之,以下 FIX 消息从客户端发送到服务器:

  • 添加订单时发送NewOrderSingle
  • 取消订单时发送OrderCancelRequest

服务器向客户端发送以下消息:

  • 当客户端操作成功或发生订单匹配时,发送ExecutionReport(带有适当的ExecType字段)。
  • 如果添加订单的尝试失败,则发送ExecutionReport(带有ExecType.REJECTED)。

单元测试

如果您阅读过或使用过 MVVM,您就知道该模式的优点之一是视图模型和模型不应包含 UI 代码,因此可以进行单元测试。不幸的是,由于这是一个原型,时间不允许进行太多单元测试,但如果这是生产代码(或者如果我们是好的 TDD 人员),那么它将值得/拥有更大的测试覆盖率。

就目前而言,进行的单元测试的重点是冰山订单的业务逻辑,详情请参见TestIcebergOrder.csMoq在需要时用于创建IServerFacade的模拟。

在许多领域,测试可以很容易地扩展,而无需太多重构。立即想到的领域是测试客户端代码如何处理服务器上的订单更改。可以使用一个假的IServerFacade,它带有对OrderExecutionEvent的预设调用列表,来测试OrderBookViewModel的订单更新处理及其为常规订单显示创建的订单栈。

还可以使用假的服务器来测试ATOrderMediator如何响应服务器操作来更新 AT 订单。尽管其中一些已经包含在冰山订单行为测试中,但最好也测试诸如当为底层订单的OrderRecord引发OrderStatus.CanceledOrderExecutionEvent时调用IcebergOrder.MarketOrderCanceled等内容。

通过提供一个带有预先准备好的LogonEventLogoffEvent引发的假IServerFacade实现,还可以很容易地测试主 UI 状态栏中显示的连接状态,只需单元测试ConnectionViewModel.ConnectionStatus

此外,鉴于我们有自己友好的模拟 FIX 服务器可以连接,因此可以编写系统级测试。这将需要一些基础设施工作,但创建探测 VM 层代码并然后查看其与实际(尽管是友好的)服务器交互时的响应的测试驱动程序,对于建立对商业系统的信心将是无价的。如果友好服务器是可脚本化的,则更是如此。

如何将两个客户端连接到模拟 FIX 服务器

我没有过多介绍客户端(或模拟 FIX 服务器)的配置设置,因为它直接取自 QuickFIX/n 演示系统。但是,有一点值得一提的是如何调整客户端配置以允许多个客户端连接到服务器。

quickfix.cfg文件在[SESSION]部分指定了要用于连接的 FIX 会话的详细信息。服务器只会期望一个具有给定SenderCompID的会话,因此要连接第二个客户端,请将此 ID 更改为服务器正在等待的另一个 ID(例如CLIENT2)。服务器期望的会话列表可以在FixAtServer.cfg中找到。

顺便说一句,修改quickfix.cfg文件也是更改客户端SESSION使用的 FIX 版本的方式。但除非您想编写一些 FIX 处理代码或弄坏东西,否则不要更改它;该客户端目前仅支持 FIX 4.4。

结论

我向您介绍了 Heathmill FIX AT 订单簿客户端,并简要介绍了自动交易和冰山合成订单类型。冰山订单确实只是 AT 功能的冰山一角(抱歉),随着电子和算法交易的普及,市场变得越来越快,交易员对他们的交易客户端的要求也越来越高。为现有客户端添加 AT 规则可能是安抚他们的一个好方法,毕竟没有人想让愤怒的交易员冲他们大喊大叫。

那么,我们最终得到了什么?与模拟 FIX 服务器一样,这是用于演示目的的原型代码,绝对不适合在生产环境中使用。除了它不是最漂亮或最流畅的 UI 之外,性能未经测试,缺乏一些基本的订单管理功能(例如实际取消或更新市价单),并且错误处理、报告和消息传递非常基础。但这就是原型的本质;目的不是创建一个商业交易屏幕,而是演示 AT 合成订单以及它们有多么有用。我希望我们已经做到了。

脚注

  1. 如果有人知道如何正确地在文章之间共享 git 存储库,那将是一个更好的解决方案!
  2. ClientApplication这个名字是早期阶段的遗留产物,当时应用程序只是这个类向屏幕输出消息……它应该有一个更具描述性和准确性的名字。

历史

  • 2014-06-05 初始版本
© . All rights reserved.