如何用boost.python将pandas多层索引列DataFrame导入C++完成回测代码迁移
基于Boost.Python实现pandas多层索引DataFrame C++侧读取的落地方案
本方案完全保留原有Python代码执行逻辑,改动量极小,无需复杂的C++侧DataFrame实现即可快速落地。
核心思路
不强行在C侧实现多层索引CSV解析、DataFrame操作等复杂逻辑,采用「Python负责数据加载+行筛选,C负责核心回测计算」的混合架构,仅通过Boost.Python做极简的数据透传即可。
具体实现步骤
- 原有Python侧数据读取代码完全不动:保留
load_data读取多层索引DataFrame的逻辑,CSV解析、索引筛选、行切片全部用成熟的pandas实现,避免踩多层表头解析的坑 - C侧策略类、Broker类的接口和原有Python类完全对齐:方法名、入参、返回值结构和原Python代码完全一致,仅把计算逻辑用C重写
- 多层索引行数据极简透传:Python侧把需要传给C的
prev_fs_row、fs_row、indi_row直接调用to_dict()方法转为嵌套字典,结构自动适配为{股票代码: {字段名: 字段值}},Boost.Python可直接解析为C的std::map<std::string, std::map<std::string, 对应类型>>,完全不需要处理pandas内部数据结构 - 结果回传逻辑对齐:C++侧计算完成后把
buy_signal、sell_signal、cash、position按原有结构返回Python,原有的update_data、performance统计逻辑完全不用修改
核心代码示例
C++侧实现与Boost.Python绑定代码
#include <boost/python.hpp> #include <map> #include <vector> #include <string> #include <algorithm> using namespace boost::python; // 类型定义和Python侧数据结构完全对齐 using FsRow = std::map<std::string, std::map<std::string, std::string>>; using IndiRow = std::map<std::string, std::map<std::string, double>>; class No_Strategy { public: std::vector<std::string> codes; int unit; double cash; std::vector<int> buy_signal; std::vector<int> sell_signal; std::vector<int> position; int pass_st; FsRow prev_fs; FsRow fs; IndiRow indi; No_Strategy(list code_list, int unit_, double cash_, list position_list) { // 把Python入参转为C++类型 for (int i=0; i<len(code_list); i++) codes.push_back(extract<std::string>(code_list[i])); unit = unit_; cash = cash_; buy_signal.resize(codes.size(), 0); sell_signal.resize(codes.size(), 0); position.resize(codes.size(), 0); for (int i=0; i<len(position_list); i++) position[i] = extract<int>(position_list[i]); pass_st = 0; } void set_data(dict prev_fs_dict, dict fs_dict, dict indi_dict) { // 直接把Python传过来的字典转成C++ map,一行代码完成多层索引数据解析 prev_fs = extract<FsRow>(prev_fs_dict); fs = extract<FsRow>(fs_dict); indi = extract<IndiRow>(indi_dict); } std::pair<int, int> _strat(std::map<std::string, std::string>& prev_fs, std::map<std::string, std::string>& curr_fs, std::map<std::string, double>& curr_indi) { // 逻辑完全抄原Python代码,一字不差对齐 long long prev_rev = 0, curr_rev = 0, prev_ni = 0, curr_ni = 0, curr_asset = 0, noncurr_asset = 0; try { std::string prev_str = prev_fs["매출액"]; prev_str.erase(std::remove(prev_str.begin(), prev_str.end(), ','), prev_str.end()); prev_rev = std::stoll(prev_str); std::string curr_str = curr_fs["매출액"]; curr_str.erase(std::remove(curr_str.begin(), curr_str.end(), ','), curr_str.end()); curr_rev = std::stoll(curr_str); } catch(...) { pass_st++; return {0, 0}; } try { std::string prev_str = prev_fs["당기순이익"]; prev_str.erase(std::remove(prev_str.begin(), prev_str.end(), ','), prev_str.end()); prev_ni = std::stoll(prev_str); std::string curr_str = curr_fs["당기순이익"]; curr_str.erase(std::remove(curr_str.begin(), curr_str.end(), ','), curr_str.end()); curr_ni = std::stoll(curr_str); } catch(...) { pass_st++; return {0, 0}; } try { std::string curr_str = curr_fs["유동자산"]; curr_str.erase(std::remove(curr_str.begin(), curr_str.end(), ','), curr_str.end()); curr_asset = std::stoll(curr_str); std::string noncurr_str = curr_fs["비유동자산"]; noncurr_str.erase(std::remove(noncurr_str.begin(), noncurr_str.end(), ','), noncurr_str.end()); noncurr_asset = std::stoll(noncurr_str); } catch(...) { pass_st++; return {0, 0}; } // 买卖策略完全和原Python对齐,修改时直接改这里即可 if (curr_indi["golden_cross"] == 1 || curr_indi["rsi_k"] < 0.65) { return {1, 0}; } if (curr_indi["dead_cross"] == 1) { return {0, 1}; } return {0, 0}; } void run() { // 逻辑完全抄原Python代码 for (int i=0; i<codes.size(); i++) { std::string code = codes[i]; buy_signal[i] = 0; sell_signal[i] = 0; int valid = 0; if (prev_fs.count(code) == 0 || fs.count(code) == 0 || indi.count(code) == 0) { pass_st++; continue; } auto& prev_fs_cell = prev_fs[code]; auto& curr_fs_cell = fs[code]; auto& curr_indi_cell = indi[code]; if (prev_fs_cell.empty() || curr_fs_cell.empty()) { pass_st++; continue; } auto [buy, sell] = _strat(prev_fs_cell, curr_fs_cell, curr_indi_cell); double price = curr_indi_cell["close"]; if (buy) { if (cash >= unit * price) { buy_signal[i] = unit; position[i] += unit; cash -= unit * price; } } else if (sell) { if (position[i] > 0) { int sell_num = position[i] - position[i]/2; sell_signal[i] = sell_num; position[i] = position[i]/2; cash += price * sell_num; } } } } // 把C++结果转成Python可读取的结构 list get_buy_signal() { list l; for (auto v: buy_signal) l.append(v); return l; } list get_sell_signal() { list l; for (auto v: sell_signal) l.append(v); return l; } list get_position() { list l; for (auto v: position) l.append(v); return l; } double get_cash() { return cash; } int get_pass_st() { return pass_st; } }; // Boost.Python绑定导出 BOOST_PYTHON_MODULE(cpp_backtest) { class_<No_Strategy>("No_Strategy", init<list, int, double, list>()) .def("set_data", &No_Strategy::set_data) .def("run", &No_Strategy::run) .def("get_buy_signal", &No_Strategy::get_buy_signal) .def("get_sell_signal", &No_Strategy::get_sell_signal) .def("get_position", &No_Strategy::get_position) .def("get_cash", &No_Strategy::get_cash) .def("get_pass_st", &No_Strategy::get_pass_st); }
Python侧对接修改(仅需改3行代码,其余完全不动)
# 原有import部分替换No_Strategy导入即可 import pandas as pd import numpy as np from utils import load_data, load_list_csv, to_int from cpp_backtest import No_Strategy # 替换原来的Python类导入 # ---------------------- 以下原有代码完全不用修改 ---------------------- class Broker(): # 原有Broker代码不变 def run(self): for date in self.time_index: if date.year == 2021: break else: prev_fs_row = self.fs.loc[date.year-1] fs_row = self.fs.loc[date.year] indi_row = self.indi.loc[date] # 仅这里加3行代码转字典传给C++ strategy = self.strategy(self.codes, self.unit, self.cash, self.position) strategy.set_data(prev_fs_row.to_dict(), fs_row.to_dict(), indi_row.to_dict()) strategy.run() # 回传结果到Python,和原有逻辑对齐 self.cash = strategy.get_cash() self.position = list(strategy.get_position()) self.buy_signal.loc[date] = list(strategy.get_buy_signal()) self.sell_signal.loc[date] = list(strategy.get_sell_signal()) self.pass_st += strategy.get_pass_st() # 原有主逻辑代码完全不变
方案优势
- 落地成本极低:不需要理解pandas多层索引的底层存储结构,不需要自己写CSV多层表头解析代码,仅需重写策略逻辑+简单绑定
- 结果100%对齐:所有计算逻辑完全照搬原Python代码,不会出现逻辑偏差
- 性能提升显著:回测核心循环全部在C++侧运行,Python仅负责数据加载和最终统计,速度可提升10~100倍
内容的提问来源于stack exchange,提问作者hyemin ju
相关产品推荐
相关产品推荐

