开发环境

  • 编程环境:Viusal Studio 2017 社区版

  • 证券终端:招商证券客户端

调试DLL的方法

输出文件设置成股票软件的Plugin目录

C:\zd_zsone\Plugin\MyPlugin.dll

我们直接将生成的DLL目标改成插件目录 :

设置附加进程所在目录

经过以上设置,即可调试我们开发的dll,监视变量的值。

编译成功后,设置断点,启动调试,然后操作通达信进行选股,选股开始即会跳到VS调试界面。

二、通达信官方提供的插件选股例子解析。

接口文档里,已经清楚的写出了接口函数的相关说明。但是某些概念依然模糊,说一下我的理解。

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

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

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

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

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

必须足够清楚的内容:

示例程序里的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) //按最近数据计算
{undefined
 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)
 {undefined
  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) //按最近数据计算
{undefined
 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] )
 {undefined
  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++)
 {undefined
  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字段的值都是莫名其妙的数字。但是更奇怪的是,其他字段的值都正常。琢磨了好久,去了通达信官网论坛,发现一个帖子和我所遇到的问题一样。

尝试过各种结构体和数据类型组合后。

我觉得以上求量比的代码应该没有问题。结果如下图所示:

在这种情况下,勉强利用了行情数据得到了当前成交量,才得以计算出换手率。

五、计算CLOSE的EMA进行选股

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

BOOL InputInfoThenCalc1(char * Code,short nSetCode,int Value[4],short DataType,short nDataNum,BYTE nTQ,unsigned long unused) //按最近数据计算
{undefined
 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]) )
 {undefined
  float *pMa1 = new float[readnum];
  float *pMa2 = new float[readnum];
  for(int i=0;i < readnum;i++)
  {undefined
   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 )
  {undefined
   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)
{undefined
 int nSumOfDays = 0;
 int flag = 0;
 int iCount =0;
 for(iCount=nDays;iCount>0;iCount--)
 {undefined
  nSumOfDays+=iCount;
 }
 for(iCount=nDays;iCount>0;iCount--)
 {undefined
  float temp = (iCount/(float)nSumOfDays)*pData[iCount-1];
  EmaPoint[0] += temp;
 }
 for(iCount = 1 , flag = nDays; iCount<nDataNum-nDays+1; iCount++ , flag++)
 {undefined
  EmaPoint[iCount] = (2*pData[flag]+(nDays-1)*EmaPoint[iCount-1])/(nDays+1);
 }
}

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

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

此例相对比较复杂,不过复杂的地方是算法部分,并不是程序技术,主要是数学方面的知识。

您有好的选股理念,在某种情况下,是可以用通达信插件选股的方式实现的。
联系我,我可以帮您用程序实现出来。