通达信插件选股
通达信插件选股功能可能大家都有听说过,有很多牛X的炒股选手,将自己的选股经验,做成程序,以插件的方式集成在炒股软件中使用. 这样子每次选股,复盘,就不用那么麻烦,而且更加有数据依据,不会因为自己的错误判断导致买错股票.
运行截图
选择插件选股功能:
插件选股界面:
那么插件选股的插件是怎么开发的呢?
在通达信基础的炒股软件中,插件选股的插件是一个 .dll的文件,放到 股票软件文件夹内/Plugin/插件.dll 下面的, 调用的时候,在软件界面上找到插件选股,调用出来,进行选股操作.
插件选股,采用C++的方式进行编程,这种编程比用公式效率更高,同时也增加了安全性,自己的选股方法也不会被泄露. 适合做成软件对外出售,而不用担心自己的选股方法会被泄露.
下面是插件开发的几个关键文件
开发工具
采用Visual Studio 2022 社区版 开发.
测试软件:
- 通达信 PC客户端
- 招商证券 PC客户端
插件入口.h头文件
/**************************************************/
/* PLUGIN动态连接库导出头文件 */
/**************************************************/
#ifndef PLUGIN_H_
#define PLUGIN_H_
#ifdef PLUGIN_EXPORTS
#define PLUGIN_API extern "C" __declspec(dllexport)
#else
#define PLUGIN_API extern "C" __declspec(dllimport)
#endif
#include "OutStruct.h"
#pragma pack(push,1)
typedef struct tag_PluginPara //参数信息的结构定义
{
char acParaName[14]; //参数的中文名称
int nMin; //参数最小取值范围
int nMax; //参数最大取值范围
int nDefault; //系统推荐的缺省值
int nValue; //用户定义的值
}PLUGINPARAM;
typedef struct tag_PlugInfo
{
char Name[50]; //名称与版本
char Dy[30]; //产地
char Author[30]; //设计人
char Descript[100]; //选股描述
char Period[30]; //适应周期
char OtherInfo[300];
short ParamNum; //0<=参数个数<=4
PLUGINPARAM ParamInfo[4]; //参数信息,见上
}PLUGIN,*LPPLUGIN;
//回调函数,取数据接口
typedef long(CALLBACK * PDATAIOFUNC)(char * Code,short nSetCode,short DataType,void * pData,short nDataNum,NTime,NTime,BYTE nTQ,unsigned long);
//注册回调函数
PLUGIN_API void RegisterDataInterface(PDATAIOFUNC pfn);
//得到版权信息
PLUGIN_API void GetCopyRightInfo(LPPLUGIN info);
//按最近数据计算(nDataNum为ASK_ALL表示所有数据)
PLUGIN_API BOOL InputInfoThenCalc1(char * Code,short nSetCode,int Value[4],short DataType,short nDataNum,BYTE nTQ,unsigned long unused);
//选取区段计算
PLUGIN_API BOOL InputInfoThenCalc2(char * Code,short nSetCode,int Value[4],short DataType,NTime time1,NTime time2,BYTE nTQ,unsigned long unused);
#pragma pack(pop)
#endif
插件实现CPP
#include "stdafx.h"
#include "stdio.h"
#include "DataProcessing.h"
#include "StockSelector.h"
#include "StockAnalysis.h"
#include "Common.h"
#define PLUGIN_EXPORTS
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
PDATAIOFUNC g_pFuncCallBack;
//数据文件夹
char dataFolderPath[MAX_PATH] = { 0 };
//获取回调函数
void RegisterDataInterface(PDATAIOFUNC pfn)
{
g_pFuncCallBack = pfn;
}
/*
在股票交易策略中,箱体突破(Breakout)是一种常用的技术分析方法,
用来识别股票价格突破某一特定价格范围(箱体)的可能性。
在C++中实现这一策略,你可以通过以下几个步骤来完成:
确定箱体的边界:首先,你需要确定箱体的上界和下界。
这通常基于过去一段时间内的最高价和最低价。
监控价格:持续监控股票的当前价格。
检测突破:当股票价格突破箱体的上界或下界时,认为发生了突破。
执行交易:根据突破的方向(向上或向下),执行相应的买入或卖出操作。
下面是一个简单的C++示例,展示如何实现这一逻辑:
*/
//注册插件信息
void GetCopyRightInfo(LPPLUGIN info)
{
//填写基本信息
strcpy(info->Name,"筛选箱体突破股");
strcpy(info->Dy,"地域");
strcpy(info->Author,"软件开发大郭(GrabByte.com)");//插件作者
strcpy(info->Period,"箱体突破");
strcpy(info->Descript,"辅助复盘的箱体突破插件");//选股对象描述
strcpy(info->OtherInfo,"筛选箱体突破停股");
//调用前,清空日志
LogFileClear();
// 获取当前可执行文件所在的文件夹路径
GetExecutableFolder(dataFolderPath, sizeof(dataFolderPath));
//填写参数信息
info->ParamNum = 2;
strcpy(info->ParamInfo[0].acParaName,"无涨停日");
info->ParamInfo[0].nMin= 1;
info->ParamInfo[0].nMax= 200;
info->ParamInfo[0].nDefault= 20;
strcpy(info->ParamInfo[1].acParaName,"股价小于");
info->ParamInfo[1].nMin=1;
info->ParamInfo[1].nMax=1000;
info->ParamInfo[1].nDefault=21;
}
//1. Code为股票代码,如申请上证指数数据则赋值为999999
//2. nSetCode为市场分类,0为深市,1为沪市
//3. DataType为申请数据类型,缺省为日K线历史数据,如申请行情数据则赋值为REPORT_DAT2,其他相关类型参见OutStruct.h
//4. pData为申请数据缓冲区,若为NULL且nDataNum为 - 1则函数返回历史数据个数
//5. nDataNum为申请数据个数,若为 - 1且pData为NULL则函数返回历史数据个数
//6. 2个Ntime为申请数据的时间范围,缺省为全部本地历史数据
//7. nTQ是否为精确除权
//选股条件判断函数 默认调用此函数,使用全部本地历史数据
BOOL InputInfoThenCalc1(char* Code, short nSetCode, int Value[4], short DataType, short nDataNum, BYTE nTQ, unsigned long unused) //按最近数据计算
{
//处理全部历史数据
NTime tmpTime = { 0 };
return processData(Code, nSetCode, Value, DataType, nDataNum, tmpTime, tmpTime, nTQ, unused);
}
//指定日期段的时候,调用此函数进行选股
BOOL InputInfoThenCalc2(char* Code, short nSetCode, int Value[4], short DataType, NTime time1, NTime time2, BYTE nTQ, unsigned long unused) //选取区段
{
//处理时间段的历史数据
//数据个数
long nDataNum = g_pFuncCallBack(Code, nSetCode, DataType, NULL, -1, time1, time2, nTQ, 0);
//数据处理
return processData(Code, nSetCode, Value, DataType, nDataNum, time1, time2, nTQ, unused);
}
DataProcessing.h
#pragma once
// DataProcessing.h
#ifndef DATA_PROCESSING_H
#define DATA_PROCESSING_H
#include "Common.h"
BOOL processData(char* Code, short nSetCode, int Value[4], short DataType, short nDataNum, NTime time1, NTime time2, BYTE nTQ, unsigned long unused);
LPHISDAT allocateHisDat(short nDataNum);
long fetchHistoricalData(char* Code, short nSetCode, short DataType, LPHISDAT pHisDat, short nDataNum, NTime time1, NTime time2, BYTE nTQ);
#endif
DataProcessing.cpp
// DataProcessing.cpp
#include "DataProcessing.h"
#include "Common.h"
#include <cstdio>
#include <cstring>
#include <memory>
#include <iostream>
#include <vector>
#include <algorithm>
/*******************************************************/
extern PDATAIOFUNC g_pFuncCallBack;
LPHISDAT allocateHisDat(short nDataNum) {
return new HISDAT[nDataNum];
}
long fetchHistoricalData(char* Code, short nSetCode, short DataType, LPHISDAT pHisDat, short nDataNum, NTime time1, NTime time2, BYTE nTQ) {
return g_pFuncCallBack(Code, nSetCode, DataType, pHisDat, nDataNum, time1, time2, nTQ, 0);
}
// 计算成交量变化率
std::vector<float> calculateVolumeChangeRate(const HISDAT* pHisDat, long readnum) {
std::vector<float> volumeChangeRate(readnum);
for (long i = 1; i < readnum; ++i) {
float prevVolume = pHisDat[i - 1].fVolume;
volumeChangeRate[i] = (prevVolume != 0) ? ((pHisDat[i].fVolume - prevVolume) / prevVolume) * 100.0f : 0.0f;
}
return volumeChangeRate;
}
// 检测最近N日的下跌趋势
bool hasSignificantDowntrend(const std::vector<float>& prices, int nDays) {
int downDays = 0;
int requiredDownDays = static_cast<int>(nDays * 0.80); // 80%的天数都是下跌趋势
for (int i = prices.size() - nDays; i < prices.size(); ++i) {
if (prices[i] < prices[i - 1]) {
downDays++;
}
}
return downDays >= requiredDownDays;
}
// 检测突然放量
bool isSuddenVolumeIncrease(const std::vector<float>& volumeChangeRate, float threshold) {
return volumeChangeRate.back() > threshold;
}
// 计算枢轴点和支撑位、压力位
void calculatePivotPoints(float high, float low, float close, float& pivot, float& support1, float& support2, float& resistance1, float& resistance2) {
pivot = (high + low + close) / 3.0f;
support1 = (2 * pivot) - high;
support2 = pivot - (high - low);
resistance1 = (2 * pivot) - low;
resistance2 = pivot + (high - low);
}
// 主力潜伏检测函数
bool detectMainForcePresence(const HISDAT* pHisDat, long readnum, const std::vector<float>& prices, int nDays) {
// 检查成交量变化率
std::vector<float> volumeChangeRate = calculateVolumeChangeRate(pHisDat, readnum);
float volumeThreshold = 100.0f; // 设置一个阈值,例如100%
// 检测最近N日的下跌趋势
if (readnum >= nDays && !hasSignificantDowntrend(prices, nDays)) {
return false;
}
// 检测突然放量
return isSuddenVolumeIncrease(volumeChangeRate, volumeThreshold);
}
BOOL processData(char* Code, short nSetCode, int Value[4], short DataType, short nDataNum, NTime time1, NTime time2, BYTE nTQ, unsigned long unused) {
BOOL nRet = FALSE;
std::unique_ptr<HISDAT[]> pHisDat(new HISDAT[nDataNum]);
std::unique_ptr<STOCKINFO> pStockInfo(new STOCKINFO());
static char logMessage[1024] = { 0 };
// 获取股票基本信息
long readnum1 = g_pFuncCallBack(Code, nSetCode, STKINFO_DAT, pStockInfo.get(), 1, time1, time2, 0, 0);
if (readnum1 <= 0 || !pStockInfo) {
return FALSE;
}
// 排除名称中包含 "ST" 的股票
if (strstr(pStockInfo->Name, "ST") != nullptr) {
return FALSE;
}
// 获取历史数据
long readnum = g_pFuncCallBack(Code, nSetCode, DataType, pHisDat.get(), nDataNum, time1, time2, nTQ, 0);
if (readnum <= 0 || pHisDat[readnum - 1].Close == 0) {
return FALSE;
}
// 获取收盘价数据
std::vector<float> prices(readnum);
for (long i = 0; i < readnum; ++i) {
prices[i] = pHisDat[i].Close;
}
// 使用 Value[0] 作为最近N日的交易日
int nDays = Value[0];
// 检测主力潜伏
if (detectMainForcePresence(pHisDat.get(), readnum, prices, nDays)) {
sprintf(logMessage, "[processData] 检测到主力潜伏 股票代码: %s 股票名称: %s", Code, pStockInfo->Name);
LogMessage(logMessage);
nRet = TRUE;
}
// 计算最近一天的高点、低点和收盘价
float high = pHisDat[readnum - 1].High;
float low = pHisDat[readnum - 1].Low;
float close = pHisDat[readnum - 1].Close;
// 计算枢轴点和支撑位、压力位
float pivot, support1, support2, resistance1, resistance2;
calculatePivotPoints(high, low, close, pivot, support1, support2, resistance1, resistance2);
// 记录支撑位和压力位,并包括股票代码和股票名称
sprintf(logMessage, "股票代码: %s 股票名称: %s 支撑位1: %.2f, 支撑位2: %.2f, 压力位1: %.2f, 压力位2: %.2f",
Code, pStockInfo->Name, support1, support2, resistance1, resistance2);
LogMessage(logMessage);
return nRet;
}