在Go语言中编译CTP

CTP是上期技术开发的期货交易核心系统,以极速、稳定著称。它提供高效交易执行、实时风控与行情服务,是专业投资者与期货公司首选的底层技术平台。

Golang 是由 Google 开发的开源编程语言,以简洁语法、高效并发和快速编译著称。它结合了静态语言的安全性与动态语言的灵活性,非常适合构建高性能、可扩展的网络服务与云原生应用。

CTP使用C++编程语言编写,不能在Go代码中直接调用。要想在Go代码中调用CTP,一个可行的技术方案是使用SWIG对CTP进行翻译和包装,然后使用cgo进行调用。下面对这种方法进行详细介绍。

假设工作目录为/home/netxianren/workspace/myctp/src/myctp, 该目录包含的文件如下:

error.dtd
error.xml
libthostmduserapi_se.so
libthosttraderapi_se.so
ThostFtdcMdApi.h
ThostFtdcTraderApi.h
ThostFtdcUserApiDataType.h
ThostFtdcUserApiStruct.h
go.mod
libmyctp.go
myctp.swigcxx

myctp目录共包含11个文件,前8个文件由上期技术提供,2个动态链接库文件libthostmduserapi_se.so和libthosttraderapi_se.so是在官方文件基础上改名而来,官方文件名称为thostmduserapi_se.so和thosttraderapi_se.so,分别在文件名的开头位置增加了lib 3个字母。

go.mod文件内容如下:

module myctp

go 1.21

go.mod文件内容的第1行指定了编译后的go模块的名称,第2行指定了go的版本。

libmyctp.go文件内容如下:

//go:build (linux && cgo) || (windows && cgo)
// +build linux,cgo windows,cgo

// Copyright 2012 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package myctp

/*
#cgo linux LDFLAGS: -fPIC -L${SRCDIR}/ -lthostmduserapi_se -lthosttraderapi_se -lstdc++
#cgo linux CPPFLAGS: -fPIC -I${SRCDIR}/
*/
import "C"

myctp.swigcxx文件内容如下:

/* Copyright 2011 The Go Authors.  All rights reserved.
   Use of this source code is governed by a BSD-style
   license that can be found in the LICENSE file.  */

/* An example of writing a C++ virtual function in Go.  */

%module(directors="1") myctp

%header %{
#include "ThostFtdcUserApiDataType.h"
#include "ThostFtdcUserApiStruct.h"
#include "ThostFtdcMdApi.h"
#include "ThostFtdcTraderApi.h"
%}

%init %{ 
  //printf("Initialization qerio.goctp done.\n");
%}



%typemap(gotype) (char **ppInstrumentID, int nCount) "[]string"


%typemap(in) (char **ppInstrumentID, int nCount)
%{
  {
    int i;
    _gostring_* a;

    $2 = $input.len;
    a = (_gostring_*) $input.array;
    $1 = (char **) malloc (($2 + 1) * sizeof (char *));
    for (i = 0; i < $2; i++) {
      
      /* Not work */
      //_gostring_ *ps = &a[i];
      //$1[i] = (char *) ps->p;
      //$1[i][ps->n] = '\0';

      /*Work well*/
      _gostring_ *ps = &a[i];
      $1[i] = (char*) malloc(ps->n + 1);
      memcpy($1[i], ps->p, ps->n);
      $1[i][ps->n] = '\0';

    }
    $1[i] = NULL;
  }
%}

%typemap(argout) (char **ppInstrumentID, int nCount) "" /* override char *[] default */

%typemap(freearg) (char **ppInstrumentID, int nCount)
%{
  {
    int i;
    for (i = 0; i < $2; i++)
    {
      free ($1[i]);
    }
    free($1);
  }
%}



/* Parse the header files to generate wrappers */
%include "std_string.i"

%feature("director") CThostFtdcMdSpi;
%feature("director") CThostFtdcTraderSpi;

%include "ThostFtdcUserApiDataType.h"
%include "ThostFtdcUserApiStruct.h"
%include "ThostFtdcMdApi.h"
%include "ThostFtdcTraderApi.h"

如果直接使用如下命令编译,会报错:/tmp/go-build2454150057/b002/_myctp_swig.go:20438:46: illegal rune literal

go build

原因是自动生成的Go代码中包含如下的代码。可以看出来,这段代码有问题,字符串被包含在单引号中(按照Go语法,字符串应该被包含在双引号中),而且,把字符串赋值给byte变量也有问题。

const THOST_FTDC_VTC_BankBankToFuture byte = '102001'
const THOST_FTDC_VTC_BankFutureToBank byte = '102002'
const THOST_FTDC_VTC_FutureBankToFuture byte = '202001'
const THOST_FTDC_VTC_FutureFutureToBank byte = '202002'

解决这个问题的一种方法是修改文件 ThostFtdcUserApiDataType.h中如下部分的内容,直接把这8行代码注释调。注释掉之后,编译不会再报错,但是带来的负面影响是Go代码中没有对应的常量定义,无法在Go代码中直接调用使用这些常量的函数。

/////////////////////////////////////////////////////////////////////////
///TFtdcVirementTradeCodeType是一个交易代码类型
/////////////////////////////////////////////////////////////////////////
///银行发起银行资金转期货
//#define THOST_FTDC_VTC_BankBankToFuture '102001'
///银行发起期货资金转银行
//#define THOST_FTDC_VTC_BankFutureToBank '102002'
///期货发起银行资金转期货
//#define THOST_FTDC_VTC_FutureBankToFuture '202001'
///期货发起期货资金转银行
//#define THOST_FTDC_VTC_FutureFutureToBank '202002'

......

/////////////////////////////////////////////////////////////////////////
///TFtdcFBTTradeCodeEnumType是一个银期交易代码枚举类型
/////////////////////////////////////////////////////////////////////////
///银行发起银行转期货
//#define THOST_FTDC_FTC_BankLaunchBankToBroker '102001'
///期货发起银行转期货
//#define THOST_FTDC_FTC_BrokerLaunchBankToBroker '202001'
///银行发起期货转银行
//#define THOST_FTDC_FTC_BankLaunchBrokerToBank '102002'
///期货发起期货转银行
//#define THOST_FTDC_FTC_BrokerLaunchBrokerToBank '202002'

文件 ThostFtdcMdApi.h 部分内容也需要修改。在C++中,变量类型char *ppInstrumentID[]和char **ppInstrumentID没有任何区别,但是变量类型char *ppInstrumentID[]被包装后在Go代码中编译会出错,变量类型char **ppInstrumentID在Go代码中编译可以通过。

///订阅行情。
	///@param ppInstrumentID 合约ID  
	///@param nCount 要订阅/退订行情的合约个数
	///@remark 
	//virtual int SubscribeMarketData(char *ppInstrumentID[], int nCount) = 0;
        virtual int SubscribeMarketData(char **ppInstrumentID, int nCount) = 0;

	///退订行情。
	///@param ppInstrumentID 合约ID  
	///@param nCount 要订阅/退订行情的合约个数
	///@remark 
	//virtual int UnSubscribeMarketData(char *ppInstrumentID[], int nCount) = 0;
        virtual int UnSubscribeMarketData(char **ppInstrumentID, int nCount) = 0;
	
	///订阅询价。
	///@param ppInstrumentID 合约ID  
	///@param nCount 要订阅/退订行情的合约个数
	///@remark 
	//virtual int SubscribeForQuoteRsp(char *ppInstrumentID[], int nCount) = 0;
        virtual int SubscribeForQuoteRsp(char **ppInstrumentID, int nCount) = 0;

	///退订询价。
	///@param ppInstrumentID 合约ID  
	///@param nCount 要订阅/退订行情的合约个数
	///@remark 
	//virtual int UnSubscribeForQuoteRsp(char *ppInstrumentID[], int nCount) = 0;
        virtual int UnSubscribeForQuoteRsp(char **ppInstrumentID, int nCount) = 0;

还有一些其他的依赖项。我的电脑上,软件配置如下:

  • 操作系统:ubuntu 22.04
  • Go 版本:1.21.1
  • SWIG 版本:4.0.2
  • g++版本: 11.4.0

一切准备就绪后,可以使用如下命令编译:

export GOPATH=$GOPATH:'/home/netxianren/workspace/myctp'
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:'/home/netxianren/workspace/myctp/src/myctp'
cd /home/netxianren/workspace/myctp/src/myctp
go install -x -v -work

也可以在LiteIDE集成工具中直接点击Build按钮进行编译。

使用上述办法,本人已成功编译CTP 6.3.15, CTP 6.3.16, CTP 6.5.1, CTP 6.7.13 四个版本。

在LiteIDE中的自动补全功能

若要在LiteIDE里实现代码自动补全功能,需要在包目录/home/netxianren/workspace/myctp/src/myctp里放一个文件myctp.go(由swig生成),但是这个文件会导致编译时时间很长。
解决办法为:编译时修改这个文件的扩展名,编译完成后扩展名再改为go.

生成myctp.go的命令如下:

swig -c++ -go -cgo -intgosize 64 myctp.swigcxx

部署时指定加载动态链接库的路径

正常部署时,将动态链接库文件libthostmduserapi_se.so和libthosttraderapi_se.so复制到系统库文件目录即可,在ubuntu系统上,通常是系统库文件目录/usr/lib。

但是有时候,我们不希望从系统库文件目录加载,而是从可执行程序文件所在的目录加载动态链接库,这样可以实现同一台电脑上部署的多个可执行程序加载不同的CTP版本。

这个可以通过修改LD_LIBRARY_PATH来实现。假设需要加载CTP动态链接库的可执行程序为future, 可执行程序future所在目录为/var,可以使用如下命令启动future, 此时future会优先从/var目录加载动态链接库文件libthostmduserapi_se.so和libthosttraderapi_se.so,将文件libthostmduserapi_se.so和libthosttraderapi_se.so复制到/var即可保证成功加载。

LD_LIBRARY_PATH='/var':$LD_LIBRARY_PATH /var/future

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部