**知识前提* -- 必须了解路孚特streaming API,如EMA,并熟悉如何使用OMM市场价格(Market Price)数据(见文末相关教程链接)。*
开发人员经常问:"我怎样才能获得所有可用的RIC列表?" ,但他们并没有意识到路孚特实时数据源承载了数百万的RIC(在撰写本文时超过8000万)。RIC过期和新RIC添加的情况一直在发生,如果他们要获取一份所有可用的RIC列表,数量无疑是巨大的。
然而,路孚特实时边界设备Edge(如果您不熟悉Edge,请参见API概念指南)确实有一种功能,允许开发人员对符合一组指定标准的RIC列表进行基于标准的请求(CBR)-- 也称为BDS(广播数据流)。
例如,您可能想找到以下信息:
- 欧洲期货交易所的所有国债期货证券的列表
- 香港交易所的现金期权和期货期权的所有RIC列表
- 所有亚太地区交易所的所有RIC列表
从开发人员的角度来看,您使用CBR/BDS功能的接口是OMM SymbolList域。路孚特的实时API及RFA API里包含了开发者指南或API概念指南,您可以从这些指南里详细了解OMM SymbolList域。本文将会对该部分内容做一个简要介绍。
SymbolList域
SymbolList域是OMM对传统的链式(Chain)机制的替代(更多细节见关于链对象)。在这种情况下,应用程序使用SymbolList名称(代替RIC代码)提出SymbolList域请求,而服务器则以包含相应RIC的映射结构作出回应。
例如,如果我从路孚特实时数据服务向“.AV.O“(纳斯达克最活跃的25个RIC)提出SymbolList请求,我将收到类似这样的回复:
如上图所示,返回的是一个映射列表(我在本教程中使用RFA Java OMM Viewer的例子,因为它提供了一个方便的GUI界面)。每个映射条目的关键值是RIC;RANK_POS字段是该RIC对应的排名,它在这个特定的SymbolList(即前25名)的背景下是合理的。对于实际的CBR/BDS响应,您将不会得到这个字段(见下文)。
需要澄清一点,“.AV.O”不是CBR/BDS,它碰巧是路孚特实时数据服务支持的SymbolList名称。
在总结了SymbolList之后,下面我将继续解释CBR/BDS机制。
制定您的RIC选择准则
我在前面提到,您可以通过指定一组要匹配的值来定义一个基于标准的请求。您要如何做到这一点?您可以指定哪些类型的标准?
技术上的前提条件
要定义匹配标准,您需要访问Edge上的RRSC控制台应用程序。您需要做的第一件事是与您的市场数据团队沟通,确认他们能够在贵司的Edge设备上创建BDS术语。
创建BDS术语
一个BDS术语是由一个或多个SQL术语、其它BDS术语以及File术语组成的。对于本文的主题,即发现与某些选择标准相匹配的RIC列表,SQL术语是最合适的。使用File术语(一个填充有RIC列表的文件)需要事先对RIC有所了解,因此在发现RIC方面没有意义。
创建SQL术语
SQL术语是用于查询Edge数据库的SQL语句,从查询中获得的RIC列表被用作基于标准的请求的响应。例如,以下几个字段可用于构建SQL查询:
SQL字段 | 用户输入类型 | 备注 |
---|---|---|
Exchange ID | 数值 | 该字段的值可以在每个路孚特API自带的enumtype.def文件中找到,它位于文件中的RDN_EXCHD2和RDN_EXCHID的Enum定义中。 |
Exchange ID (Name) | 文本等值 | 可使用SQL查询编辑器进行选择(见下文) |
Record Type | 数值 | 记录类型,如债务、期货、期权等(见以下链接) |
Record Type (Name) | 文本等值 | 记录类型,如债务、期货、期权等(见以下链接) |
RIC Name | 文本 | 通配符,详见下表 |
您可以在本网站关于使用FID259(RECORDTYPE)的文章中找到关于记录类型的解释。
RIC名称的通配符
RIC名称匹配标准可以使用以下通配符
描述 | 字符 | 例子 | 含义 |
---|---|---|---|
单字符 | ‘_’ | IBM._ | 所有以“IBM.”为前缀且后面包含任何单字符的RIC,例如IBM.O,IBM.N,IBM.T。 |
多字符 | ‘%’ |
%.HK | 所有来自香港交易所、以“.HK”为后缀的RIC。 |
范围 | [] |
800[2-6].HK | 代表以下这些RIC:8002.HK,8003.HK,8004.HK,8005.HK和8006.HK。 |
否定 | ‘^’ | _[^#]% | 所有不是链式记录的RIC(即不是0#GC:,0#.FTSE,1#.HSI等)。 |
使用Edge控制台
对Edge控制台的访问通常只限于贵司的市场数据管理团队。但是,如果他们不熟悉它的使用,下面的内容应该会有帮助。
BDS术语和SQL术语可以通过Edge控制台应用程序的任务菜单创建。
由于您的BDS术语是由一个或多个SQL术语组成的,所以首先需要创建SQL术语
上图显示了在构建SQL术语时可以匹配的更完整的标准/字段集。现在,让我们来创建一个SQL术语。
上面的例子中,SQL查询将从阿姆斯特丹交易所(77)、洲际交易所(758)和斯德哥尔摩交易所(70)中选择所有的RIC。
接下来这个SQL术语将被保存为”SQL_EUROPE_V1”:
SQL术语创建好之后,我可以继续创建BDS术语来使用这个SQL术语。
我可以选择一些术语来构建我的新BDS术语,在这里我会选择上面创建的SQL_EUROPE_V1术语。然而,正如您所看到的,您可以选择多个SQL术语以及其它现有的BDS术语,这将允许您根据需求使您的选择变得简单或复杂。
最后,我将这个BDS术语保存为BDS_EUROPE_V1
现在我有了SQL术语和一个引用SQL术语的BDS术语。
我演示得很简单,但是,正如之前所解释的,您可以在一个BDS术语中使用多个SQL术语。例如,下面的BDS术语是由3个SQL术语组成的。
测试BDS术语
为了测试新的BDS术语,我们需要一个能够请求和处理SymbolList域的应用程序。路孚特所有具有OMM功能的API包(包括企业消息API即EMA)都包含了一个简单的SymbolList例子,就代码行数而言,这应该是最短的。为了简洁起见,我将使用EMA的Java例子ex270_SymbolList。
在继续演示之前,需要指出的是,如果我对上述BDS_EUROPE_V1术语进行请求,它将返回大约740000个RIC,这对于测试来说并不理想。有鉴于此,我创建了另一个SQL术语,其匹配范围小得多,如下所示。
请求BDS
如上所述,我将使用ex270_SymbolList,它可以在Java RT-SDK软件包的Java\Ema\Examples\src\main\java\com\refinitiv\ema\examples\training\consumer\series200\文件夹中找到(C++版本的RT-SDK中也有一个类似的270_SymbolList例子)。
当请求更常用的市场价格(Market Price)数据时,您需要在请求消息中指定一个MARKET_PRICE域,比如:
RFA : ommmsg.setMsgModelType(RDMMsgTypes.MARKET_PRICE)
EMA : reqMsg.domainType(EmaRdm.MMT_MARKET_PRICE) // default domain in EMA - so not explicitly required
然而,如果您看一下example270的main()方法,您会发现在创建ReqMsg时,请求消息的域被指定为MMT_SYMBOL_LIST。
consumer.registerClient(EmaFactory.createReqMsg().domainType(EmaRdm.MMT_SYMBOL_LIST)
.serviceName("ELEKTRON_DD").name("BDS_VOD"), appClient, 0);
您也会注意到,我已经将我所请求的SymbolList的名称改为BDS_VOD。
MarketPrice数据和SymbolList之间的另一个关键区别是,SymbolList响应是以映射(Map)的形式出现的,而MarketPrice响应则使用更简单的FieldList。
如果您熟悉OMM Map,您可以跳过下面的部分,转到结果部分。
您可以在我们的RT-SDK和RFA API软件包中的《开发者指南》或《API概念指南》中更详细地了解OMM Map。以下内容可以提供一个简要介绍。
下面的例子截取了SymbolList Refresh响应的一部分,它有助于更好地理解Map的结构。
<REFRESH domainType="SYMBOL_LIST" streamId="5" containerType="MAP" flags="0x1F8
(HAS_MSG_KEY|SOLICITED|REFRESH_COMPLETE)" State: Open/Ok/None - text: "All is well">
<key serviceId="257" name="BDS_VOD"/>
<dataBody>
<map countHint="30" containerType="FIELD_LIST" >
<summaryData>
<fieldList>
<fieldEntry fieldId="6456" data="06"/>
</fieldList>
</summaryData>
<mapEntry flags="0x00" action="ADD" key="VOD.BN" >
<fieldList>
<fieldEntry fieldId="1" data="0D42"/>
</fieldList>
</mapEntry>
<mapEntry flags="0x00" action="ADD" key="VOD.MW" >
<fieldList>
<fieldEntry fieldId="1" data="19A1"/>
</fieldList>
</mapEntry>
<mapEntry flags="0x00" action="ADD" key="VOD.SId" >
<fieldList>
<fieldEntry fieldId="1" data="1AD0"/>
</fieldList>
</mapEntry>
....
....
<mapEntry flags="0x00" action="ADD" key="VOD.SI" >
<fieldList>
<fieldEntry fieldId="1" data="0BEB"/>
</fieldList>
</mapEntry>
</map>
</dataBody>
</REFRESH>
上述Refresh消息片段的分解如下:
- 首先“head”部分确认了Refresh消息的各种属性,包括域的类型、唯一的streamID、状态信息、名称和容器类型。
- 其次Map 有效载荷(Payload)包括一个摘要部分,它只是再次重复域的类型,这在BDS响应的情况下不是特别有用。然而,对于像二级交易委托账本(即OrderBook,也使用Map)之类,摘要部分将包含货币、交易单位、交易所ID等细节,即交易委托账本中所有交易的共同值。
- 接下来是单独的Map条目:
- 添加(Add)动作表明这是一个需要添加到本地缓存的新条目,也就是你在内存中对Map的本地表示。
- 如果这是一条更新(Update)消息,它可能包含带有删除动作的条目,表示该条目已经过期,应该从本地缓存中删除(对于一个OrderBook,您也可以收到Update的动作,例如当一条委托交易的价格或数量发生了变化)。
- BDS SymbolList条目的Map键是实际的RIC本身(对于一个OrderBook,键值可以是交易价格+交易方)。
- fieldList包含一个或多个与Map条目相关的字段。在这里,我们只有RIC的一个许可代码(对于一个OrderBook,它将是价格、交易方、数量、交易时间等)。
BDS SymbolList请求的结果
一旦我定义了SQL_VOD术语并将其链接到BDS_VOD术语,我就会使用一个消费者应用程序样本来请求它。我从ELEKTRON_DD服务中为BDS_VOD做了一个SymbolList请求,结果如下所示:
贵司使用的服务名称可能有所不同,它需要是一个提供路孚特实时数据的服务,通常被命名为IDN_RDF, ELEKTRON_DD或类似的名称。
由于上述请求返回的RIC数量较少,完整的Map以单个RefreshMsg的形式到达。更实际的BDS查询很可能包含更多的RIC,因此这些消息将以多个RefreshMsg的形式交付,其中每个RefreshMsg包含一部分的RIC。最后一个RefreshMsg将把Complete标志设置为”true”,表明所有部分都已发送,OrderBook交付已经完成。
您将注意到上述REFRESH响应中的REFRESH_COMPLETE标志,这是一个自包含的RefreshMsg。您可以在代码中通过测试Complete标志来测试最后一个RefreshMsg,例如:
public void onRefreshMsg(RefreshMsg refreshMsg, OmmConsumerEvent event)
{
...
if (refreshMsg.complete())
System.out.println("Final RefreshMsg received");
...
}
注意:您可以对一个BDS提出快照(Snapshot)请求并收到当前匹配的RIC列表,之后数据流将被关闭。但是,如果您提出流式(Streaming)请求,该数据流将保持开放直到您关闭它。您将收到包含Map条目的更新(Update)消息,其中包括:
- 当符合您的标准的任何新的RIC可用时,进行添加(Add)操作。
- 当任何以前匹配的RIC过期时,进行删除(Delete)操作。
- 当现有RIC的权限代码发生变化时,进行更新(Update)操作(这种情况很少发生)。
一天当中,可能有一些RIC过期,也可能有一些新的RIC被创建。所以通常情况下,更新(Update)消息的Map里有对不同条目添加(Add)和删除(Delete)的混合操作。
使用案例
我遇到了两个关于BDS/CBR功能的实际使用案例:
- 维护黄金源:一个SymbolList消费者在快照模式下运行数次。这些运行发生在午夜后不久,每次都调用不同的BDS以涵盖各种交易所;之后应用程序与现有的存储库进行delta从而得出新的和过期的RIC列表;然后新的和过期的列表被用来更新黄金源。此时并不要求RIC本身的市场数据,黄金源在以后的市场开放时间内用于请求所需条目的市场数据。
- 获取一天中的快照:一个SymbolList消费者在一天中的两个时间点以快照模式运行。一旦获得了完整的RIC列表,应用程序就会对每个RIC提出MarketPrice快照请求,并捕获市场数据。
第一个用例在非交易时间提出BDS请求,主要是因为收到的RIC总数达到几百万,处理它们需要时间。此外,日间的需求是只为黄金源中的一部分RIC请求市场数据。
相比之下,第二个用例只涉及几千个RIC,需求是捕捉SymbolList中返回的所有RIC的市场数据。
索取RIC的市场价格数据
如果您的用例涉及到为SymbolList中返回的所有RIC请求市场价格(Market Price)域的数据,您可以利用SymbolList域的一个功能来自动请求所有RIC。
RDM使用指南(每个API都有)的SymbolList部分更详细地描述了SymbolList的行为。下面是该文件的一个片段:
元素名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
SymbolListBehaviors | ElementList | ElementList里包含:DataStreams的ElekmentEntry,设置为0. | 该元素表示从SymbolList中打开的单个RIC的任何预期数据行为。如果没有这个元素,单个数据流将不会被打开。 |
:DataStreams元素可以是以下值之一:
值 | 结果 |
---|---|
0x0 | 消费者只对获得名称感兴趣,而不关心SymbolList中各个RIC的数据。这是默认值。 |
0x1 | 消费者感兴趣的是将SymbolList中的每个RIC打开为流式(Streaming)。 |
0x2 | 消费者感兴趣的是将SymbolList中的每个RIC打开为快照(Snapshot)。 |
例如,如果我修改上述例子的main()方法,加入以下内容,该例子将请求SymbolList,同时收到列表中每个RIC的MarketPrice快照数据。
public static void main(String[] args)
{
....
ElementList symbolListBehaviors= EmaFactory.createElementList();
ElementList dataStream= EmaFactory.createElementList();
dataStream.add(EmaFactory.createElementEntry().uintValue(EmaRdm.ENAME_DATA_STREAMS,
SymbolListDataStreamRequestFlags.SYMBOL_LIST_DATA_SNAPSHOTS));
symbolListBehaviors.add(EmaFactory.createElementEntry()
.elementList(EmaRdm.ENAME_SYMBOL_LIST_BEHAVIORS, dataStream));
...
...
consumer.registerClient(EmaFactory.createReqMsg().domainType(EmaRdm.MMT_SYMBOL_LIST)
.serviceName("ELEKTRON_DD").name("BDS_VOD").payload(symbolListBehaviors),
appClient, 0);
...
}
如您所见,我正在创建一个带有DataStream条目的ElementList,指定Data Snapshots为值,将其添加到SymbolListBehaviors ElementList中,然后将其作为BDS SymbolList请求消息的有效载荷(Payload)。
在处理MAP Payload之外,我还修改了onRefreshMsg()程序以处理FIELD_LIST,这样在收到市场价格数据时也会输出到控制台。
public void onRefreshMsg(RefreshMsg refreshMsg, OmmConsumerEvent event)
{
...
if (DataType.DataTypes.MAP == refreshMsg.payload().dataType())
decode(refreshMsg.payload().map());
if (DataType.DataTypes.FIELD_LIST == refreshMsg.payload().dataType())
decode(refreshMsg.payload().fieldList(), false);
...
}
当我重新运行修改后的例子时,我收到了BDS SymbolList响应和SymbolList中每个RIC的快照。
这种数据自动请求功能不是BDS特有的,因此可以普遍用于SymbolList请求。
结束语
总结一下上述信息:
- 路孚特实时Edge设备提供实时的RIC发现功能。
- 要使用该功能,您需要访问Edge 控制台应用程序RRSC。
- 该功能被称为广播数据流 - BDS(也称为基于标准的请求 - CBR)。
- BDS术语可以由SQL术语、File术语及其它BDS术语组成。
- OMM消费者应用程序需要使用定义的BDS术语名称作为RIC名称,提出一个SymbolList域请求。
- 响应以一个或多个Refresh消息的形式发送(最后一个Refresh消息的Complete标志设置为”true”)。
- 有效载荷(Payload)由Map组成,其中每个Map条目的关键值是实际的RIC本身,每个条目也有一个动作:添加(Add)、删除(Delete)或更新(Update)。
- 快照(Snapshot)请求将提供在该时间点上符合SQL术语标准的RIC列表。
- 当RIC过期或创建新的RIC时,流式(Streaming)请求将继续接收更新(Update)消息。
- 通过使用SymbolListBehaviors,还可以为SymbolList中的每个RIC自动请求快照(Snapshot)或流数据(Streaming data)。
补充资源
您可以在链接面板中找到API、教程、咨询台和问答论坛的链接。