<think>我们计划开发一个通达信DLL插件,用于计算全市场日涨幅中位数,并将结果存储为通达信自定义数据格式(.th000文件),以便在通达信软件中使用。
开发步骤:
1. 理解通达信DLL接口:通达信提供了扩展DLL的接口,我们需要按照其规范编写函数。
2. 读取全市场日线数据:遍历通达信安装目录下的日线数据文件(.day文件)。
3. 计算每日全市场涨幅中位数:对每个交易日,收集所有正常交易股票的涨幅,计算中位数。
4. 将计算结果保存为.th000文件:按照通达信自定义数据格式存储。
5. 提供实时更新功能:DLL应支持增量更新,避免重复计算。
通达信DLL接口要求:
- 必须导出`RegFunc`和`UnRegFunc`两个函数
- 在`RegFunc`函数中注册自定义函数
自定义数据格式(.th000):
通达信自定义数据文件格式如下:
- 文件头:16字节
DWORD dwVersion; // 版本号,一般为0x00000100
DWORD dwStockNum; // 股票数量(对于序列数据,这里为1)
DWORD dwPeriod; // 周期,0=日线,1=周线,2=月线,3=年线,4=1分钟,5=5分钟,6=15分钟,7=30分钟,8=60分钟
DWORD dwReserved; // 保留
- 数据头(每个股票的数据头,对于序列数据只有一个):
char szLabel[32]; // 数据标签,例如"MarketMedian"
char szDescription[64]; // 数据描述
DWORD dwCount; // 数据点数量
DWORD dwStartDate; // 起始日期(YYYYMMDD)
DWORD dwEndDate; // 结束日期(YYYYMMDD)
DWORD dwReserved[8]; // 保留
- 数据体:每个数据点由两个DWORD组成(日期和值)
DWORD dwDate; // 日期(YYYYMMDD)
float fValue; // 数值
我们将按照上述格式生成一个自定义数据文件,存储每日的涨幅中位数。
代码结构:
1. 遍历市场日线数据目录(sh/lday, sz/lday)的所有.day文件。
2. 读取每个.day文件,解析出每天的收盘价,计算日涨幅(需要前一天的收盘价)。
3. 按日期收集所有股票的日涨幅(跳过新股上市首日等无前收盘的情况)。
4. 对每个交易日,计算涨幅中位数。
5. 将结果按.th000格式写入文件。
注意:由于全市场数据量很大,我们需要注意性能。可以考虑以下优化:
- 多线程读取文件(但注意文件IO瓶颈)
- 按日期索引,避免重复计算
由于DLL需要被通达信调用,我们将在DLL中导出一个函数,比如`CalculateMarketMedian`,当通达信调用这个函数时,执行计算并生成.th000文件。
以下是完整的C++代码示例(Windows DLL):</think># 通达信日涨幅中位数DLL开发指南
下面我将详细介绍通达信日涨幅中位数DLL的开发过程,并提供完整代码。该DLL将计算全市场日涨幅中位数,并将结果存储为通达信自定义数据格式(.th000),以便在通达信软件中使用。
## 开发原理与流程
### 1. 通达信DLL接口规范
通达信DLL需要实现以下核心函数:
```cpp
// 注册函数
__declspec(dllexport) int RegFunc(int hWnd);
// 取消注册
__declspec(dllexport) int UnRegFunc(int hWnd);
// 自定义计算函数
__declspec(dllexport) int CalculateMarketMedian(void);
```
### 2. 数据处理流程
```mermaid
graph TD
A[启动DLL] --> B[遍历市场数据目录]
B --> C[读取.day文件]
C --> D[解析日线数据]
D --> E[计算日涨幅]
E --> F[按日期分组]
F --> G[计算每日涨幅中位数]
G --> H[生成.th000文件]
H --> I[在通达信中显示结果]
```
## 完整DLL实现代码
```cpp
#include <Windows.h>
#include <vector>
#include <map>
#include <algorithm>
#include <fstream>
#include <string>
#include <io.h>
#include <direct.h>
#pragma pack(push, 1)
struct DayRecord {
DWORD date;
DWORD open;
DWORD high;
DWORD low;
DWORD close;
float amount;
DWORD volume;
DWORD reserved;
};
#pragma pack(pop)
struct CustomDataHeader {
DWORD dwVersion = 0x00000100; // 版本号
DWORD dwStockNum = 1; // 股票数量
DWORD dwPeriod = 0; // 0=日线
DWORD dwReserved = 0; // 保留
};
struct StockDataHeader {
char szLabel[32] = "MarketMedian"; // 数据标签
char szDescription[64] = "全市场日涨幅中位数"; // 数据描述
DWORD dwCount; // 数据点数量
DWORD dwStartDate; // 起始日期
DWORD dwEndDate; // 结束日期
DWORD dwReserved[8] = {0}; // 保留
};
struct DataPoint {
DWORD date;
float value;
};
// 全局变量存储通达信安装路径
char g_szTdxPath[MAX_PATH] = "C:\\new_tdx";
// 计算单只股票日涨幅
void CalculateStockChanges(const std::vector<DayRecord>& records,
std::map<DWORD, std::vector<float>>& dailyChanges) {
for (size_t i = 1; i < records.size(); i++) {
float prevClose = records[i-1].close / 100.0f;
float currentClose = records[i].close / 100.0f;
float change = ((currentClose - prevClose) / prevClose) * 100.0f;
dailyChanges[records[i].date].push_back(change);
}
}
// 计算每日涨幅中位数
void CalculateDailyMedians(const std::map<DWORD, std::vector<float>>& dailyChanges,
std::vector<DataPoint>& medians) {
for (const auto& pair : dailyChanges) {
DWORD date = pair.first;
std::vector<float> changes = pair.second;
if (changes.empty()) continue;
// 排序并计算中位数
std::sort(changes.begin(), changes.end());
size_t n = changes.size();
float median;
if (n % 2 == 1) {
median = changes[n/2];
} else {
median = (changes[n/2 - 1] + changes[n/2]) / 2.0f;
}
medians.push_back({date, median});
}
// 按日期排序
std::sort(medians.begin(), medians.end(),
[](const DataPoint& a, const DataPoint& b) {
return a.date < b.date;
});
}
// 生成.th000文件
bool GenerateTh000File(const std::vector<DataPoint>& medians,
const char* outputPath) {
if (medians.empty()) return false;
std::ofstream outFile(outputPath, std::ios::binary);
if (!outFile) return false;
// 写入文件头
CustomDataHeader header;
outFile.write(reinterpret_cast<char*>(&header), sizeof(header));
// 写入股票数据头
StockDataHeader stockHeader;
stockHeader.dwCount = medians.size();
stockHeader.dwStartDate = medians.front().date;
stockHeader.dwEndDate = medians.back().date;
outFile.write(reinterpret_cast<char*>(&stockHeader), sizeof(stockHeader));
// 写入数据点
for (const auto& point : medians) {
outFile.write(reinterpret_cast<const char*>(&point.date), sizeof(DWORD));
outFile.write(reinterpret_cast<const char*>(&point.value), sizeof(float));
}
return true;
}
// 主计算函数
void CalculateMarketMedian() {
// 获取市场目录
char szShPath[MAX_PATH], szSzPath[MAX_PATH];
sprintf_s(szShPath, "%s\\vipdoc\\sh\\lday", g_szTdxPath);
sprintf_s(szSzPath, "%s\\vipdoc\\sz\\lday", g_szTdxPath);
// 收集所有.day文件
std::vector<std::string> files;
struct _finddata_t fileinfo;
intptr_t handle;
// 上海市场
char pattern[MAX_PATH];
sprintf_s(pattern, "%s\\*.day", szShPath);
handle = _findfirst(pattern, &fileinfo);
if (handle != -1) {
do {
char fullPath[MAX_PATH];
sprintf_s(fullPath, "%s\\%s", szShPath, fileinfo.name);
files.push_back(fullPath);
} while (_findnext(handle, &fileinfo) == 0);
_findclose(handle);
}
// 深圳市场
sprintf_s(pattern, "%s\\*.day", szSzPath);
handle = _findfirst(pattern, &fileinfo);
if (handle != -1) {
do {
char fullPath[MAX_PATH];
sprintf_s(fullPath, "%s\\%s", szSzPath, fileinfo.name);
files.push_back(fullPath);
} while (_findnext(handle, &fileinfo) == 0);
_findclose(handle);
}
// 按日期存储每日涨幅
std::map<DWORD, std::vector<float>> dailyChanges;
// 处理每个股票文件
for (const auto& file : files) {
std::ifstream inFile(file, std::ios::binary);
if (!inFile) continue;
// 读取文件大小
inFile.seekg(0, std::ios::end);
size_t fileSize = inFile.tellg();
inFile.seekg(0, std::ios::beg);
// 计算记录数
size_t numRecords = fileSize / sizeof(DayRecord);
if (numRecords == 0) continue;
// 读取所有记录
std::vector<DayRecord> records(numRecords);
inFile.read(reinterpret_cast<char*>(records.data()), fileSize);
// 计算该股票日涨幅
CalculateStockChanges(records, dailyChanges);
}
// 计算每日涨幅中位数
std::vector<DataPoint> medians;
CalculateDailyMedians(dailyChanges, medians);
// 生成.th000文件
char outputPath[MAX_PATH];
sprintf_s(outputPath, "%s\\T0002\\hq_cache\\MarketMedian.th000", g_szTdxPath);
if (GenerateTh000File(medians, outputPath)) {
MessageBoxA(NULL, "日涨幅中位数计算完成!", "DLL提示", MB_OK);
} else {
MessageBoxA(NULL, "计算失败!", "DLL错误", MB_OK | MB_ICONERROR);
}
}
// 注册函数
__declspec(dllexport) int RegFunc(int hWnd) {
// 创建菜单项
HMENU hMenu = CreateMenu();
AppendMenuA(hMenu, MF_STRING, 1001, "计算日涨幅中位数");
// 添加到通达信菜单
SendMessageA((HWND)hWnd, 0x0400, 0x200, (LPARAM)hMenu);
return 0;
}
// 取消注册
__declspec(dllexport) int UnRegFunc(int hWnd) {
// 清理资源
return 0;
}
// 自定义计算函数
__declspec(dllexport) int CalculateMarketMedian(void) {
CalculateMarketMedian();
return 0;
}
// DLL入口点
BOOL APIENTRY DllMain(HMODULE 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;
}
```
## 编译与部署步骤
### 1. 编译DLL
使用Visual Studio创建Win32 DLL项目,添加上述代码,编译生成`MarketMedian.dll`
### 2. 部署到通达信
1. 将编译好的DLL复制到通达信安装目录的`T0002\dlls`文件夹
2. 在通达信中加载DLL:
- 打开通达信软件
- 按`Ctrl+F`打开公式管理器
- 选择"DLL函数" → "导入DLL"
- 选择`MarketMedian.dll`
### 3. 使用DLL功能
1. 在通达信菜单中找到"计算日涨幅中位数"选项
2. 点击后DLL将开始计算
3. 计算结果保存在`T0002\hq_cache\MarketMedian.th000`
4. 在通达信中可通过公式调用该数据:
```
MEDIAN:="MarketMedian@DAY";
DRAWKLINE(HIGH,OPEN,LOW,CLOSE);
DRAWLINE(MEDIAN);
```
## 性能优化策略
### 1. 增量计算
```cpp
DWORD g_lastCalculatedDate = 0; // 全局变量记录最后计算日期
void CalculateStockChanges(std::vector<DayRecord>& records,
std::map<DWORD, std::vector<float>>& dailyChanges) {
// 只处理新数据
auto it = std::find_if(records.rbegin(), records.rend(),
[](const DayRecord& r) { return r.date <= g_lastCalculatedDate; });
if (it != records.rend()) {
records.erase(records.begin(), (++it).base());
}
// ...其余计算逻辑...
// 更新最后计算日期
if (!records.empty()) {
g_lastCalculatedDate = records.back().date;
}
}
```
### 2. 多线程处理
```cpp
#include <thread>
#include <mutex>
std::mutex g_mutex;
void ProcessFile(const std::string& file,
std::map<DWORD, std::vector<float>>& dailyChanges) {
// 文件处理逻辑...
std::lock_guard<std::mutex> lock(g_mutex);
// 合并结果...
}
void CalculateMarketMedian() {
// ...
std::vector<std::thread> threads;
for (const auto& file : files) {
threads.emplace_back(ProcessFile, file, std::ref(dailyChanges));
}
for (auto& t : threads) {
t.join();
}
// ...
}
```
### 3. 内存映射优化
```cpp
#include <Windows.h>
void ProcessFileMemoryMapped(const std::string& filePath) {
HANDLE hFile = CreateFileA(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
HANDLE hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
LPVOID pData = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
size_t fileSize = GetFileSize(hFile, NULL);
size_t numRecords = fileSize / sizeof(DayRecord);
DayRecord* records = static_cast<DayRecord*>(pData);
// 处理记录...
UnmapViewOfFile(pData);
CloseHandle(hMap);
CloseHandle(hFile);
}
```
## 注意事项
1. **数据完整性**:确保通达信数据目录包含完整的历史数据
2. **权限问题**:以管理员身份运行通达信确保DLL有权限写入文件
3. **内存管理**:处理大量数据时注意内存使用
4. **错误处理**:添加完善的错误日志记录
5. **兼容性**:测试不同版本的通达信软件
6. **安全考虑**:验证输入文件路径防止路径遍历攻击
## 结果应用示例
计算出的日涨幅中位数可用于:
1. 市场情绪指标:`$$ \text{市场情绪} = \frac{\text{当日涨幅中位数}}{\text{历史波动率}} $$`
2. 量化策略信号:
```python
# Python伪代码
if median_change > 1.5 and volume > MA(volume, 20):
buy_signal = True
elif median_change < -1.2 and volume > MA(volume, 20):
sell_signal = True
```
3. 风险监测:连续3日涨幅中位数>2%时触发风险预警