小 T 导读:如果我们要做行情 tick 数据的存储,怎样的数据结构查找起来才会比较快?在加入 TDengine 之前,本文作者丁博在弘源泰平量化投资做量化工程师,曾经遇到过这一类存储行情 tick 数据的问题,本文会就此问题进行详细的技术解读。
01
内存存储方案
1
两级 map 方案
using namespace std;
int main()
{
unordered_map<TThostFtdcInstrumentIDType, map<long, CThostFtdcDepthMarketDataField>*> tickData;
}
2
map + array
-
交易策略通常会依赖标准化的行情计算交易信号,收行情和标准化并作一步会更节省时间。 -
可以直接用数组下标索引对应时间的行情,查找的时间复杂度为 O(1)。
using namespace std;
int main()
{
unordered_map<TThostFtdcInstrumentIDType, array<CThostFtdcDepthMarketDataField, 28800>> tickData;
}
02
持久化存储方案
1
下载 TDengine Database Server
2
安装并启动
sudo dpkg -i TDengine-server-2.4.0.7-Linux-x64.deb
sudo rpm -ivh TDengine-server-2.4.0.7-Linux-x64.rpm
3
建行情表
-
进入 taos 命令行
bo@RDBB:~$ taos
Welcome to the TDengine shell from Linux, Client Version:2.4.0.12
Copyright (c) 2020 by TAOS Data, Inc. All rights reserved.
-
执行下面的语句
create database marketdata;use marketdata;create stable tick( ts timestamp, updatetime binary(9), updatemillisec int, askprice1 double, bidprice1 double, askvolume1 int, bidvolume1 int) tags (exchangeid binary(9));
-
查看表结构
taos> desc tick;
Field | Type | Length | Note |
=================================================================================
ts | TIMESTAMP | 8 | |
updatetime | BINARY | 9 | |
updatemillisec | INT | 4 | |
askprice1 | DOUBLE | 8 | |
bidprice1 | DOUBLE | 8 | |
askvolume1 | INT | 4 | |
bidvolume1 | INT | 4 | |
exchangeid | BINARY | 9 | TAG |
Query OK, 8 row(s) in set (0.000378s)
4
写入行情
using namespace std;
void insertTickData(TAOS* taos, CThostFtdcDepthMarketDataField &tick) {
stringstream sql;
// 会自动创建子表tick.InstrumentID
sql << "insert into " << tick.InstrumentID << " using tick tags("
<< tick.ExchangeID << ") values(now, '" << tick.UpdateTime << "', "
<< tick.UpdateMillisec << "," << tick.AskPrice1 << "," << tick.BidPrice1
<< "," << tick.AskVolume1 << "," << tick.BidVolume1 << ")";
TAOS_RES *res = taos_query(taos, sql.str().c_str());
if (res == nullptr || taos_errno(res) != 0) {
cerr << "insertTitckData failed," << taos_errno(res) << ", " << taos_errstr(res) << endl;
}
}
int main()
{
TAOS *taos = taos_connect("localhost", "root", "taosdata", "marketdata", 6030);
// 构造测试数据
CThostFtdcDepthMarketDataField tick;
strcpy_s(tick.InstrumentID, "IH2209");
strcpy_s(tick.UpdateTime, "14:10:32");
strcpy_s(tick.ExchangeID, "DEC");
tick.UpdateMillisec = 500;
tick.AskPrice1 = 123.8;
tick.BidPrice1 = 123.4;
tick.AskVolume1 = 10;
tick.BidVolume1 = 9;
// 写入测试数据
insertTickData(taos, tick);
taos_close(taos);
}
5
查询最新的行情
using namespace std;
CThostFtdcDepthMarketDataField* getLastTick(TAOS* taos, const char* instrumentID) {
string sql("select last(*) from ");
sql += instrumentID;
TAOS_RES* res = taos_query(taos, sql.c_str());
if (res == nullptr || taos_errno(res) != 0) {
cerr << "getLastTick failed," << taos_errno(res) << ", " << taos_errstr(res) << endl;
return nullptr;
}
TAOS_ROW row = taos_fetch_row(res);
if (row == nullptr) {
return nullptr;
}
CThostFtdcDepthMarketDataField* tick = new CThostFtdcDepthMarketDataField();
//int64_t ts = *((int64_t*)row[0]);
memcpy(tick->UpdateTime, row[1], 9);
tick->UpdateMillisec = *(int*)row[2];
tick->AskPrice1 = *((double *)row[3]);
tick->BidPrice1 = *((double*)row[4]);
taos_free_result(res);
return tick;
}
int main() {
TAOS* taos = taos_connect("localhost", "root", "taosdata", "marketdata", 6030);
CThostFtdcDepthMarketDataField* tick = getLastTick(taos, "IH2209");
cout << "askPrice1=" << tick->AskPrice1 << " bidPrice1=" << tick->BidPrice1 << endl;
delete tick;
taos_close(taos);
}

03
从实际业务出发的实践经验分享
依赖多,稳定性较差:PMS作为多品种的投后分析服务, 需要使用到各种日线数据、当天实时行情数据、当天分钟数据等,在数据获取方面需要依赖Http以及Postgres、LevelDB等数据库。过于多的数据获取链路会导致平台可靠性降低,同时依赖于其他各个服务,导致查询问题过于复杂。
-
性能不能满足需求:PMS作为多品种投后分析,在算法分析层面需要大量的行情获取,而且对行情获取的性能也有较大的要求,当前所有行情会占据大量分析的性能。

本文分享自微信公众号 - TDengine(taosdata_news)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。