├── .gitignore ├── CMakeLists.txt ├── README.md ├── api ├── dll │ ├── thostmduserapi_se.dll │ └── thosttraderapi_se.dll ├── include │ ├── ThostFtdcMdApi.h │ ├── ThostFtdcTraderApi.h │ ├── ThostFtdcUserApiDataType.h │ ├── ThostFtdcUserApiStruct.h │ ├── error.dtd │ └── error.xml ├── lib │ ├── thostmduserapi_se.lib │ └── thosttraderapi_se.lib └── so │ ├── libthostmduserapi_se.so │ └── libthosttraderapi_se.so ├── broker ├── simnow.txt └── simnow_24.txt └── src ├── CMakeLists.txt ├── MarketData.cpp ├── MarketData.h ├── Trader.cpp ├── Trader.h ├── common.h ├── main.cpp ├── record.cpp ├── record.h └── ringbuffer.h /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | CMakeCache.txt 3 | CMakeFiles 4 | CMakeScripts 5 | Testing 6 | Makefile 7 | cmake_install.cmake 8 | install_manifest.txt 9 | compile_commands.json 10 | CTestTestfile.cmake 11 | _deps 12 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.18) 2 | 3 | project ("mdRecorder") 4 | 5 | set(CMAKE_CXX_STANDARD 20) 6 | 7 | #set(CMAKE_BUILD_TYPE "Debug”) 8 | set(CMAKE_BUILD_TYPE "Release") 9 | 10 | add_subdirectory ("src") 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MarketDataRec 2 | 用C++开发的跨平台的基于上期技术ctp api的交易程序,可以实现在云服务器连续自动运行,无需手动干预,微信监控实时通知,接触不到电脑也可随时了解交易行情动态,数据文件自动压缩上传百度网盘,熟悉无锁ringbuffer的使用,尽可能降低延迟。 3 | 4 | # 交流请加微信:X_Trader_Lab 5 | 6 | # B站视频:https://space.bilibili.com/1759041860 7 | 8 | # 视频目录: 9 | 10 | 00.准备 11 | 12 | 01.CTP_API的基本架构 13 | 14 | 02.编写CMakeLists.txt 15 | 16 | 03.行情接口的初始化 17 | 18 | 04.行情订阅和获取实时五档行情 19 | 20 | 05.交易接口初始化及合约查询 21 | 22 | 06.看穿式监管评测 23 | 24 | 07.无锁ringbuffer的使用 25 | 26 | 08.record类的实现 27 | 28 | 09.把实时行情写入csv 29 | 30 | 10.程序在云服务器Linux下编译运行 31 | 32 | 11.微信实时监控通知 33 | 34 | 12.bypy实现自动上传百度云 35 | 36 | 13.overnight函数的实现 37 | 38 | 14.crontab定时任务 39 | 40 | 41 | # 交流请加微信:X_Trader_Lab 42 | 43 | # 免费课程:https://edu.csdn.net/course/detail/38234 44 | # 进阶课程:https://www.cctalk.com/m/group/91151359 45 | -------------------------------------------------------------------------------- /api/dll/thostmduserapi_se.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hftlab/MarketDataRec/e22b7db77de9b55e6bd918135124c60f0604584c/api/dll/thostmduserapi_se.dll -------------------------------------------------------------------------------- /api/dll/thosttraderapi_se.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hftlab/MarketDataRec/e22b7db77de9b55e6bd918135124c60f0604584c/api/dll/thosttraderapi_se.dll -------------------------------------------------------------------------------- /api/include/ThostFtdcMdApi.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hftlab/MarketDataRec/e22b7db77de9b55e6bd918135124c60f0604584c/api/include/ThostFtdcMdApi.h -------------------------------------------------------------------------------- /api/include/ThostFtdcTraderApi.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hftlab/MarketDataRec/e22b7db77de9b55e6bd918135124c60f0604584c/api/include/ThostFtdcTraderApi.h -------------------------------------------------------------------------------- /api/include/ThostFtdcUserApiStruct.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hftlab/MarketDataRec/e22b7db77de9b55e6bd918135124c60f0604584c/api/include/ThostFtdcUserApiStruct.h -------------------------------------------------------------------------------- /api/include/error.dtd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /api/include/error.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hftlab/MarketDataRec/e22b7db77de9b55e6bd918135124c60f0604584c/api/include/error.xml -------------------------------------------------------------------------------- /api/lib/thostmduserapi_se.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hftlab/MarketDataRec/e22b7db77de9b55e6bd918135124c60f0604584c/api/lib/thostmduserapi_se.lib -------------------------------------------------------------------------------- /api/lib/thosttraderapi_se.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hftlab/MarketDataRec/e22b7db77de9b55e6bd918135124c60f0604584c/api/lib/thosttraderapi_se.lib -------------------------------------------------------------------------------- /api/so/libthostmduserapi_se.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hftlab/MarketDataRec/e22b7db77de9b55e6bd918135124c60f0604584c/api/so/libthostmduserapi_se.so -------------------------------------------------------------------------------- /api/so/libthosttraderapi_se.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hftlab/MarketDataRec/e22b7db77de9b55e6bd918135124c60f0604584c/api/so/libthosttraderapi_se.so -------------------------------------------------------------------------------- /broker/simnow.txt: -------------------------------------------------------------------------------- 1 | TradeFront = tcp://180.168.146.187:10201 2 | MarketFront = tcp://180.168.146.187:10211 3 | BrokerID = 9999 4 | UserID = ******** 5 | Password = ******** 6 | AuthCode = 0000000000000000 7 | AppID = simnow_client_test -------------------------------------------------------------------------------- /broker/simnow_24.txt: -------------------------------------------------------------------------------- 1 | TradeFront = tcp://180.168.146.187:10130 2 | MarketFront = tcp://180.168.146.187:10131 3 | BrokerID = 9999 4 | UserID = ***** 5 | Password = ***** 6 | AuthCode = 0000000000000000 7 | AppID = simnow_client_test -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if(WIN32) 2 | link_directories(${CMAKE_SOURCE_DIR}/api/lib) 3 | endif() 4 | 5 | if(UNIX) 6 | link_directories(${CMAKE_SOURCE_DIR}/api/so) 7 | endif() 8 | 9 | add_executable(${PROJECT_NAME} main.cpp MarketData.cpp Trader.cpp record.cpp) 10 | set_target_properties(${PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_NAME}) 11 | 12 | target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/api/include) 13 | 14 | file(COPY ${CMAKE_SOURCE_DIR}/broker/ DESTINATION ${CMAKE_BINARY_DIR}/${PROJECT_NAME}/broker) 15 | 16 | if(WIN32) 17 | target_link_libraries(${PROJECT_NAME} PRIVATE thosttraderapi_se thostmduserapi_se) 18 | file(COPY ${CMAKE_SOURCE_DIR}/api/dll/ DESTINATION ${CMAKE_BINARY_DIR}/${PROJECT_NAME}) 19 | endif() 20 | 21 | if(UNIX) 22 | target_link_libraries(${PROJECT_NAME} PRIVATE thostmduserapi_se thosttraderapi_se) 23 | file(COPY ${CMAKE_SOURCE_DIR}/api/so/ DESTINATION ${CMAKE_BINARY_DIR}/${PROJECT_NAME}) 24 | endif() 25 | -------------------------------------------------------------------------------- /src/MarketData.cpp: -------------------------------------------------------------------------------- 1 | #include "MarketData.h" 2 | 3 | 4 | MarketData::MarketData(std::vector& inst) : mdapi(nullptr), inst_vec(inst) 5 | { 6 | createFolder(pMdFlowPath); 7 | //loadBrokerInfo("./broker/simnow.txt", bi); 8 | //loadBrokerInfo("./broker/simnow_24.txt", bi); 9 | //loadBrokerInfo("./broker/mk.txt", bi); 10 | loadBrokerInfo("./broker/mk_5.txt", bi); 11 | //loadBrokerInfo("./broker/5.txt", bi); 12 | 13 | mdapi = CThostFtdcMdApi::CreateFtdcMdApi(pMdFlowPath, true, true); 14 | mdapi->RegisterSpi(this); 15 | mdapi->RegisterFront(bi.MarketFront); 16 | mdapi->Init(); 17 | } 18 | 19 | MarketData::~MarketData() {} 20 | 21 | 22 | ///当客户端与交易后台建立起通信连接时(还未登录前),该方法被调用。 23 | void MarketData::OnFrontConnected() 24 | { 25 | printf("Market Front Connected\n"); 26 | send2WeCom("Market Front Connected"); 27 | 28 | CThostFtdcReqUserLoginField t{}; 29 | strcpy(t.BrokerID, bi.BrokerID); 30 | strcpy(t.UserID, bi.UserID); 31 | strcpy(t.Password, bi.Password); 32 | 33 | int rtn = mdapi->ReqUserLogin(&t, ++nReqID); 34 | } 35 | 36 | ///当客户端与交易后台通信连接断开时,该方法被调用。当发生这个情况后,API会自动重新连接,客户端可不做处理。 37 | ///@param nReason 错误原因 38 | /// 0x1001 网络读失败 39 | /// 0x1002 网络写失败 40 | /// 0x2001 接收心跳超时 41 | /// 0x2002 发送心跳失败 42 | /// 0x2003 收到错误报文 43 | void MarketData::OnFrontDisconnected(int nReason) 44 | { 45 | printf("Market Front Disconnected\n"); 46 | send2WeCom("Market Front Disconnected"); 47 | printf("nReason = %d Reason = %s\n", nReason, nReason2str(nReason)); 48 | 49 | overnight(mdapi->GetTradingDay()); 50 | } 51 | 52 | ///登录请求响应 53 | void MarketData::OnRspUserLogin(CThostFtdcRspUserLoginField* pRspUserLogin, CThostFtdcRspInfoField* pRspInfo, int nRequestID, bool bIsLast) 54 | { 55 | if (pRspInfo != nullptr && pRspInfo->ErrorID != 0) 56 | { 57 | printf("ErrorID = %d ErrorMsg = %s\n", pRspInfo->ErrorID, pRspInfo->ErrorMsg); 58 | return; 59 | } 60 | 61 | for (auto iter : inst_vec) 62 | { 63 | char* ppInsts[] = { (char*)iter.c_str() }; 64 | mdapi->SubscribeMarketData(ppInsts, 1); 65 | } 66 | } 67 | 68 | ///登出请求响应 69 | void MarketData::OnRspUserLogout(CThostFtdcUserLogoutField* pUserLogout, CThostFtdcRspInfoField* pRspInfo, int nRequestID, bool bIsLast) 70 | {} 71 | 72 | ///订阅行情应答 73 | void MarketData::OnRspSubMarketData(CThostFtdcSpecificInstrumentField* pSpecificInstrument, CThostFtdcRspInfoField* pRspInfo, int nRequestID, bool bIsLast) 74 | { 75 | if (pRspInfo != nullptr && pRspInfo->ErrorID != 0) 76 | { 77 | printf("ErrorID = %d ErrorMsg = %s\n", pRspInfo->ErrorID, pRspInfo->ErrorMsg); 78 | return; 79 | } 80 | 81 | static auto day = mdapi->GetTradingDay(); 82 | 83 | if (pSpecificInstrument->InstrumentID == nullptr) { return; } 84 | 85 | #ifdef _WIN32 86 | if (!std::filesystem::exists(day)) 87 | #else 88 | if (!access(day, F_OK) == 0) 89 | #endif // _WIN32 90 | { 91 | createFolder(day); 92 | } 93 | 94 | char fileName[64] = { '\0' }; 95 | sprintf(fileName, "./%s/%s_%s.csv", day, pSpecificInstrument->InstrumentID, day); 96 | 97 | #ifdef _WIN32 98 | if (std::filesystem::exists(fileName)) { return; } 99 | #else 100 | if (access(fileName, F_OK) == 0) { return; } 101 | #endif // _WIN32 102 | 103 | std::ofstream outFile; 104 | outFile.open(fileName, std::ios::out); // 新开文件 105 | outFile << " InstrumentID" << ","///交易所代码 106 | << "ExchangeID " << ","///合约代码 107 | << "TradingDay" << ","///交易日 108 | << "UpdateTime" << ","///最后修改时间 109 | << "UpdateMillisec" << ","///最后修改毫秒 110 | << "PreSettlementPrice" << ","///上次结算价 111 | << "PreClosePrice" << ","///昨收盘 112 | << "PreOpenInterest" << ","///昨持仓量 113 | << "OpenPrice" << ","///今开盘 114 | << "UpperLimitPrice" << ","///涨停板价 115 | << "LowerLimitPrice" << ","///跌停板价 116 | << "HighestPrice" << ","///最高价 117 | << "LowestPrice" << ","///最低价 118 | << "LastPrice" << ","///最新价 119 | << "Volume" << ","///数量 120 | << "Turnover" << ","///成交金额 121 | << "OpenInterest" << ","///持仓量 122 | << "BidPrice1" << ","///申买价一 123 | << "BidVolume1" << ","///申买量一 124 | << "AskPrice1" << ","///申卖价一 125 | << "AskVolume1" << ","///申卖量一 126 | << "BidPrice2" << ","///申买价二 127 | << "BidVolume2" << ","///申买量二 128 | << "AskPrice2" << ","///申卖价二 129 | << "AskVolume2" << ","///申卖量二 130 | << "BidPrice3" << ","///申买价三 131 | << "BidVolume3" << ","///申买量三 132 | << "AskPrice3" << ","///申卖价三 133 | << "AskVolume3" << ","///申卖量三 134 | << "BidPrice4" << ","///申买价四 135 | << "BidVolume4" << ","///申买量四 136 | << "AskPrice4" << ","///申卖价四 137 | << "AskVolume4" << ","///申卖量四 138 | << "BidPrice5" << ","///申买价五 139 | << "BidVolume5" << ","///申买量五 140 | << "AskPrice5" << ","///申卖价五 141 | << "AskVolume5"///申卖量五 142 | << std::endl; 143 | outFile.close(); 144 | } 145 | 146 | ///深度行情通知 147 | void MarketData::OnRtnDepthMarketData(CThostFtdcDepthMarketDataField* pDepthMarketData) 148 | { 149 | if (pDepthMarketData != nullptr) 150 | { 151 | md.insert(*pDepthMarketData); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/MarketData.h: -------------------------------------------------------------------------------- 1 | #ifndef MARKET_DATA_H 2 | #define MARKET_DATA_H 3 | 4 | #include "ThostFtdcMdApi.h" 5 | #include "common.h" 6 | #include "ringbuffer.h" 7 | 8 | 9 | class MarketData : public CThostFtdcMdSpi 10 | { 11 | public: 12 | MarketData(std::vector& inst); 13 | ~MarketData(); 14 | 15 | public: 16 | ///当客户端与交易后台建立起通信连接时(还未登录前),该方法被调用。 17 | virtual void OnFrontConnected(); 18 | 19 | ///当客户端与交易后台通信连接断开时,该方法被调用。当发生这个情况后,API会自动重新连接,客户端可不做处理。 20 | ///@param nReason 错误原因 21 | /// 0x1001 网络读失败 22 | /// 0x1002 网络写失败 23 | /// 0x2001 接收心跳超时 24 | /// 0x2002 发送心跳失败 25 | /// 0x2003 收到错误报文 26 | virtual void OnFrontDisconnected(int nReason); 27 | 28 | ///登录请求响应 29 | virtual void OnRspUserLogin(CThostFtdcRspUserLoginField* pRspUserLogin, CThostFtdcRspInfoField* pRspInfo, int nRequestID, bool bIsLast); 30 | 31 | ///登出请求响应 32 | virtual void OnRspUserLogout(CThostFtdcUserLogoutField* pUserLogout, CThostFtdcRspInfoField* pRspInfo, int nRequestID, bool bIsLast); 33 | 34 | ///订阅行情应答 35 | virtual void OnRspSubMarketData(CThostFtdcSpecificInstrumentField* pSpecificInstrument, CThostFtdcRspInfoField* pRspInfo, int nRequestID, bool bIsLast); 36 | 37 | ///深度行情通知 38 | virtual void OnRtnDepthMarketData(CThostFtdcDepthMarketDataField* pDepthMarketData); 39 | 40 | public: 41 | ringbuffer md; 42 | 43 | public: 44 | CThostFtdcMdApi* mdapi; 45 | BrokerInfo bi; 46 | int nReqID = 0; 47 | std::vector inst_vec; 48 | }; 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /src/Trader.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hftlab/MarketDataRec/e22b7db77de9b55e6bd918135124c60f0604584c/src/Trader.cpp -------------------------------------------------------------------------------- /src/Trader.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hftlab/MarketDataRec/e22b7db77de9b55e6bd918135124c60f0604584c/src/Trader.h -------------------------------------------------------------------------------- /src/common.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMON_H 2 | #define COMMON_H 3 | 4 | #include 5 | #include 6 | 7 | #ifdef _WIN32 8 | #include 9 | #include 10 | #else 11 | #include 12 | #endif // _WIN32 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | const char pMdFlowPath[] = "./mdflow/"; 23 | const char pTradeFlowPath[] = "./tdflow/"; 24 | 25 | struct BrokerInfo 26 | { 27 | char TradeFront[32]; 28 | char MarketFront[32]; 29 | char BrokerID[16]; 30 | char UserID[16]; 31 | char Password[16]; 32 | char AuthCode[32]; 33 | char AppID[32]; 34 | }; 35 | 36 | inline void loadBrokerInfo(const char* filename, BrokerInfo& bi) 37 | { 38 | FILE* fp = fopen(filename, "r"); 39 | if (!fp) { perror("File opening failed"); return; } 40 | 41 | char buf[64] = { '\0' }; 42 | char key[16] = { '\0' }; 43 | char value[32] = { '\0' }; 44 | std::map m; 45 | 46 | while (fgets(buf, sizeof(buf), fp) != NULL) 47 | { 48 | if (char* r = strchr(buf, '\n')) { r[0] = '\0'; } 49 | sscanf(buf, "%s = %s", key, value); 50 | m[key] = value; 51 | } 52 | strcpy(bi.TradeFront, m["TradeFront"].c_str()); 53 | strcpy(bi.MarketFront, m["MarketFront"].c_str()); 54 | strcpy(bi.BrokerID, m["BrokerID"].c_str()); 55 | strcpy(bi.UserID, m["UserID"].c_str()); 56 | strcpy(bi.Password, m["Password"].c_str()); 57 | strcpy(bi.AuthCode, m["AuthCode"].c_str()); 58 | strcpy(bi.AppID, m["AppID"].c_str()); 59 | } 60 | 61 | inline void createFolder(const char *path) 62 | { 63 | #ifdef _WIN32 64 | std::filesystem::path _path(path); 65 | if (std::filesystem::exists(_path)) 66 | { 67 | std::filesystem::remove_all(_path); 68 | } 69 | std::filesystem::create_directories(_path); 70 | #else 71 | if (access(path, F_OK) == 0) 72 | { 73 | char rm[32]; sprintf(rm, "rm %s -rf", path); 74 | system(rm); 75 | } 76 | char mkdir[32]; sprintf(mkdir, "mkdir %s", path); 77 | system(mkdir); 78 | #endif // _WIN32 79 | } 80 | 81 | inline const char* reqRtnReason(int rtn) 82 | { 83 | switch (rtn) 84 | { 85 | case 0:///发送成功 86 | return "Sent successfully"; break; 87 | case -1:///因网络原因发送失败 88 | return "Failed to send"; break; 89 | case -2:///未处理请求队列总数量超限 90 | return "Unprocessed requests exceeded the allowed volume"; break; 91 | case -3:///每秒发送请求数量超限 92 | return "The number of requests sent per second exceeds the allowed volume"; break; 93 | default:///未知 94 | return "Unknown"; 95 | } 96 | } 97 | 98 | inline const char* nReason2str(int nReason) 99 | { 100 | switch (nReason) 101 | { 102 | case 0x1001: 103 | return "网络读失败"; 104 | case 0x1002: 105 | return "网络写失败"; 106 | case 0x2001: 107 | return "接收心跳超时"; 108 | case 0x2002: 109 | return "发送心跳失败"; 110 | case 0x2003: 111 | return "收到错误报文"; 112 | default: 113 | return "Unknown"; 114 | } 115 | } 116 | 117 | inline void send2WeCom(const char* text) 118 | { 119 | char command[512]; 120 | #ifdef _WIN32 121 | sprintf(command, "curl \"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=6ef63fbe-ff08-4099-9a2c-4433c464a035\" -H \"Content-Type: application/json\" -d \"{\\\"msgtype\\\": \\\"text\\\",\\\"text\\\": {\\\"content\\\": \\\"Win %s\\\"}}\"", text); 122 | #else 123 | sprintf(command, "curl \'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=6ef63fbe-ff08-4099-9a2c-4433c464a035\' -H \'Content-Type: application/json\' -d \'{\"msgtype\": \"text\",\"text\": {\"content\": \"%s\"}}\'", text); 124 | #endif 125 | system(command); 126 | } 127 | 128 | inline void overnight(const char* day) 129 | { 130 | long long sleeptime = 0; 131 | std::time_t begin = std::time(nullptr); 132 | 133 | auto t = std::localtime(&begin); 134 | std::tm end = *t; 135 | end.tm_sec = 0; end.tm_min = 58; end.tm_hour = 8; 136 | 137 | if (t->tm_hour < 8) 138 | { 139 | if (t->tm_wday == 6) 140 | { 141 | sleeptime = mktime(&end) + 2 * 24 * 3600 - begin; 142 | } 143 | else 144 | { 145 | sleeptime = mktime(&end) - begin; 146 | } 147 | send2WeCom("sleep"); 148 | std::this_thread::sleep_for(std::chrono::seconds(sleeptime)); 149 | } 150 | else if (t->tm_hour < 20 && t->tm_hour > 14) 151 | { 152 | #ifndef _WIN32 153 | char command[128] = { '\0' }; 154 | sprintf(command, "tar -czvf %s.tar.gz %s && bypy upload %s.tar.gz && rm %s -rf", day, day, day, day); 155 | system(command); 156 | #endif // _WIN32 157 | send2WeCom("restart"); 158 | system("shutdown -r"); 159 | } 160 | } 161 | 162 | #endif 163 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #pragma warning(disable:4996) 2 | #include "record.h" 3 | 4 | 5 | #ifdef _WIN32 6 | #pragma comment(lib, "thostmduserapi_se.lib") 7 | #pragma comment(lib, "thosttraderapi_se.lib") 8 | #endif // _WIN32 9 | 10 | int main() 11 | { 12 | record r; 13 | return 0; 14 | } -------------------------------------------------------------------------------- /src/record.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hftlab/MarketDataRec/e22b7db77de9b55e6bd918135124c60f0604584c/src/record.cpp -------------------------------------------------------------------------------- /src/record.h: -------------------------------------------------------------------------------- 1 | #ifndef RECORD_H 2 | #define RECORD_H 3 | 4 | #include "Trader.h" 5 | #include "MarketData.h" 6 | 7 | 8 | class record 9 | { 10 | public: 11 | record(); 12 | ~record(); 13 | 14 | public: 15 | void start(); 16 | 17 | private: 18 | Trader* td_ptr; 19 | MarketData* md_ptr; 20 | }; 21 | 22 | #endif -------------------------------------------------------------------------------- /src/ringbuffer.h: -------------------------------------------------------------------------------- 1 | #ifndef RING_BUFFER_H 2 | #define RING_BUFFER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | //brief Lock free, with no wasted slots ringbuffer implementation 10 | //tparam T Type of buffered elements 11 | //tparam buffer_size Size of the buffer. Must be a power of 2. 12 | //tparam cacheline_size Size of the cache line, to insert appropriate padding in between indexes and buffer 13 | //tparam index_t Type of array indexing type. Serves also as placeholder for future implementations. 14 | template 15 | class ringbuffer 16 | { 17 | public: 18 | //brief Default constructor, will initialize head and tail indexes 19 | ringbuffer() : head(0), tail(0) {} 20 | 21 | //brief Special case constructor to premature out unnecessary initialization code when object is 22 | //instatiated in .bss section 23 | //warning If object is instantiated on stack, heap or inside noinit section then the contents have to be 24 | //explicitly cleared before use 25 | //param dummy Ignored 26 | ringbuffer(int dummy) { (void)(dummy); } 27 | 28 | //brief Clear buffer from producer side 29 | //warning function may return without performing any action if consumer tries to read data at the same time 30 | void producerClear(void) { consumerClear(); } 31 | // head modification will lead to underflow if cleared during consumer read 32 | // doing this properly with CAS is not possible without modifying the consumer code 33 | 34 | //brief Clear buffer from consumer side 35 | void consumerClear(void) 36 | { 37 | tail.store(head.load(std::memory_order_relaxed), std::memory_order_relaxed); 38 | } 39 | 40 | //brief Check if buffer is empty 41 | //return True if buffer is empty 42 | bool isEmpty(void) const { return readAvailable() == 0; } 43 | 44 | //brief Check if buffer is full 45 | //return True if buffer is full 46 | bool isFull(void) const { return writeAvailable() == 0; } 47 | 48 | //brief Check how many elements can be read from the buffer 49 | //return Number of elements that can be read 50 | index_t readAvailable(void) const 51 | { 52 | return head.load(std::memory_order_acquire) - tail.load(std::memory_order_relaxed); 53 | } 54 | 55 | //brief Check how many elements can be written into the buffer 56 | //return Number of free slots that can be be written 57 | index_t writeAvailable(void) const 58 | { 59 | return buffer_size - (head.load(std::memory_order_relaxed) - tail.load(std::memory_order_acquire)); 60 | } 61 | 62 | //brief Inserts data into internal buffer, without blocking 63 | //param data element to be inserted into internal buffer 64 | //return True if data was inserted 65 | bool insert(T data) 66 | { 67 | index_t tmp_head = head.load(std::memory_order_relaxed); 68 | 69 | if ((tmp_head - tail.load(std::memory_order_acquire)) == buffer_size) 70 | return false; 71 | else 72 | { 73 | data_buff[tmp_head++ & buffer_mask] = data; 74 | std::atomic_signal_fence(std::memory_order_release); 75 | head.store(tmp_head, std::memory_order_release); 76 | } 77 | return true; 78 | } 79 | 80 | //brief Inserts data into internal buffer, without blocking 81 | //param[in] data Pointer to memory location where element, to be inserted into internal buffer, is located 82 | //return True if data was inserted 83 | bool insert(const T* data) 84 | { 85 | index_t tmp_head = head.load(std::memory_order_relaxed); 86 | 87 | if ((tmp_head - tail.load(std::memory_order_acquire)) == buffer_size) 88 | return false; 89 | else 90 | { 91 | data_buff[tmp_head++ & buffer_mask] = *data; 92 | std::atomic_signal_fence(std::memory_order_release); 93 | head.store(tmp_head, std::memory_order_release); 94 | } 95 | return true; 96 | } 97 | 98 | //brief Inserts data returned by callback function, into internal buffer, without blocking 99 | 100 | //This is a special purpose function that can be used to avoid redundant availability checks in case when 101 | //acquiring data have a side effects (like clearing status flags by reading a peripheral data register) 102 | 103 | //param get_data_callback Pointer to callback function that returns element to be inserted into buffer 104 | //return True if data was inserted and callback called 105 | bool insertFromCallbackWhenAvailable(T(*get_data_callback)(void)) 106 | { 107 | index_t tmp_head = head.load(std::memory_order_relaxed); 108 | 109 | if ((tmp_head - tail.load(std::memory_order_acquire)) == buffer_size) 110 | return false; 111 | else 112 | { 113 | //execute callback only when there is space in buffer 114 | data_buff[tmp_head++ & buffer_mask] = get_data_callback(); 115 | std::atomic_signal_fence(std::memory_order_release); 116 | head.store(tmp_head, std::memory_order_release); 117 | } 118 | return true; 119 | } 120 | 121 | //brief Removes single element without reading 122 | //return True if one element was removed 123 | bool remove() 124 | { 125 | index_t tmp_tail = tail.load(std::memory_order_relaxed); 126 | 127 | if (tmp_tail == head.load(std::memory_order_relaxed)) 128 | return false; 129 | else 130 | tail.store(++tmp_tail, std::memory_order_release); // release in case data was loaded/used before 131 | 132 | return true; 133 | } 134 | 135 | //brief Removes multiple elements without reading and storing it elsewhere 136 | //param cnt Maximum number of elements to remove 137 | //return Number of removed elements 138 | size_t remove(size_t cnt) 139 | { 140 | index_t tmp_tail = tail.load(std::memory_order_relaxed); 141 | index_t avail = head.load(std::memory_order_relaxed) - tmp_tail; 142 | 143 | cnt = (cnt > avail) ? avail : cnt; 144 | 145 | tail.store(tmp_tail + cnt, std::memory_order_release); 146 | return cnt; 147 | } 148 | 149 | //brief Reads one element from internal buffer without blocking 150 | //param[out] data Reference to memory location where removed element will be stored 151 | //return True if data was fetched from the internal buffer 152 | bool remove(T& data) { return remove(&data); }// references are anyway implemented as pointers 153 | 154 | //brief Reads one element from internal buffer without blocking 155 | //param[out] data Pointer to memory location where removed element will be stored 156 | //return True if data was fetched from the internal buffer 157 | bool remove(T* data) 158 | { 159 | index_t tmp_tail = tail.load(std::memory_order_relaxed); 160 | 161 | if (tmp_tail == head.load(std::memory_order_acquire)) 162 | return false; 163 | else 164 | { 165 | *data = data_buff[tmp_tail++ & buffer_mask]; 166 | std::atomic_signal_fence(std::memory_order_release); 167 | tail.store(tmp_tail, std::memory_order_release); 168 | } 169 | return true; 170 | } 171 | 172 | //brief Gets the first element in the buffer on consumed side 173 | //It is safe to use and modify item contents only on consumer side 174 | //return Pointer to first element, nullptr if buffer was empty 175 | T* peek() 176 | { 177 | index_t tmp_tail = tail.load(std::memory_order_relaxed); 178 | 179 | if (tmp_tail == head.load(std::memory_order_acquire)) 180 | return nullptr; 181 | else 182 | return &data_buff[tmp_tail & buffer_mask]; 183 | } 184 | 185 | //brief Gets the n'th element on consumed side 186 | //It is safe to use and modify item contents only on consumer side 187 | //param index Item offset starting on the consumed side 188 | //return Pointer to requested element, nullptr if index exceeds storage count 189 | T* at(size_t index) 190 | { 191 | index_t tmp_tail = tail.load(std::memory_order_relaxed); 192 | 193 | if ((head.load(std::memory_order_acquire) - tmp_tail) <= index) 194 | return nullptr; 195 | else 196 | return &data_buff[(tmp_tail + index) & buffer_mask]; 197 | } 198 | 199 | //brief Gets the n'th element on consumed side 200 | 201 | //Unchecked operation, assumes that software already knows if the element can be used, if 202 | //requested index is out of bounds then reference will point to somewhere inside the buffer 203 | //The isEmpty() and readAvailable() will place appropriate memory barriers if used as loop limiter 204 | //It is safe to use and modify T contents only on consumer side 205 | 206 | //param index Item offset starting on the consumed side 207 | //return Reference to requested element, undefined if index exceeds storage count 208 | T& operator[](size_t index) 209 | { 210 | return data_buff[(tail.load(std::memory_order_relaxed) + index) & buffer_mask]; 211 | } 212 | 213 | //brief Insert multiple elements into internal buffer without blocking 214 | 215 | //This function will insert as much data as possible from given buffer. 216 | 217 | //param[in] buff Pointer to buffer with data to be inserted from 218 | //param count Number of elements to write from the given buffer 219 | //return Number of elements written into internal buffer 220 | size_t writeBuff(const T* buff, size_t count); 221 | 222 | //brief Insert multiple elements into internal buffer without blocking 223 | 224 | //This function will continue writing new entries until all data is written or there is no more space. 225 | //The callback function can be used to indicate to consumer that it can start fetching data. 226 | 227 | //warning This function is not deterministic 228 | 229 | //param[in] buff Pointer to buffer with data to be inserted from 230 | //param count Number of elements to write from the given buffer 231 | //param count_to_callback Number of elements to write before calling a callback function in first loop 232 | //param execute_data_callback Pointer to callback function executed after every loop iteration 233 | //return Number of elements written into internal buffer 234 | size_t writeBuff(const T* buff, size_t count, size_t count_to_callback, void (*execute_data_callback)(void)); 235 | 236 | //brief Load multiple elements from internal buffer without blocking 237 | 238 | //This function will read up to specified amount of data. 239 | 240 | //param[out] buff Pointer to buffer where data will be loaded into 241 | //param count Number of elements to load into the given buffer 242 | //return Number of elements that were read from internal buffer 243 | size_t readBuff(T* buff, size_t count); 244 | 245 | //brief Load multiple elements from internal buffer without blocking 246 | 247 | //This function will continue reading new entries until all requested data is read or there is nothing 248 | //more to read. 249 | //The callback function can be used to indicate to producer that it can start writing new data. 250 | 251 | //warning This function is not deterministic 252 | 253 | //param[out] buff Pointer to buffer where data will be loaded into 254 | //param count Number of elements to load into the given buffer 255 | //param count_to_callback Number of elements to load before calling a callback function in first iteration 256 | //param execute_data_callback Pointer to callback function executed after every loop iteration 257 | //return Number of elements that were read from internal buffer 258 | size_t readBuff(T* buff, size_t count, size_t count_to_callback, void (*execute_data_callback)(void)); 259 | 260 | private: 261 | constexpr static index_t buffer_mask = buffer_size - 1; //!< bitwise mask for a given buffer size 262 | alignas(cacheline_size) std::atomic head; //!< head index 263 | alignas(cacheline_size) std::atomic tail; //!< tail index 264 | 265 | // put buffer after variables so everything can be reached with short offsets 266 | alignas(cacheline_size) T data_buff[buffer_size]; //!< actual buffer 267 | 268 | // let's assert that no UB will be compiled in 269 | static_assert((buffer_size != 0), "buffer cannot be of zero size"); 270 | static_assert((buffer_size& buffer_mask) == 0, "buffer size is not a power of 2"); 271 | static_assert(sizeof(index_t) <= sizeof(size_t), 272 | "indexing type size is larger than size_t, operation is not lock free and doesn't make sense"); 273 | 274 | static_assert(std::numeric_limits::is_integer, "indexing type is not integral type"); 275 | static_assert(!(std::numeric_limits::is_signed), "indexing type shall not be signed"); 276 | static_assert(buffer_mask <= ((std::numeric_limits::max)() >> 1), 277 | "buffer size is too large for a given indexing type (maximum size for n-bit type is 2^(n-1))"); 278 | }; 279 | 280 | template 281 | size_t ringbuffer::writeBuff(const T* buff, size_t count) 282 | { 283 | index_t available = 0; 284 | index_t tmp_head = head.load(std::memory_order_relaxed); 285 | size_t to_write = count; 286 | 287 | available = buffer_size - (tmp_head - tail.load(std::memory_order_acquire)); 288 | 289 | if (available < count) // do not write more than we can 290 | to_write = available; 291 | 292 | // maybe divide it into 2 separate writes 293 | for (size_t i = 0; i < to_write; i++) 294 | data_buff[tmp_head++ & buffer_mask] = buff[i]; 295 | 296 | std::atomic_signal_fence(std::memory_order_release); 297 | head.store(tmp_head, std::memory_order_release); 298 | 299 | return to_write; 300 | } 301 | 302 | template 303 | size_t ringbuffer::writeBuff(const T* buff, size_t count, 304 | size_t count_to_callback, void(*execute_data_callback)()) 305 | { 306 | size_t written = 0; 307 | index_t available = 0; 308 | index_t tmp_head = head.load(std::memory_order_relaxed); 309 | size_t to_write = count; 310 | 311 | if (count_to_callback != 0 && count_to_callback < count) 312 | to_write = count_to_callback; 313 | 314 | while (written < count) 315 | { 316 | available = buffer_size - (tmp_head - tail.load(std::memory_order_acquire)); 317 | 318 | if (available == 0) // less than ?? 319 | break; 320 | 321 | if (to_write > available) // do not write more than we can 322 | to_write = available; 323 | 324 | while (to_write--) 325 | data_buff[tmp_head++ & buffer_mask] = buff[written++]; 326 | 327 | std::atomic_signal_fence(std::memory_order_release); 328 | head.store(tmp_head, std::memory_order_release); 329 | 330 | if (execute_data_callback != nullptr) 331 | execute_data_callback(); 332 | 333 | to_write = count - written; 334 | } 335 | 336 | return written; 337 | } 338 | 339 | template 340 | size_t ringbuffer::readBuff(T* buff, size_t count) 341 | { 342 | index_t available = 0; 343 | index_t tmp_tail = tail.load(std::memory_order_relaxed); 344 | size_t to_read = count; 345 | 346 | available = head.load(std::memory_order_acquire) - tmp_tail; 347 | 348 | if (available < count) // do not read more than we can 349 | to_read = available; 350 | 351 | // maybe divide it into 2 separate reads 352 | for (size_t i = 0; i < to_read; i++) 353 | buff[i] = data_buff[tmp_tail++ & buffer_mask]; 354 | 355 | std::atomic_signal_fence(std::memory_order_release); 356 | tail.store(tmp_tail, std::memory_order_release); 357 | 358 | return to_read; 359 | } 360 | 361 | template 362 | size_t ringbuffer::readBuff(T* buff, size_t count, 363 | size_t count_to_callback, void(*execute_data_callback)()) 364 | { 365 | size_t read = 0; 366 | index_t available = 0; 367 | index_t tmp_tail = tail.load(std::memory_order_relaxed); 368 | size_t to_read = count; 369 | 370 | if (count_to_callback != 0 && count_to_callback < count) 371 | to_read = count_to_callback; 372 | 373 | while (read < count) 374 | { 375 | available = head.load(std::memory_order_acquire) - tmp_tail; 376 | 377 | if (available == 0) // less than ?? 378 | break; 379 | 380 | if (to_read > available) // do not write more than we can 381 | to_read = available; 382 | 383 | while (to_read--) 384 | buff[read++] = data_buff[tmp_tail++ & buffer_mask]; 385 | 386 | std::atomic_signal_fence(std::memory_order_release); 387 | tail.store(tmp_tail, std::memory_order_release); 388 | 389 | if (execute_data_callback != nullptr) 390 | execute_data_callback(); 391 | 392 | to_read = count - read; 393 | } 394 | 395 | return read; 396 | } 397 | 398 | #endif 399 | --------------------------------------------------------------------------------