一、引子
通达信商品指数一共有23个,如下图所示:
如果想获取历史数据,只需要通过通达信的数据下载和导出功能即可,现在我们需要获取这23个指数的实时数据,通过导出功能就没有办法了。
在最初的阶段,考虑的是合成的逻辑,即找到每一个指数的成份股,计算出对应的加权指数后再进行算术平均来计算对应的指数。这种方法的好处是每一样都可以算出来,缺点是总是有一点点误差,可能是商品指数的加权应该有一种修正逻辑,并且由于每次的计算都有误差,随着时日久远,误差会越来越大,造成完全不可用,后来没有办法,每天下载23个期货指数做为修正基数,勉强也用了一段时间。最近不知为何,忽然发现指数完全对不上,特别是通达信商品指数,乱的不像样,把之前的所有努力都废了。
自己计算看来没有办法了,可能是通达信中修正逻辑改了或是其它什么原因导致算不准了,只能考虑直接使用通达信的板块指数
二、pytdx
这种东西看起来可以获取到通达信的几乎所有数据,获取到
通过如下代码:
from pytdx.exhq import TdxExHq_API
from pytdx.exhq import TDXParams
api = TdxExHq_API()
with api.connect("182.175.240.157", 7727):
df = api.to_df(api.get_markets())
print(df)
data = api.to_df(api.get_instrument_bars(TDXParams.KLINE_TYPE_1MIN, 30, "AGL9", 0, 10))
print(data)
可以连接上期货的扩展板块,并且在交易日时也可以获取到除了商品期货指数的所有期货数据(试验了一下,今天是什么数据也获取不到,应该还是该接口对于扩展数据的不稳定支持有关)
看起来商品期货指数想通过简单的api获取是不可能了
三、通达信插件
1、通达信公式的逻辑
这一块的逻辑为针对特定数据,如代码为T开头的且必须是4个字符串的认为是商品期货指数,这样就不用所有数据都保存了,这一层过滤可在公式端进行,满足这些条件的传送对应的除T以外的代码,不满足的传递-1
T1: SUBSTR(CODE,1,1) == 'T',NODRAW;
S_CODE:= SUBSTR(CODE,2,3);
T2: STRLEN(CODE)==4, NODRAW;
S_DATE:= CON2STR(DATE+19000000,0);
S_TIME:= CON2STR(TIME,0);
T3: PERIOD==0, NODRAW;
D_CODE:=IF(T1 AND T2 AND T3, STR2CON(S_CODE), -1);
考虑到我们要存储的是分钟数据,因此至少要先传入日期和分钟,由于根据插件标准,一次最多3个入参且必须是浮点数,所以一个K线数据至少需要4个函数才能实现
MM1:TDXDLL2(1,D_CODE,STR2CON(S_DATE),STR2CON(S_TIME));
MM2:TDXDLL2(2,D_CODE,OPEN,HIGH);
MM3:TDXDLL2(3,D_CODE,LOW,CLOSE);
MM4:TDXDLL2(4,D_CODE,VOL,VOLINSTK);
这样我们在公式端就完成了一个K线的基本数据的传入了,至于为何每次都需要传入D_CODE,是为了2,3,4函数中识别是否为同一个商品指数而做。
2、通达信插件的逻辑
1)插件初始化
考虑到我们需要存储到文件中,每次打开文件写入再关闭文件可能会影响效率,可以在插件刚刚加载时将对应的23个文件全部打开
int init()
{
table_init();
char buf[512] = { 0 };
for (auto it = g_tables.begin(); it != g_tables.end(); it++) {
bool FileExist = false;
memset(buf, 0, sizeof(buf));
sprintf(buf, "C:/TdxFutureBk/%s_%s.csv", it->first.c_str(), it->second.c_str());
if (!_access(buf, 0))
FileExist = true;
FILE *p = fopen(buf, "a");
if (p != NULL) {
g_fstreams[it->first] = p;
if (!FileExist) {
fflush(p);
}
}
}
return 0;
}
g_tables是为了后续将数据存入数据库而准备,每一个商品指数对应板块表名,这里可以不考虑
然后在动态库的入口先调用init即可
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
init();
// 第一次将一个DLL映射到进程地址空间时调用
// The DLL is being mapped into the process' address space.
break;
2)void FutureBk1(int DataLen, float *pfOUT, float *pfINa, float *pfINb, float *pfINc)的逻辑
此函数首先需判断DataLen是否大于1,否的话退出
然后再判断pfINa是否大于0,否的话说明不是国内期货指数,也退出
接下来从INa中获取代码,与“T"拼接在一起作为指数代码code,从INb中获取日期,从INc中获取时间,
这些完成后进行全局的数据初始化
char szTime[32] = { 0 };
memset(szTime, 0, sizeof(szTime));
sprintf(szTime, "%04d-%02d-%02d %02d:%02d:%02d", iDate / 10000, (iDate % 10000) / 100,
(iDate % 10000) % 100, iTime / 100, iTime % 100, 0);
memset(buf, 0, sizeof(buf));
sprintf(buf, "%s,%s,1", bkCode.c_str(), szTime);
g_datas[code] = buf;
3)void FutureBk2,3(int DataLen, float *pfOUT, float *pfINa, float *pfINb, float *pfINc)
DataLen和pfINa的检查是与Bk1一致的
然后将INb转成open, INc转成high
然后检查code对应的g_datas不存在或是对应的值为空字符串,说明数据不合法,退出
如果通过检查,则将转换成的open,high追加到全局变量g_datas中
memset(buf, 0, sizeof(buf));
sprintf(buf, "%s,%s,%s", data.c_str(), open.c_str(), high.c_str());
g_datas[code] = buf;
FutureBk3与2类似逻辑,只是追加的为low,close
4、void FutureBk4(int DataLen, float *pfOUT, float *pfINa, float *pfINb, float *pfINc)
FutureBk4与3的检查是一样的,通过后追加成交量和持仓量
然后需要通过code为K查找g_fstreams是否存在此code,存在的话调用g_fstreams将文本存入对应的文件中
至此,插件内容就写完了。
四、使用
生成的动态库TdxFutureBk.dll放置于T0002\dlls中然后绑定在2号库,然后开启通达信,将多股同列调成5*5,将调整成1分钟周期放置在那儿(在通达信公式中限定只存储1分钟板块数据),当有实时行情时,就会将所有板块自动存于相应的板块文件中(一个板块约一分钟存入8笔左右)
五、存储到数据库中
如果需要对数据进行实时处理,更好的办法是存于redis中或是mysql数据库中,这样就可以利用实时板块数据来计算相应的指标了