1. 程式人生 > >基於CTP的國內期貨程式化交易之行情獲取講解

基於CTP的國內期貨程式化交易之行情獲取講解

       前面兩篇文章主要講了國外期貨相關程式開發,使用的是鄭州易盛的行情及交易api,而國內期貨相關程式開發易盛貌似也是有sdk的,不過專案中使用的是上期技術的sdk,即大家經常提到的CTP api——綜合交易平臺api。相比較而言,易盛給自己的sdk起的名字好聽一點,叫易盛國際金融衍生品交易分析系統,聽著高大上一些。        上期技術的api使用思路與易盛的api基本一致,大同小異,其實無論誰設計這個架構,基本也都是這個思路,一個發起請求呼叫,一個響應請求回撥,呼叫邏輯由sdk提供方編寫,回撥邏輯由開發者編寫,這樣共同完成整個業務邏輯開發。不過畢竟是兩家公司開發的sdk,所以在定義引數及一些交易術語上,還是有些不同的,這個需要開發者多查閱文件、多摸索才行。
       基於CPT api開發行情獲取程式,主要用到的標頭檔案為:ThostFtdcMdApi.h、ThostFtdcUserApiDataType.h及ThostFtdcUserApiStruct.h,動態庫為:libthostmduserapi.so。        下面是一些程式碼示例:        1. 建立CTP api例項:
CThostFtdcMdApi *pMarketDataApi = CThostFtdcMdApi::CreateFtdcMdApi(dirName);
      即通過呼叫CreateFtdcMdApi()建立api例項——pMarketDataApi,隨後呼叫該例項發起各種請求,比如連線伺服器、使用者登入、訂閱合約、退訂合約等。
2. 建立CTP api回撥例項:
MarketDataSource *pDataSource = new MarketDataSource(pMarketDataApi, this);
這個需要自己編寫相應實現類,需要繼承上期技術提供的CThostFtdcMdSpi類。重寫該類裡面的方法,以處理伺服器發過來的各類資料。 3. 將上述兩個例項關聯起來,併發起連線伺服器及使用者登入:
pMarketDataApi->RegisterSpi(pDataSource);
pDataSource->connect(serverAddr, brokerId, username, password);
連線伺服器以及例項初始化相關程式碼:
void MarketDataSource::connect(string serverAddr, string brokerId, string username, string password)
{
    serverAddr_ = serverAddr;
    brokerId_ = brokerId;
    username_ = username;
    password_ = password;

    pMarketDataApi_->RegisterFront((char *)serverAddr_.c_str());
    pMarketDataApi_->Init();
}
連線請求發出後,OnFrontConnected()及OnRspUserLogin()會響應請求,根據返回的資訊,可以確定是否登入完成。登入成功後,就可以訂閱合約了。
void MarketDataSource::OnFrontConnected()
{
    LOG_INFO << username_ << " 回撥: 與伺服器已建立連線, 開始登入";
}

void MarketDataSource::OnRspUserLogin(CThostFtdcRspUserLoginField *pRspUserLogin, CThostFtdcRspInfoField *pRspInfo, int nRequestID, bool bIsLast)
{
    if (pRspInfo == NULL)
    {
        LOG_INFO << username_ << " 登入回撥異常, 指標為空";
        return;
    }

    if (pRspInfo->ErrorID == Err_Succeed)
    {
        LOG_INFO << username_ << " 登入成功, 當前交易日: " << pMarketDataApi_->GetTradingDay();
    }
}
       4. 訂閱期貨合約:
void MarketDataSource::subscribeContracts(std::set<ContractInfo> &contracts)
{
    const size_t count = contracts.size();
    char *instruments[count];
    int i = 0;
    for (std::set<ContractInfo>::iterator it = contracts.begin(); it != contracts.end(); ++it)
    {
        string strInstrument = it->CommodityNo + it->ContractNo;
        instruments[i] = new char[32];
        memset(instruments[i], 0, 32);
        strcpy(instruments[i], strInstrument.c_str());
        i++;
    }

    int result = Err_Succeed;
    result = pMarketDataApi_->SubscribeMarketData(instruments, (int)count);
    if (result == Err_Succeed)
    {
        LOG_INFO << username_ << " " << "請求: 合約訂閱成功";
    }
    else
    {
        LOG_INFO << username_ << " "
                 << "請求: 合約訂閱失敗" << " "
                 << "錯誤碼: " << result << " " << ErrorCode::get(result);
    }

    for (i = 0; i < count; ++i)
    {
        delete[] instruments[i];
    }
}
       上述程式碼主要參考CTP文件編寫,比較簡單,按照文件說明,填寫正確引數,然後呼叫SubscribeMarketData()函式即可。        5. 接收行情資料:
void MarketDataSource::OnRtnDepthMarketData(CThostFtdcDepthMarketDataField *pDepthMarketData)
{
    if (pDepthMarketData != NULL)
    {
        CThostFtdcDepthMarketDataField marketData;
        memcpy(&marketData, pDepthMarketData, sizeof(CThostFtdcDepthMarketDataField));

        LOG_INFO << "行情更新:"
                 << marketData.TradingDay << " "
                 << marketData.UpdateTime << " "
                 << marketData.UpdateMillisec << " "
                 << marketData.InstrumentID << " "
                 << marketData.LastPrice << " "
                 << username_;
    }
}
       一旦合約訂閱成功,在交易時間段內,就會有行情資料來源源不斷的推送過來。上期技術文件中提到行情是每秒2條資料,這個還是比較準的。注意,這裡有一個坑,那就是在非交易時間段,經常會接收髒資料,姑且叫測試資料吧。但這個測試資料是個十幾位長的超級大浮點數,需要做好過濾,否則程式就各種異常了,甚至程式Crash。