实战案例19:金融数据解析
各位同学,今天我们来聊一个很接地气的话题——金融数据解析。说实话,我早年做嵌入式开发时,总觉得正则表达式就是用来匹配个邮箱、电话号码啥的。直到有一次接了个金融数据清洗的活儿,才真正体会到正则的威力。
金融数据有个特点:格式严格但杂乱。股票代码有固定前缀,交易时间精确到毫秒,价格数字带着各种小数点。用正则去解析,说白了就是给这些数据「定规矩」。
解析股票代码
先看股票代码。A股市场里,沪市以6开头,深市以0或3开头。代码长度通常是6位数字。但有些数据源会带后缀,比如.SH、.SZ。
我习惯这样写匹配规则:
#include <stdio.h>
#include <regex.h>
int parse_stock_code(const char *text) {
regex_t regex;
regmatch_t matches[2];
// 匹配6位数字,可选后缀.SH或.SZ
const char *pattern = "([0-9]{6})(\\.(SH|SZ))?";
if (regcomp(®ex, pattern, REG_EXTENDED) != 0) {
fprintf(stderr, "正则编译失败\n");
return -1;
}
if (regexec(®ex, text, 2, matches, 0) == 0) {
printf("股票代码: ");
for (int i = matches[1].rm_so; i < matches[1].rm_eo; i++) {
putchar(text[i]);
}
printf("\n");
if (matches[2].rm_so != -1) {
printf("交易所后缀: ");
for (int i = matches[2].rm_so; i < matches[2].rm_eo; i++) {
putchar(text[i]);
}
printf("\n");
}
}
regfree(®ex);
return 0;
}
这里有个坑。我曾经在项目中遇到一个数据源,股票代码前面有空格,后面跟着乱七八糟的字符。正则里的^和$锚点一定要慎用。我建议用regexec的默认行为,只匹配子串,别限定开头结尾。
([0-9]{5}|[0-9]{6})。但要注意优先级,用括号包好。
解析交易时间
交易时间格式五花八门。常见的有:2024-01-15 09:30:00、2024/01/15 09:30:00.123,甚至还有15-JAN-2024 09:30:00这种英文格式。
我个人习惯先统一格式,再提取字段。看这个例子:
void parse_trade_time(const char *time_str) {
regex_t regex;
regmatch_t matches[7];
// 匹配日期和时间,支持毫秒
const char *pattern =
"([0-9]{4})[-/]([0-9]{2})[-/]([0-9]{2}) "
"([0-9]{2}):([0-9]{2}):([0-9]{2})(\\.([0-9]{3}))?";
if (regcomp(®ex, pattern, REG_EXTENDED) != 0) return;
if (regexec(®ex, time_str, 7, matches, 0) == 0) {
printf("年: "); print_match(time_str, matches[1]);
printf("月: "); print_match(time_str, matches[2]);
printf("日: "); print_match(time_str, matches[3]);
printf("时: "); print_match(time_str, matches[4]);
printf("分: "); print_match(time_str, matches[5]);
printf("秒: "); print_match(time_str, matches[6]);
if (matches[7].rm_so != -1) {
printf("毫秒: "); print_match(time_str, matches[7]);
}
}
regfree(®ex);
}
嗯,这里要注意毫秒部分是可选的。我用?量词处理。如果数据里没有毫秒,matches[7]的rm_so会是-1,判断一下就行。
2024-01-15T09:30:00。正则里把空格改成[T ]就能兼容。别问我怎么知道的——那次排查花了整整一下午。
解析价格格式
价格格式最头疼。有带千分位逗号的:1,234.56,有带货币符号的:$1,234.56,还有负数的:-1,234.56。
我的思路是:先去掉干扰字符,再提取数字。看代码:
double parse_price(const char *price_str) {
regex_t regex;
regmatch_t matches[3];
// 匹配可选负号、数字、逗号、小数点
const char *pattern = "(-?)([0-9]{1,3}(,[0-9]{3})*)(\\.[0-9]+)?";
if (regcomp(®ex, pattern, REG_EXTENDED) != 0) return 0.0;
if (regexec(®ex, price_str, 3, matches, 0) == 0) {
// 提取纯数字字符串
char num_str[64];
int len = matches[2].rm_eo - matches[2].rm_so;
strncpy(num_str, price_str + matches[2].rm_so, len);
num_str[len] = '\0';
// 去掉逗号
char *p = num_str;
while (*p) {
if (*p == ',') memmove(p, p+1, strlen(p));
p++;
}
double value = atof(num_str);
if (matches[1].rm_so != -1) value = -value;
// 处理小数部分
if (matches[3].rm_so != -1) {
char frac[16];
int flen = matches[3].rm_eo - matches[3].rm_so;
strncpy(frac, price_str + matches[3].rm_so, flen);
frac[flen] = '\0';
value += atof(frac);
}
regfree(®ex);
return value;
}
regfree(®ex);
return 0.0;
}
说白了,正则只负责定位,真正的数值转换还得靠atof。我试过用正则直接提取纯数字,但遇到1,234.56这种格式,正则匹配出来的结果带逗号,还得手动清理。
计算统计数据
解析完数据,下一步就是算统计量。比如给定一组价格,算均值、方差、最大回撤。这部分其实跟正则关系不大,但作为完整案例,我简单说一下。
假设我们已经用正则解析出一组价格,存在数组里:
typedef struct {
double prices[1024];
int count;
} PriceData;
void calc_statistics(PriceData *data) {
if (data->count == 0) return;
double sum = 0.0, sum_sq = 0.0;
double max_price = data->prices[0];
double min_price = data->prices[0];
for (int i = 0; i < data->count; i++) {
double p = data->prices[i];
sum += p;
sum_sq += p * p;
if (p > max_price) max_price = p;
if (p < min_price) min_price = p;
}
double mean = sum / data->count;
double variance = (sum_sq / data->count) - (mean * mean);
double stddev = sqrt(variance);
printf("样本数: %d\n", data->count);
printf("均值: %.2f\n", mean);
printf("标准差: %.2f\n", stddev);
printf("最高价: %.2f\n", max_price);
printf("最低价: %.2f\n", min_price);
printf("价差: %.2f\n", max_price - min_price);
}
你想想看,如果没有正则,光是清洗这些数据就得写一堆strstr、strtok,代码又臭又长。正则一把梭,清爽多了。
知识体系总览
我把本章的核心逻辑画了张图,方便你理解:
从图上能看出来,整个流程就是:原始数据 → 正则解析(三路并行) → 汇总统计。我在实际项目中,还会加一层数据校验,比如检查股票代码是否在已知列表里,时间是否在交易时段内。这些校验逻辑用正则也能做一部分,但更复杂的就得靠业务代码了。
好了,这一章的内容就到这里。代码示例我都跑过,你直接复制到Linux或Mac的终端里,用gcc编译就能用。Windows的话,记得把regex.h换成regex库,或者用MSYS2环境。