首先声明,鄙人是编程人员,不是股民。对选股概念了解甚少。本文仅作编程人员学习借鉴之用。不对选股理论进行探讨和解释。

    以前有客户找我做过通达信插件选股的小任务,当时第一次接触面向接口(此类“接口”)编程,也是第一次接触到股票里相关的概念。最近由于接手一个任务,与上次开发相类似,故旧事重提,仔细研究一番。将个人学习研究所得知识与大家分享。在网上搜相关资料,可用的、有价值的甚少。希望此文能给将要研究通达信插件选股的编程人员一点点帮助。

 

    编程环境:Viusal Studio 2010

    证券终端:广发证券至强版v6.06

 

    一、首先说一下在VS2010下,调试DLL的方法:

    1,将Debug目录下的dll(例如:MyPlugin.dll)文件复制到通达信根目录下的plugin文件夹下,此步骤可以手动,不过既然是开发程序,肯定要调试N遍,会输出N次dll文件,我们可以在VS写个后期生成事件,例如:copy "C:\Documents and Settings\Administrator\桌面\choice2\Debug\choice2.dll" D:\通达信\plugin\choice2.dll。此步设置如下图。通达信插件选股(基于通达信插件编…-编程知识网 

    2,设置附加进程所在目录,如下图。

通达信插件选股(基于通达信插件编…-编程知识网

经过以上设置,即可调试我们开发的dll,监视变量的值。编译成功后,设置断点,启动调试,然后操作通达信进行选股,选股开始即会跳到VS调试界面。

    二、通达信官方提供的插件选股例子解析。(网上可以下载到)
    接口文档里,已经清楚的写出了接口函数的相关说明。但是某些概念依然模糊,说一下我的理解。

    1,必须足够了解的几个导出函数:

       RegisterDataInterface  注册回调函数,也就是PDATAIOFUNC,这个回调函数用来申请历史数据

       GetCopyRightInfo       填写插件信息的函数,很easy

       InputInfoThenCalc1     默认调用此函数,使用全部本地历史数据

       InputInfoThenCalc2     指定日期段的时候,调用此函数进行选股

    2,必须足够清楚的内容:

       示例程序里的m_pfn(Code,nSetCode,DataType,pHisDat,nDataNum,tmpTime,tmpTime,nTQ,0)函数,其中非常重要的参数是DataType,pHisDat,nDataNum。分别表示:想要申请的数据类型,数据存储缓冲区(内存),想要获取数据的个数上限(实际上得到的很可能没有那么多),此函数的返回值就是实际上获取到的数据个数。

       InputInfoThenCalc1(char * Code,short nSetCode,int Value[4],short DataType,short nDataNum,BYTE nTQ,unsigned long unused)    以下讲解中,以全部本地历史数据为例,即选股调用此函数

       InputInfoThenCalc2(char * Code,short nSetCode,int Value[4],short DataType,NTime time1,NTime time2,BYTE nTQ,unsigned long unused)

       通过下断点监视,得到结论:1.插件选股过程,针对每支股票调用一次InputInfoThenCalc1函数,如果返回值为TRUE,表示该股票被选中,反之,不被选中。2.插件选股最多支持四个参数,四个参数的值,在插件的代码里用Value[4]来存储。3.当我用日线数据获取数据时,nDataNum被赋值为2000,这个数据个数上限,基本足够我们编写简单的选股程序所用。

    三、选出涨幅大于指定值的股票例子

BOOL InputInfoThenCalc1(char * Code,short nSetCode,int Value[4],short DataType,short nDataNum,BYTE nTQ,unsigned long unused) //按最近数据计算
{
 BOOL nRet = FALSE;
 NTime tmpTime;
 memset(&tmpTime,0,sizeof(NTime));  //时间结构置为0,获取全部本地历史数据

 LPREPORTDAT pReport = new REPORTDAT;
 if(m_pfn(Code,nSetCode,REPORT_DAT,pReport,1,tmpTime,tmpTime,0,0) == 1)
 {
  if(pReport->Close > 0.001 && (100*(pReport->Now-pReport->Close)/pReport->Close) > Value[0])
   nRet = TRUE;
 }
 delete pReport;
 return nRet;
}

特别注意:计算涨幅时,要使用行情数据,所以将数据类型指定为REPORT_DAT,返回数据个数上限指定为1即可,因为我们只用当前的数据就够了。涨幅的计算公式,百度一堆一堆。相对来说,这个是最简单的。很容易理解。

    四、选出换手率在指定范围之内的股票

BOOL InputInfoThenCalc1(char * Code,short nSetCode,int Value[4],short DataType,short nDataNum,BYTE nTQ,unsigned long unused) //按最近数据计算
{
 BOOL nRet = FALSE;
 NTime tmpTime;
 memset(&tmpTime,0,sizeof(NTime));
 LPREPORTDAT pReport = new REPORTDAT;
 long readnum = m_pfn(Code,nSetCode,REPORT_DAT,pReport,1,tmpTime,tmpTime,0,0);
 LPSTOCKINFO pStockInfo = new STOCKINFO;
 long readnum1 = m_pfn(Code,nSetCode,STKINFO_DAT,pStockInfo,1,tmpTime,tmpTime,0,0);
 float HuanShou = ( pReport->Volume / pStockInfo->ActiveCapital ) * 10000;
 
 

 if( HuanShou > Value[0] && HuanShou < Value[1] )
 {
  nRet = TRUE;
 }
 //delete pReportDat;
 delete pStockInfo;
 delete pReport;
 return nRet;
}

换手率=某一段时期内的成交量/发行总股数×100% (在我国:成交量/流通总股数×100%)(引用百度百科)

按照上面的公式来计算,然后根据我们需要的小数点位置来计算扩大或缩小的比例即可。同上例,我们一定要注意我们想要的数据,要指定的数据类型。计算换手率,需要成交量和总股数两个数据,成交量使用当前的值,所以我们获取一个当前的即可,数据类型指定为REPORT_DAT行情数据;总股数我并不知道是属于哪个数据类型的,一遍一遍尝试改变数据类型之后,终于得到了一个准确的值,此时,数据类型指定为STKINFO_DAT。(有时候,我们对几种可能不是很了解的时候,可以一个一个去尝试,前提是,我们要知道正确的结果是什么样的。一个办法就是,先记录下通达信界面上显示的该数据的值)。

本来是打算顺便计算一下量比数据的,也指定个范围进行选股。经过尝试后,发现通达信的接口貌似有问题。获取成交量的时候,只能利用行情数据来获取当前的成交量,无法获取历史成交量。查阅资料:量比=现成交总手/〖(过去5个交易日平均每分钟成交量)×当日累计开市时间(分)〗。(摘自百度百科)按照以上公式,我们需要获取到今日止连续6天的成交量。所以得到如下代码:

LPREPORTDAT pReportDat = new REPORTDAT[nDataNum];
 long readnum2 = m_pfn(Code,nSetCode,DataType,pReportDat,nDataNum,tmpTime,tmpTime,0,0);
 float * VolPoint= new float[readnum2];
 for(int i=0;i < readnum2;i++)
 {
  VolPoint[i]=pReportDat[i].Volume;
 }
 float VolPerDay = VolPoint[readnum2-2]+VolPoint[readnum2-3]+VolPoint[readnum2-4]+VolPoint[readnum2-5]+VolPoint[readnum2-6];
 float liangbi = VolPoint[readnum2-1] / (VolPerDay/5);

仔细分析,也许您会发现,代码表达的意思,并不是查阅资料所给出的公式。是的,如果按照公式来算,我们应该得到每天的,每分钟的成交量数据,才能算出分钟平均值。这样的情况,我们就不能使用日线数据。就需要下载分时图数据,按照标准的公式计算,保证您不会出错。但是这里,我仔细观察计算得到,以上代码计算方法得到的数据和通达信界面上显示的量比是相吻合的。

重点指出:以上计算量比的代码,并不能正常工作。因为历史成交量无法获取,我尝试过各种数据类型。当我获取历史成交量时,Volume字段的值都是莫名其妙的数字。但是更奇怪的是,其他字段的值都正常。琢磨了好久,去了通达信官网论坛,发现一个帖子和我所遇到的问题一样。帖子地址http://tdx.com.cn/dispbbs.asp?boardid=15&Id=173264  尝试过各种结构体和数据类型组合后。我觉得以上求量比的代码应该没有问题。结果如下图所示:

通达信插件选股(基于通达信插件编…-编程知识网
在这种情况下,勉强利用了行情数据得到了当前成交量,才得以计算出换手率。

 

    五、计算CLOSE的EMA进行选股

//选股函数,参照官方例子程序+百度EMA算法得出如下代码

BOOL InputInfoThenCalc1(char * Code,short nSetCode,int Value[4],short DataType,short nDataNum,BYTE nTQ,unsigned long unused) //按最近数据计算
{
 BOOL nRet = FALSE;
 NTime tmpTime={0};

 LPHISDAT pHisDat = new HISDAT[nDataNum];  //数据缓冲区
 long readnum = m_pfn(Code,nSetCode,DataType,pHisDat,nDataNum,tmpTime,tmpTime,nTQ,0);  //利用回调函数申请数据,返回得到的数据个数
 
 if( readnum > max(Value[0],Value[1]) )
 {
  float *pMa1 = new float[readnum];
  float *pMa2 = new float[readnum];
  for(int i=0;i < readnum;i++)
  {
   pMa1[i] = pHisDat[i].Close;
   pMa2[i] = pHisDat[i].Close;
  }
  float *EmaPoint1 = new float[readnum-Value[0]+1];
  float *EmaPoint2 = new float[readnum-Value[1]+1];
  memset( EmaPoint1 , 0 , readnum-Value[0]+1 );
  memset( EmaPoint2 , 0 , readnum-Value[1]+1 );
  AfxCalcEma( pMa1 , readnum , Value[0] , EmaPoint1);
  AfxCalcEma( pMa2 , readnum , Value[1] , EmaPoint2);
  if( AfxCross( EmaPoint1 , readnum-Value[0]+1 , EmaPoint2 , readnum-Value[1]+1 , Value[2] ) == 1 )
  {
   nRet = TRUE;
  }
  delete []EmaPoint1;EmaPoint1=NULL;
  delete []EmaPoint2;EmaPoint2=NULL;
  delete []pMa1;pMa1=NULL;
  delete []pMa2;pMa2=NULL;
 }
 delete []pHisDat;pHisDat=NULL;
 return nRet;
}
//计算CLOSE的EMA函数实现如下

void AfxCalcEma(float *pData , int nDataNum , int nDays , float *EmaPoint)
{
 int nSumOfDays = 0;
 int flag = 0;
 int iCount =0;
 for(iCount=nDays;iCount>0;iCount–)
 {
  nSumOfDays+=iCount;
 }
 for(iCount=nDays;iCount>0;iCount–)
 {
  float temp = (iCount/(float)nSumOfDays)*pData[iCount-1];
  EmaPoint[0] += temp;
 }
 for(iCount = 1 , flag = nDays; iCount<nDataNum-nDays+1; iCount++ , flag++)
 {
  EmaPoint[iCount] = (2*pData[flag]+(nDays-1)*EmaPoint[iCount-1])/(nDays+1);
 }
}

//计算指定之间范围内,短线是否从下方穿越长线

int AfxCross( float *pMaPoint1 , int DataNum1 , float *pMaPoint2 , int DataNum2 , int nDaysNum )
{
 int flag1 = DataNum1-1;
 int flag2 = DataNum2-1;
 if( pMaPoint1[flag1] > pMaPoint2[flag2] )
 {
  flag1–;
  flag2–;
  for(int iCount = nDaysNum; iCount>0; iCount–)
  {
   if( pMaPoint1[flag1] < pMaPoint2[flag2] )
   {
    return 1;
    break;
   }
   else
   {
    flag1–;
    flag2–;
   }
  }
 }
 else
 {
  return 0;
 }
}

此例相对比较复杂,不过复杂的地方是算法部分,并不是程序技术,主要是数学方面的知识。您有好的选股理念,在某种情况下,是可以用通达信插件选股的方式实现的。

 

结束语:谨以此文,献给打算研究、正在研究和曾经研究过通达信插件选股的朋友们。这只是编程里的冰山一角。希望我们都能做一个优秀的开发人员,编写出高质量的程序。处女作,大家多多批评指正。