對(duì)于量化交易來(lái)說(shuō),量化策略和技術(shù)系統(tǒng)缺一不可,為了知其所以然,本文實(shí)現(xiàn)了一個(gè)C++連接CTP接口進(jìn)行仿真交易的demo,從接收行情、下訂單、數(shù)據(jù)處理到添加策略、掛載運(yùn)行交易等多個(gè)環(huán)節(jié)來(lái)看一下量化交易的最簡(jiǎn)單流程,管中窺豹,一探究竟。
?
準(zhǔn)備工作
交易所接口
這里使用上期所提供的CTP接口API,通過(guò)CTP可以連接交易所進(jìn)行行情接收交易。下載地址:CTP下載
本文使用的win32版本的,linux版本用法類似。
CTP接口包含以下內(nèi)容:
?
- ThostFtdcTraderApi.h:C++頭文件,包含交易相關(guān)的指令,如報(bào)單。
- ThostFtdcMdApi.h:C++頭文件,包含獲取行情相關(guān)的指令。
- ThostFtdcUserApiStruct.h:包含了所有用到的數(shù)據(jù)結(jié)構(gòu)。
- ThostFtdcUserApiDataType.h:包含了所有用到的數(shù)據(jù)類型。
- thosttraderapi.lib、thosttraderapi.dll:交易部分的動(dòng)態(tài)鏈接庫(kù)和靜態(tài)鏈接庫(kù)。
- thostmduserapi.lib、thostmduserapi.dll:行情部分的動(dòng)態(tài)鏈接庫(kù)和靜態(tài)鏈接庫(kù)。
- error.dtd、error.xml:包含所有可能的錯(cuò)誤信息。
?
?
整個(gè)開發(fā)包有2個(gè)核心頭文件包括4個(gè)核心接口,
CThostFtdcMdApi接口和CThostFtdcTraderApi兩個(gè)頭文件,一個(gè)處理行情,一個(gè)處理交易。
(1)處理行情的CThostFtdcMdApi接口有兩個(gè)類,分別是CThostFtdcMdApi和CThostFtdcMdSpi,以Api結(jié)尾的是用來(lái)下命令的,以Spi結(jié)尾的是用來(lái)響應(yīng)命令的回調(diào)。
(2)處理交易的CThostFtdcTraderApi接口也有兩個(gè)類,分別是CThostFtdcTraderApi和CThostFtdcTraderSpi, ?通過(guò)CThostFtdcTraderApi向CTP發(fā)送操作請(qǐng)求,通過(guò)CThostFtdcTraderSpi接收CTP的操作響應(yīng)。
?
期貨賬戶
要連接期貨交易所交易,需要開設(shè)自己的賬戶,實(shí)現(xiàn)期貨交易、銀期轉(zhuǎn)賬、保證金等功能,由于小白一般不會(huì)用實(shí)盤資金交易,所以此處推薦用上期所提供的simnow虛擬交易平臺(tái)simnow申請(qǐng)一個(gè)虛擬賬戶。
SIMNOW提供兩類數(shù)據(jù)前置地址:
(1)交易時(shí)段的地址,如09:00-15:00和21:00-02:30,使用第一套地址,這些數(shù)據(jù)是真實(shí)的行情數(shù)據(jù),只是時(shí)間上比真實(shí)的行情會(huì)有延遲30秒左右(SIMNOW從交易所接收后轉(zhuǎn)發(fā)出來(lái)的)。
(2)非交易時(shí)段地址,這時(shí)的數(shù)據(jù)是歷史行情的播放,比如昨天的數(shù)據(jù)之類的,可以用來(lái)做程序調(diào)試。
?
建議選擇申請(qǐng)那個(gè)7x24行情的賬戶,便于開發(fā)調(diào)試。
?
開發(fā)步驟
工程總覽
?
其中,
?
- CTP的API文件配置到工程
- CustomMdSpi.h,CustomMdSpi.cpp是派生的行情回調(diào)類
- CustomTradeSpi.h,CustomTradeSpi.cpp是派生的交易回調(diào)類
- TickToKlineHelper.h,TickToKlineHelper.cpp是處理時(shí)序數(shù)據(jù),轉(zhuǎn)換成K線的類
- StrategyTrade.h,StrategyTrade.cpp是策略類
- main.cpp是程序的入口
一個(gè)簡(jiǎn)單的程序化交易系統(tǒng)需要完成的業(yè)務(wù)可以劃分為:
1.基本操作,比如登錄,訂閱等;
2.行情操作,比如對(duì)行情數(shù)據(jù)的接收,存儲(chǔ)等
3.訂單操作,比如報(bào)單;對(duì)報(bào)單,成交狀況的查詢;報(bào)單,成交狀況的私有回報(bào)等。
4.數(shù)據(jù)監(jiān)聽和處理操作,比如接收到新數(shù)據(jù)之后的統(tǒng)計(jì)處理,滿足統(tǒng)計(jì)條件后的報(bào)單處理(其實(shí)這里就是我們的策略所在)
?
導(dǎo)入CTP接口庫(kù)
visual studio創(chuàng)建工程后,首先需要將ctp的頭文件以及鏈接庫(kù)(lib和dll)目錄配置到工程
?
?
?
#pragma comment (lib, "thostmduserapi.lib")#pragma comment (lib, "thosttraderapi.lib")
?
?
全局參數(shù)
連接到交易所,需要配置經(jīng)紀(jì)商代碼、帳戶名、密碼以及訂閱合約和買賣合約的相關(guān)參數(shù)
TThostFtdcBrokerIDType gBrokerID = "9999"; TThostFtdcInvestorIDType gInvesterID = ""; TThostFtdcPasswordType gInvesterPassword = ""; CThostFtdcMdApi *g_pMdUserApi = nullptr; char gMdFrontAddr[] = "tcp://180.168.146.187:10010"; char *g_pInstrumentID[] = {"TF1706", "zn1705", "cs1801", "CF705"}; int instrumentNum = 4; unordered_map<string, TickToKlineHelper> g_KlineHash; CThostFtdcTraderApi *g_pTradeUserApi = nullptr; char gTradeFrontAddr[] = "tcp://180.168.146.187:10001"; TThostFtdcInstrumentIDType g_pTradeInstrumentID = "m1709"; TThostFtdcDirectionType gTradeDirection = THOST_FTDC_D_Sell; TThostFtdcPriceType gLimitPrice = 2818;
這里只是簡(jiǎn)單的寫一下,真實(shí)完整的交易系統(tǒng)中,一般用配置文件,有用戶去定制
?
行情回調(diào)類
繼承CThostFtdcMdSpi實(shí)現(xiàn)自己的行情回調(diào)類CustomMdSpi,在系統(tǒng)運(yùn)行時(shí)這些重寫的函數(shù)會(huì)被CTP的系統(tǒng)api回調(diào)從而實(shí)現(xiàn)個(gè)性化行情
CustomMdSpi頭文件
?
#pragma once#include <vector>#include "CTP_API/ThostFtdcMdApi.h" class CustomMdSpi: public CThostFtdcMdSpi{ public: void OnFrontConnected(); void OnFrontDisconnected(int nReason); void OnHeartBeatWarning(int nTimeLapse); void OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRspUserLogout(CThostFtdcUserLogoutField *pUserLogout, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRspSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRspUnSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRspSubForQuoteRsp(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRspUnSubForQuoteRsp(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketData); void OnRtnForQuoteRsp(CThostFtdcForQuoteRspField *pForQuoteRsp);};
?
都是重寫回調(diào)函數(shù)
連接應(yīng)答
?
void CustomMdSpi::OnFrontConnected(){ std::cout << "=====建立網(wǎng)絡(luò)連接成功=====" << std::endl; CThostFtdcReqUserLoginField loginReq; memset(&loginReq, 0, sizeof(loginReq)); strcpy(loginReq.BrokerID, gBrokerID); strcpy(loginReq.UserID, gInvesterID); strcpy(loginReq.Password, gInvesterPassword); static int requestID = 0; int rt = g_pMdUserApi->ReqUserLogin(&loginReq, requestID); if (!rt) std::cout << ">>>>>>發(fā)送登錄請(qǐng)求成功" << std::endl; else std::cerr << "--->>>發(fā)送登錄請(qǐng)求失敗" << std::endl;}
?
登錄應(yīng)答
?
void CustomMdSpi::OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast){ bool bResult = pRspInfo && (pRspInfo->ErrorID != 0); if (!bResult){ std::cout << "=====賬戶登錄成功=====" << std::endl; std::cout << "交易日: " << pRspUserLogin->TradingDay << std::endl; std::cout << "登錄時(shí)間: " << pRspUserLogin->LoginTime << std::endl; std::cout << "經(jīng)紀(jì)商: " << pRspUserLogin->BrokerID << std::endl; std::cout << "帳戶名: " << pRspUserLogin->UserID << std::endl; int rt = g_pMdUserApi->SubscribeMarketData(g_pInstrumentID, instrumentNum); if (!rt) std::cout << ">>>>>>發(fā)送訂閱行情請(qǐng)求成功" << std::endl; else std::cerr << "--->>>發(fā)送訂閱行情請(qǐng)求失敗" << std::endl;} else std::cerr << "返回錯(cuò)誤--->>> ErrorID=" << pRspInfo->ErrorID << ", ErrorMsg=" << pRspInfo->ErrorMsg << std::endl;}
?
訂閱行情應(yīng)答
?
void CustomMdSpi::OnRspSubMarketData(CThostFtdcSpecificInstrumentField *pSpecificInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast){ bool bResult = pRspInfo && (pRspInfo->ErrorID != 0); if (!bResult){ std::cout << "=====訂閱行情成功=====" << std::endl; std::cout << "合約代碼: " << pSpecificInstrument->InstrumentID << std::endl; char filePath[100] = {'\0'}; sprintf(filePath, "%s_market_data.csv", pSpecificInstrument->InstrumentID); std::ofstream outFile;outFile.open(filePath, std::ios::out); outFile << "合約代碼" << ","<< "更新時(shí)間" << ","<< "最新價(jià)" << ","<< "成交量" << ","<< "買價(jià)一" << ","<< "買量一" << ","<< "賣價(jià)一" << ","<< "賣量一" << ","<< "持倉(cāng)量" << ","<< "換手率"<< std::endl;outFile.close();} else std::cerr << "返回錯(cuò)誤--->>> ErrorID=" << pRspInfo->ErrorID << ", ErrorMsg=" << pRspInfo->ErrorMsg << std::endl;}
?
- 因?yàn)槭钱惒浇涌?#xff0c;這里連接、登錄、訂閱行情是一步套一步來(lái)調(diào)用的,在運(yùn)行過(guò)程中,會(huì)啟動(dòng)一個(gè)行情線程,交易所每500ms會(huì)推送一個(gè)訂閱的行情tick數(shù)據(jù),因此,某些接口會(huì)被連續(xù)間隔調(diào)用,直到連接關(guān)閉
- 收到行情后除了存在內(nèi)存,也可以用文本文件或者數(shù)據(jù)庫(kù)等形式存儲(chǔ)起來(lái),在這里創(chuàng)建初始文件或者建庫(kù)
深度行情通知
void CustomMdSpi::OnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketData){ std::cout << "=====獲得深度行情=====" << std::endl; std::cout << "交易日: " << pDepthMarketData->TradingDay << std::endl; std::cout << "交易所代碼: " << pDepthMarketData->ExchangeID << std::endl; std::cout << "合約代碼: " << pDepthMarketData->InstrumentID << std::endl; std::cout << "合約在交易所的代碼: " << pDepthMarketData->ExchangeInstID << std::endl; std::cout << "最新價(jià): " << pDepthMarketData->LastPrice << std::endl; std::cout << "數(shù)量: " << pDepthMarketData->Volume << std::endl; char filePath[100] = {'\0'}; sprintf(filePath, "%s_market_data.csv", pDepthMarketData->InstrumentID); std::ofstream outFile;outFile.open(filePath, std::ios::app); outFile << pDepthMarketData->InstrumentID << "," << pDepthMarketData->UpdateTime << "." << pDepthMarketData->UpdateMillisec << "," << pDepthMarketData->LastPrice << "," << pDepthMarketData->Volume << "," << pDepthMarketData->BidPrice1 << "," << pDepthMarketData->BidVolume1 << "," << pDepthMarketData->AskPrice1 << "," << pDepthMarketData->AskVolume1 << "," << pDepthMarketData->OpenInterest << "," << pDepthMarketData->Turnover << std::endl;outFile.close(); std::string instrumentKey = std::string(pDepthMarketData->InstrumentID); if (g_KlineHash.find(instrumentKey) == g_KlineHash.end())g_KlineHash[instrumentKey] = TickToKlineHelper();g_KlineHash[instrumentKey].KLineFromRealtimeData(pDepthMarketData); }
- 每個(gè)tick世間節(jié)點(diǎn)系統(tǒng)都會(huì)調(diào)用這個(gè)函數(shù),推送具體的行情截面數(shù)據(jù)
- 可以在此處將行情寫到本地,或者做一些數(shù)據(jù)處理(例如實(shí)時(shí)K線計(jì)算,判斷是否觸發(fā)策略等)
?
交易回調(diào)類
同理,也需要繼承CThostFtdcTraderSpi來(lái)實(shí)現(xiàn)自己的CustomTradeSpi類,用于交易下單、報(bào)單等操作的回調(diào)
CustomTradeSpi頭文件
?
#pragma once#include "CTP_API/ThostFtdcTraderApi.h" class CustomTradeSpi : public CThostFtdcTraderSpi{public: void OnFrontConnected(); void OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRspError(CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnFrontDisconnected(int nReason); void OnHeartBeatWarning(int nTimeLapse); void OnRspUserLogout(CThostFtdcUserLogoutField *pUserLogout, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRspSettlementInfoConfirm(CThostFtdcSettlementInfoConfirmField *pSettlementInfoConfirm, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRspQryInstrument(CThostFtdcInstrumentField *pInstrument, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRspQryTradingAccount(CThostFtdcTradingAccountField *pTradingAccount, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRspQryInvestorPosition(CThostFtdcInvestorPositionField *pInvestorPosition, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRspOrderInsert(CThostFtdcInputOrderField *pInputOrder, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRspOrderAction(CThostFtdcInputOrderActionField *pInputOrderAction, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast); void OnRtnOrder(CThostFtdcOrderField *pOrder); void OnRtnTrade(CThostFtdcTradeField *pTrade); public: bool loginFlag; void reqOrderInsert( TThostFtdcInstrumentIDType instrumentID, TThostFtdcPriceType price, TThostFtdcVolumeType volume, TThostFtdcDirectionType direction); private: void reqUserLogin(); void reqUserLogout(); void reqSettlementInfoConfirm(); void reqQueryInstrument(); void reqQueryTradingAccount(); void reqQueryInvestorPosition(); void reqOrderInsert(); void reqOrderAction(CThostFtdcOrderField *pOrder); bool isErrorRspInfo(CThostFtdcRspInfoField *pRspInfo); bool isMyOrder(CThostFtdcOrderField *pOrder); bool isTradingOrder(CThostFtdcOrderField *pOrder); };
?
?
除了重寫的基類函數(shù),還自己封裝一些主動(dòng)調(diào)用的操作函數(shù),比如登入登出、下單報(bào)單、查詢報(bào)單等
?
?
?
登錄應(yīng)答
?
void CustomTradeSpi::OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin,CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast){ if (!isErrorRspInfo(pRspInfo)){ std::cout << "=====賬戶登錄成功=====" << std::endl;loginFlag = true; std::cout << "交易日: " << pRspUserLogin->TradingDay << std::endl; std::cout << "登錄時(shí)間: " << pRspUserLogin->LoginTime << std::endl; std::cout << "經(jīng)紀(jì)商: " << pRspUserLogin->BrokerID << std::endl; std::cout << "帳戶名: " << pRspUserLogin->UserID << std::endl; trade_front_id = pRspUserLogin->FrontID;session_id = pRspUserLogin->SessionID; strcpy(order_ref, pRspUserLogin->MaxOrderRef); reqSettlementInfoConfirm();}}
查詢投資者結(jié)算結(jié)果應(yīng)答
?
?
void CustomTradeSpi::OnRspSettlementInfoConfirm(CThostFtdcSettlementInfoConfirmField *pSettlementInfoConfirm,CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast){ if (!isErrorRspInfo(pRspInfo)){ std::cout << "=====投資者結(jié)算結(jié)果確認(rèn)成功=====" << std::endl; std::cout << "確認(rèn)日期: " << pSettlementInfoConfirm->ConfirmDate << std::endl; std::cout << "確認(rèn)時(shí)間: " << pSettlementInfoConfirm->ConfirmTime << std::endl; reqQueryInstrument();}}
查詢合約應(yīng)答
?
?
void CustomTradeSpi::OnRspQryInstrument(CThostFtdcInstrumentField *pInstrument,CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast){ if (!isErrorRspInfo(pRspInfo)){ std::cout << "=====查詢合約結(jié)果成功=====" << std::endl; std::cout << "交易所代碼: " << pInstrument->ExchangeID << std::endl; std::cout << "合約代碼: " << pInstrument->InstrumentID << std::endl; std::cout << "合約在交易所的代碼: " << pInstrument->ExchangeInstID << std::endl; std::cout << "執(zhí)行價(jià): " << pInstrument->StrikePrice << std::endl; std::cout << "到期日: " << pInstrument->EndDelivDate << std::endl; std::cout << "當(dāng)前交易狀態(tài): " << pInstrument->IsTrading << std::endl; reqQueryTradingAccount();}}
查詢投資者資金帳戶應(yīng)答
?
?
void CustomTradeSpi::OnRspQryTradingAccount(CThostFtdcTradingAccountField *pTradingAccount,CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast){ if (!isErrorRspInfo(pRspInfo)){ std::cout << "=====查詢投資者資金賬戶成功=====" << std::endl; std::cout << "投資者賬號(hào): " << pTradingAccount->AccountID << std::endl; std::cout << "可用資金: " << pTradingAccount->Available << std::endl; std::cout << "可取資金: " << pTradingAccount->WithdrawQuota << std::endl; std::cout << "當(dāng)前保證金: " << pTradingAccount->CurrMargin << std::endl; std::cout << "平倉(cāng)盈虧: " << pTradingAccount->CloseProfit << std::endl; reqQueryInvestorPosition();}}
查詢投資者持倉(cāng)應(yīng)答
?
?
void CustomTradeSpi::OnRspQryInvestorPosition(CThostFtdcInvestorPositionField *pInvestorPosition,CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast){ if (!isErrorRspInfo(pRspInfo)){ std::cout << "=====查詢投資者持倉(cāng)成功=====" << std::endl; if (pInvestorPosition){ std::cout << "合約代碼: " << pInvestorPosition->InstrumentID << std::endl; std::cout << "開倉(cāng)價(jià)格: " << pInvestorPosition->OpenAmount << std::endl; std::cout << "開倉(cāng)量: " << pInvestorPosition->OpenVolume << std::endl; std::cout << "開倉(cāng)方向: " << pInvestorPosition->PosiDirection << std::endl; std::cout << "占用保證金:" << pInvestorPosition->UseMargin << std::endl;} else std::cout << "----->該合約未持倉(cāng)" << std::endl; if (loginFlag)reqOrderInsert(g_pTradeInstrumentID, gLimitPrice, 1, gTradeDirection); }}
這里把下單錄入的操作放在了持倉(cāng)結(jié)果出來(lái)之后的回調(diào)里面,策略交易也簡(jiǎn)單的放在了這里,真實(shí)的情況下,應(yīng)該是由行情觸發(fā)某個(gè)策略條件開一個(gè)線程進(jìn)行策略交易
?
下單操作
?
?
void CustomTradeSpi::reqOrderInsert(TThostFtdcInstrumentIDType instrumentID,TThostFtdcPriceType price,TThostFtdcVolumeType volume,TThostFtdcDirectionType direction){CThostFtdcInputOrderField orderInsertReq; memset(&orderInsertReq, 0, sizeof(orderInsertReq)); strcpy(orderInsertReq.BrokerID, gBrokerID); strcpy(orderInsertReq.InvestorID, gInvesterID); strcpy(orderInsertReq.InstrumentID, instrumentID); strcpy(orderInsertReq.OrderRef, order_ref); orderInsertReq.OrderPriceType = THOST_FTDC_OPT_LimitPrice; orderInsertReq.Direction = direction; orderInsertReq.CombOffsetFlag[0] = THOST_FTDC_OF_Open; orderInsertReq.CombHedgeFlag[0] = THOST_FTDC_HF_Speculation; orderInsertReq.LimitPrice = price; orderInsertReq.VolumeTotalOriginal = volume; orderInsertReq.TimeCondition = THOST_FTDC_TC_GFD; orderInsertReq.VolumeCondition = THOST_FTDC_VC_AV; orderInsertReq.MinVolume = 1; orderInsertReq.ContingentCondition = THOST_FTDC_CC_Immediately; orderInsertReq.ForceCloseReason = THOST_FTDC_FCC_NotForceClose; orderInsertReq.IsAutoSuspend = 0; orderInsertReq.UserForceClose = 0; static int requestID = 0; int rt = g_pTradeUserApi->ReqOrderInsert(&orderInsertReq, ++requestID); if (!rt) std::cout << ">>>>>>發(fā)送報(bào)單錄入請(qǐng)求成功" << std::endl; else std::cerr << "--->>>發(fā)送報(bào)單錄入請(qǐng)求失敗" << std::endl;}
通過(guò)重載寫了兩個(gè)函數(shù),一個(gè)是用默認(rèn)參數(shù)下單,一個(gè)可以傳參下單,比如設(shè)定合約代碼、價(jià)格、數(shù)量等
?
報(bào)單操作
?
void CustomTradeSpi::reqOrderAction(CThostFtdcOrderField *pOrder){ static bool orderActionSentFlag = false; if (orderActionSentFlag) return; CThostFtdcInputOrderActionField orderActionReq; memset(&orderActionReq, 0, sizeof(orderActionReq)); strcpy(orderActionReq.BrokerID, pOrder->BrokerID); strcpy(orderActionReq.InvestorID, pOrder->InvestorID); strcpy(orderActionReq.OrderRef, pOrder->OrderRef); orderActionReq.FrontID = trade_front_id; orderActionReq.SessionID = session_id; orderActionReq.ActionFlag = THOST_FTDC_AF_Delete; strcpy(orderActionReq.InstrumentID, pOrder->InstrumentID); static int requestID = 0; int rt = g_pTradeUserApi->ReqOrderAction(&orderActionReq, ++requestID); if (!rt) std::cout << ">>>>>>發(fā)送報(bào)單操作請(qǐng)求成功" << std::endl; else std::cerr << "--->>>發(fā)送報(bào)單操作請(qǐng)求失敗" << std::endl;orderActionSentFlag = true;}
?
主要是對(duì)于未成交的訂單進(jìn)行編輯或者撤銷操作
?
?
報(bào)單應(yīng)答
?
void CustomTradeSpi::OnRtnOrder(CThostFtdcOrderField *pOrder){ char str[10]; sprintf(str, "%d", pOrder->OrderSubmitStatus); int orderState = atoi(str) - 48; std::cout << "=====收到報(bào)單應(yīng)答=====" << std::endl; if (isMyOrder(pOrder)){ if (isTradingOrder(pOrder)){ std::cout << "--->>> 等待成交中!" << std::endl; } else if (pOrder->OrderStatus == THOST_FTDC_OST_Canceled) std::cout << "--->>> 撤單成功!" << std::endl;}} void CustomTradeSpi::OnRtnTrade(CThostFtdcTradeField *pTrade){ std::cout << "=====報(bào)單成功成交=====" << std::endl; std::cout << "成交時(shí)間: " << pTrade->TradeTime << std::endl; std::cout << "合約代碼: " << pTrade->InstrumentID << std::endl; std::cout << "成交價(jià)格: " << pTrade->Price << std::endl; std::cout << "成交量: " << pTrade->Volume << std::endl; std::cout << "開平倉(cāng)方向: " << pTrade->Direction << std::endl;}
等待成交進(jìn)行輪詢可以選擇報(bào)單操作,成交完成后的應(yīng)答
?
時(shí)間序列轉(zhuǎn)K線
從交易拿到的tick數(shù)據(jù)是時(shí)間序列數(shù)據(jù),在證券交易中其實(shí)還需要根據(jù)時(shí)間序列算出一些技術(shù)指標(biāo)數(shù)據(jù),例如MACD,KDJ、K線等,這里簡(jiǎn)單地對(duì)數(shù)據(jù)做一下處理,寫一個(gè)TickToKlineHelper將時(shí)間序列專程K線
?
?
K線數(shù)據(jù)結(jié)構(gòu)
?
struct KLineDataType{ double open_price; double high_price; double low_price; double close_price; int volume; };
轉(zhuǎn)換函數(shù)
?
?
void TickToKlineHelper::KLineFromLocalData(const std::string &sFilePath, const std::string &dFilePath){ m_priceVec.clear();m_volumeVec.clear();m_KLineDataArray.clear(); std::cout << "開始轉(zhuǎn)換tick到k線..." << std::endl; std::ifstream srcInFile; std::ofstream dstOutFile;srcInFile.open(sFilePath, std::ios::in);dstOutFile.open(dFilePath, std::ios::out);dstOutFile << "開盤價(jià)" << ','<< "最高價(jià)" << ','<< "最低價(jià)" << ','<< "收盤價(jià)" << ',' << "成交量" << std::endl; std::string lineStr; bool isFirstLine = true; while (std::getline(srcInFile, lineStr)){ if (isFirstLine){ isFirstLine = false; continue;} std::istringstream ss(lineStr); std::string fieldStr; int count = 4; while (std::getline(ss, fieldStr, ',')){count--; if (count == 1)m_priceVec.push_back(std::atof(fieldStr.c_str())); else if (count == 0){m_volumeVec.push_back(std::atoi(fieldStr.c_str())); break;}} if (m_priceVec.size() == kDataLineNum){KLineDataType k_line_data;k_line_data.open_price = m_priceVec.front();k_line_data.high_price = *std::max_element(m_priceVec.cbegin(), m_priceVec.cend());k_line_data.low_price = *std::min_element(m_priceVec.cbegin(), m_priceVec.cend());k_line_data.close_price = m_priceVec.back(); k_line_data.volume = m_volumeVec.back() - m_volumeVec.front(); dstOutFile << k_line_data.open_price << ','<< k_line_data.high_price << ','<< k_line_data.low_price << ','<< k_line_data.close_price << ','<< k_line_data.volume << std::endl; m_priceVec.clear();m_volumeVec.clear();}} srcInFile.close();dstOutFile.close(); std::cout << "k線生成成功" << std::endl;} void TickToKlineHelper::KLineFromRealtimeData(CThostFtdcDepthMarketDataField *pDepthMarketData){m_priceVec.push_back(pDepthMarketData->LastPrice);m_volumeVec.push_back(pDepthMarketData->Volume); if (m_priceVec.size() == kDataLineNum){KLineDataType k_line_data;k_line_data.open_price = m_priceVec.front();k_line_data.high_price = *std::max_element(m_priceVec.cbegin(), m_priceVec.cend());k_line_data.low_price = *std::min_element(m_priceVec.cbegin(), m_priceVec.cend());k_line_data.close_price = m_priceVec.back(); k_line_data.volume = m_volumeVec.back() - m_volumeVec.front();m_KLineDataArray.push_back(k_line_data); m_priceVec.clear();m_volumeVec.clear();}}
?
- 可以從本地文件中讀取行情數(shù)據(jù),進(jìn)行離線轉(zhuǎn)換,也可以在接受到行情時(shí)進(jìn)行實(shí)時(shí)計(jì)算
- 基本思想是,針對(duì)每個(gè)合約代碼,建立字典,維持一個(gè)行情數(shù)組,當(dāng)時(shí)間間隔達(dá)到要求(例如分鐘、分時(shí)、分日)時(shí)計(jì)算該時(shí)段的開、高、低、收、成交量等數(shù)據(jù)存入K線數(shù)組
- 最低時(shí)間單位的K線計(jì)算出來(lái)之后,高時(shí)間間隔的K線數(shù)據(jù)可以根據(jù)低時(shí)間間隔的K線計(jì)算出來(lái)(例如,算出了分鐘K,那么分時(shí)K就根據(jù)分鐘K來(lái)算)
- 本例子中只是實(shí)現(xiàn)了一個(gè)大概的原理,非常不精確,僅供參考
策略交易
量化交易系統(tǒng)最終是需要將編寫的策略代碼掛載到系統(tǒng)中進(jìn)行策略交易的,這里做了一個(gè)簡(jiǎn)單的實(shí)現(xiàn)
StrategyTrade.h
#pragma once #include <functional>#include "CTP_API/ThostFtdcUserApiStruct.h"#include "TickToKlineHelper.h"#include "CustomTradeSpi.h" typedef void(*reqOrderInsertFun)( TThostFtdcInstrumentIDType instrumentID, TThostFtdcPriceType price, TThostFtdcVolumeType volume, TThostFtdcDirectionType direction); using ReqOrderInsertFunctionType = std::function< void(TThostFtdcInstrumentIDType instrumentID,TThostFtdcPriceType price,TThostFtdcVolumeType volume,TThostFtdcDirectionType direction)>; void StrategyCheckAndTrade(TThostFtdcInstrumentIDType instrumentID, CustomTradeSpi *customTradeSpi);
StrategyTrade.cpp
#include <vector>#include <string>#include <unordered_map>#include <thread>#include <mutex>#include "StrategyTrade.h"#include "CustomTradeSpi.h" extern std::unordered_map<std::string, TickToKlineHelper> g_KlineHash; std::mutex marketDataMutex; void StrategyCheckAndTrade(TThostFtdcInstrumentIDType instrumentID, CustomTradeSpi *customTradeSpi){ std::lock_guard<std::mutex> lk(marketDataMutex);TickToKlineHelper tickToKlineObject = g_KlineHash.at(std::string(instrumentID)); std::vector<double> priceVec = tickToKlineObject.m_priceVec; if (priceVec.size() >= 3){ int len = priceVec.size(); if (priceVec[len - 1] > priceVec[len - 2] && priceVec[len - 2] > priceVec[len - 3])customTradeSpi->reqOrderInsert(instrumentID, priceVec[len - 1], 1, THOST_FTDC_D_Buy); else if (priceVec[len - 1] < priceVec[len - 2] && priceVec[len - 2] < priceVec[len - 3])customTradeSpi->reqOrderInsert(instrumentID, priceVec[len - 1], 1, THOST_FTDC_D_Buy);}}
- 基本思想,針對(duì)指定合約,判斷如果連續(xù)三個(gè)上漲就買開倉(cāng),連續(xù)三個(gè)下跌就賣開倉(cāng),價(jià)格都是用最新價(jià)
- 因?yàn)樾星楹徒灰资欠珠_的線程,涉及到線程競(jìng)爭(zhēng),所以在實(shí)際下單時(shí)需要加入互斥鎖,線程同步
- 策略如何被行情觸發(fā)然后交易其實(shí)需要用事件驅(qū)動(dòng)來(lái)做的,這里沒(méi)有實(shí)現(xiàn)T_T
入口
main.cpp
int main(){ cout << "請(qǐng)輸入賬號(hào): "; scanf("%s", gInvesterID); cout << "請(qǐng)輸入密碼: "; scanf("%s", gInvesterPassword); cout << "初始化行情..." << endl;g_pMdUserApi = CThostFtdcMdApi::CreateFtdcMdApi(); CThostFtdcMdSpi *pMdUserSpi = new CustomMdSpi; g_pMdUserApi->RegisterSpi(pMdUserSpi); g_pMdUserApi->RegisterFront(gMdFrontAddr); g_pMdUserApi->Init(); cout << "初始化交易..." << endl;g_pTradeUserApi = CThostFtdcTraderApi::CreateFtdcTraderApi(); CustomTradeSpi *pTradeSpi = new CustomTradeSpi; g_pTradeUserApi->RegisterSpi(pTradeSpi); g_pTradeUserApi->SubscribePublicTopic(THOST_TERT_RESTART); g_pTradeUserApi->SubscribePrivateTopic(THOST_TERT_RESTART); g_pTradeUserApi->RegisterFront(gTradeFrontAddr); g_pTradeUserApi->Init(); g_pMdUserApi->Join(); delete pMdUserSpi;g_pMdUserApi->Release(); g_pTradeUserApi->Join(); delete pTradeSpi;g_pTradeUserApi->Release(); getchar(); return 0;}
- CThostFtdcMdApi跟CustomMdSpi要建立關(guān)聯(lián),CThostFtdcTraderApi跟CustomTradeSpi建立關(guān)聯(lián),其實(shí)就是類似于函數(shù)注冊(cè)
- 配置行情和交易地址
- 行情和交易分別是不同的線程,注意線程同步
- 記得內(nèi)存回收
運(yùn)行結(jié)果
行情
應(yīng)答日志
?
存成csv表格
?
交易
應(yīng)答日志
?
?
K線數(shù)據(jù)
?
?
?
?
報(bào)單情況
用上期所的快期軟件,登錄上自己的賬號(hào)之后,從過(guò)程序下單,在這個(gè)界面里能看到實(shí)時(shí)的報(bào)單成交狀況
?
?
源碼下載
?
csdn:demo
?
github:demo
?
結(jié)語(yǔ)
?
本文旨在為剛接觸CTP的小白們拋磚引玉,各交易接口的深度運(yùn)用還需要看官方開發(fā)文檔。
另外,對(duì)于完整的量化交易系統(tǒng)來(lái)說(shuō),不僅要具備行情、交易、策略模塊,事件驅(qū)動(dòng)、風(fēng)控、回測(cè)模塊以及底層的數(shù)據(jù)存儲(chǔ)、網(wǎng)絡(luò)并發(fā)都是需要深入鉆研的方面,金融工程的Quant Researcher可以只專注于數(shù)據(jù)的分析、策略的研發(fā),但是對(duì)于程序員Quant Developer來(lái)說(shuō),如何設(shè)計(jì)和開發(fā)一個(gè)高并發(fā)、低延遲、功能完善與策略結(jié)合緊密的量化交易系統(tǒng)的確是一項(xiàng)需要不斷完善的工程。
?
ps:如果需要更高級(jí)和細(xì)致甚至可以用于實(shí)盤的功能,比如完整的開源交易系統(tǒng),數(shù)據(jù)系統(tǒng),算法交易,數(shù)據(jù)和交易接口等完備的解決方案,由于博客回復(fù)不現(xiàn)實(shí),只能私信聯(lián)系啦~
支持是知識(shí)分享的動(dòng)力,有問(wèn)題可掃碼哦
?
總結(jié)
以上是生活随笔為你收集整理的C++连接CTP接口实现简单量化交易(行情、交易、k线、策略)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。