0%

基于决策表实现交易策略

与编程语言级别的逻辑控制(if-then-elseswitch) 相比,决策表能在一个平面中罗列出所有的可能情况,并清晰的指出相应的处理方式,不需要层层嵌套。用户不需要考虑其中的逻辑关系就能一眼看出其中什么样的动作对应什么样的情况,可读性大大提高,并且不容易因为疏忽产生不易察觉的错误。

所以决策表是进行实现复杂控制逻辑的一个有力工具。现在,考虑构建一个通用的决策表,用于定义交易策略。

决策表与规则引擎

决策表又称判断表,是一种呈表格状的图形工具,适用于描述处理判断条件较多,各条件又相互组合、有多种决策方案的情况。
精确而简洁描述复杂逻辑的方式,将多个条件与这些条件满足后要执行动作相对应。
但不同于传统程序语言中的控制语句,决策表能将多个独立的条件和多个动作直接的联系清晰的表示出来^1

决策表用表格的形式表示一组输入与一组输出的相关规则,可以作为规则引擎的输入。比如,Drools
就直接支持使用 Excel 定义的规则表作为规则。

决策表的构成

决策表通常包括以下组成部分:

  • 条件

    一组变量、关系或预测。条件可能的值的范围决定了决策表的类型。
    最简单的形式为逻辑条件,取值为二元的 True/False。
    也可以是用数组或字母代表的分类规则,可以取有限个离散值。

  • 输入

    每个条件可能的值。决策表要覆盖条件的所有可能组合。
    但可以用“不关心”符号来化简决策表。

  • 动作

    一组要执行的过程或操作

  • 输出

    根据输入,决定是否以及按怎样的顺序执行动作。 

这里
有一些决策表的例子。

使用决策表构建策略

与编程语言级别的逻辑控制(if-then-elseswitch) 相比,
决策表能在一个平面中罗列出所有的可能情况,并清晰的指出相应的处理方式,不需要层层嵌套。

用户不需要考虑其中的逻辑关系就能一眼看出其中什么样的动作对应什么样的情况,
可读性大大提高,并且不容易因为疏忽产生不易察觉的错误。
所以决策表是进行实现复杂控制逻辑的一个有力工具。

现在,考虑构建一个通用的决策表,用于定义交易策略。
这篇文章提供了一个很好的起点[^2],让我们在此基础上更近一步:

输入

为了简化条件的处理,可以要求所有的输入都是 bool 值。非 bool 值的条件,可以通过变形和多种条件的组合转换成 bool 条件。
为了简化决策表,需要支持”不关心”符号。
这样,输入就只有三种情况 (True, False, True or False),可以用符号(T,F,-)来表示。

输出

如果动作能够实现排列好顺序,则输出就是“是否执行特定的动作”的序列。可以用X表示执行,空白表示不执行。

输出只是是否执行某个特定的动作。可以用 ``

按照决策表原始的定义,

输出应该是某种“动作”。这很复杂,
很可能需要用到Roslyn这样的大象。
但对于交易策略来说,所有的输出无非是某种“指令”。如果把这个“指令”延后到后面的环境去执行,
则所有的输出就变成了一个“表达式”。

条件

用 C# 的 lambda 表达式可以很容易的创建条件(Condition),但这还是通过编码的方式,不够通用。
我更希望能够通过外部文件来定义条件(比如 文本文件 或 Excel),这就需要用到表达式引擎。
由于我已经定义了策略上下文(Context)来封装策略的运行时环境,
只需要把策略上下文中的变量注册到表达式引擎中,就可以很容易通过表达式解析的方式定义条件了。

动作

与“条件”类似,我已经定义过策略的输出为Action,在后续环节再生成交易指令。
为了避免在动作中涉及到复杂的运算,需要丰富Action Factory的功能,实现各种不同的创建Action的方式。
这样,就可以用表达式为工厂方法传递参数,避免复杂运算。

C# 表达式引擎: Expression Evaluator

Expression Evaluator是一个轻量级的 C#表达式引擎[^3]。
Expression Evaluator仅仅依赖Antlr(“又一个语言识别工具”,一个开源的,支持多平台的语法解析器),
并且自身还不到1M。

Expression Evaluator的主要特性包括[^4]:

  • 支持算术运算符,支持关系运算符,以及逻辑运算符
  • 支持表达式分组和括号,以及递增递减运算符
  • 支持表达式属性访问以及动态类型,支持字符串的+运算
  • 支持数值类型的后缀d/f/m/l/u/ul、
  • 支持隐式表达式,以及成员访问操作符(.)
  • 支持一些默认的类型,如double, float, char, string, DateTime, Convert, Math
  • 支持foreach循环

实现要点

在我的架构中,策略要尽可能的简单,由 Context 封装策略的运行环境。?
那么很自然的,表达式引擎应该在 Context 中引入并创建,策略中只是调用其结果。

在 Context 中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 表达式变量



// 在构造函数中,定义引擎并注册变量
public StrategyContext()
{
var registry = new TypeRegistry();
registry.RegisterSymbol("当前委托", CurrentOrder);
registry.RegisterSymbol("做市商行情", CurrentMMTick);
}

……

// 提供

参考资料

[^2]: Decision Table in C#
[^3]: Expression Evaluator:一个轻量级的C#编译器服务
[^4]: Expression Evaluator表达式计算组件使用