实战案例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(&regex, pattern, REG_EXTENDED) != 0) {
        fprintf(stderr, "正则编译失败\n");
        return -1;
    }
    
    if (regexec(&regex, 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(&regex);
    return 0;
}

这里有个坑。我曾经在项目中遇到一个数据源,股票代码前面有空格,后面跟着乱七八糟的字符。正则里的^$锚点一定要慎用。我建议用regexec的默认行为,只匹配子串,别限定开头结尾。

小技巧:如果数据里混着港股代码(5位数字),可以加个分支条件:([0-9]{5}|[0-9]{6})。但要注意优先级,用括号包好。

解析交易时间

交易时间格式五花八门。常见的有:2024-01-15 09:30:002024/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(&regex, pattern, REG_EXTENDED) != 0) return;
    
    if (regexec(&regex, 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(&regex);
}

嗯,这里要注意毫秒部分是可选的。我用?量词处理。如果数据里没有毫秒,matches[7]rm_so会是-1,判断一下就行。

避坑指南:我曾经遇到过一个数据源,时间字段里混着T分隔符,比如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(&regex, pattern, REG_EXTENDED) != 0) return 0.0;
    
    if (regexec(&regex, 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(&regex);
        return value;
    }
    
    regfree(&regex);
    return 0.0;
}

说白了,正则只负责定位,真正的数值转换还得靠atof。我试过用正则直接提取纯数字,但遇到1,234.56这种格式,正则匹配出来的结果带逗号,还得手动清理。

核心思路:正则做「粗提取」,C标准库做「精转换」。各司其职,代码才干净。

计算统计数据

解析完数据,下一步就是算统计量。比如给定一组价格,算均值、方差、最大回撤。这部分其实跟正则关系不大,但作为完整案例,我简单说一下。

假设我们已经用正则解析出一组价格,存在数组里:

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);
}

你想想看,如果没有正则,光是清洗这些数据就得写一堆strstrstrtok,代码又臭又长。正则一把梭,清爽多了。

知识体系总览

我把本章的核心逻辑画了张图,方便你理解:

金融数据解析核心流程 原始金融数据 正则表达式解析 股票代码 | 交易时间 | 价格格式 股票代码提取 交易时间解析 价格格式清洗 统计数据输出

从图上能看出来,整个流程就是:原始数据 → 正则解析(三路并行) → 汇总统计。我在实际项目中,还会加一层数据校验,比如检查股票代码是否在已知列表里,时间是否在交易时段内。这些校验逻辑用正则也能做一部分,但更复杂的就得靠业务代码了。

个人经验:正则不是万能的。遇到嵌套结构(比如JSON里的金融数据),还是得用专门的解析库。正则擅长的是「扁平结构」的文本提取。记住这个原则,能少走很多弯路。

好了,这一章的内容就到这里。代码示例我都跑过,你直接复制到Linux或Mac的终端里,用gcc编译就能用。Windows的话,记得把regex.h换成regex库,或者用MSYS2环境。

公众号:蓝海资料掘金营,微信deep3321