Understand the design principles of CTP futures quantitative trading API from the nature of virtual functions

If you could sum up the function of CTP API in one sentence, it would be “Sending the client’s request information to the server, and executing the business processing logic customized by the client developer when receiving the information returned by the server “.

The development language of CTP API is C++, which is a strongly typed language and requires all types to be determined at compile time. There are two problems that need to be solved: first, the API function implementation logic needs to be encapsulated in a library file, because it is neither safe nor friendly to client developers if exposed; second, in The compiled library file must be able to call the business processing logic that is implemented during client development.

CTP API takes advantage of the property of virtual functions penetrating downwards along the inheritance chain of classes, exposing the two base classes CThostFtdcTraderAp and CThostFtdcTraderSpi The statement realizes the isolation of API functional modules and Client-side custom business processing modules.

For API functional modules, the type of client-defined business processing module is CThostFtdcTraderSpi, and you can use this type to call business processing logic. Correspondingly, for the Client-side custom business processing module, the API function module type is CThostFtdcTraderApi, and you can use this type to call the API function.

Next, let’s take a look at what virtual functions are and how virtual functions penetrate downward.

A function preceded by the virtual keyword is a virtual function. The function of a virtual function is to penetrate down to the implementation logic in the subclass when calling a function using a pointer of the parent class type. Of course, only by instantiating the subclass first, and then converting the subclass pointer into a pointer of the parent class type, can the parent class type pointer call a virtual function to penetrate the implementation of the subclass. If the parent class is instantiated directly and the virtual function is called using the pointer of the parent class instance, the implementation in the parent class is still executed.

If a virtual function is assigned a value of 0 when it is declared, it becomes a pure virtual function. The value is 0 because no implementation is required here. . A class containing pure virtual functions is a pure virtual class, which is usually used as an interface, so it is also called an interface class. Because the pure virtual functions declared in pure virtual classes are not implemented, pure virtual classes cannot be instantiated directly, so they are also called abstract classes.

All pure virtual functions must be implemented by a subclass that inherits the pure virtual class before they can be used normally. First instantiate the subclass that implements all pure virtual functions, and then convert the pointer of the subclass into a pointer of the base class type. You can use the pointer of the base class type to call the functions implemented in the subclass.

In summary, the base class achieves the purpose of encapsulating subclasses through (pure) virtual functions. When different modules interact with each other, they only need to use pointers of base class types to call the required functions. There is no need to care about the specific code, thus realizing the isolation of codes between different modules.

1. CThostFtdcTraderApi

Taking CTP’s trading API as an example, CThostFtdcTraderApi is the exposed base class declaration. There is a subclass in the library file (dll/so) that implements all pure virtual function functions. Client developers do not know the specific content of this subclass, but they can pass a base class type The pointer of CThostFtdcTraderApi calls the API function implemented in this subclass.

In the initialization phase, first use CreateFtdcTraderApi to obtain a CThostFtdcTraderApi type pointer.

You can imagine the process of generating this pointer: first, instantiate the subclass that implements the API function module in the library file. This subclass inherits the base class CThostFtdcTraderApi and implements the functions of all pure virtual functions; then, Convert the subclass pointer to a pointer of base class type CThostFtdcTraderApi, and the base class type pointer is returned as the result. The client can use this return value to call the API function module.

The virtual function declared by the base class CreateFtdcTraderApi ensures that the pointer of the base class type can penetrate into the function code of the subclass and be executed. For example, the client authentication request function is declared in the CThostFtdcTraderApi class as:

class CThostFtdcTraderApi
{<!-- -->
///Client authentication request
virtual int ReqAuthenticate(CThostFtdcReqAuthenticateField *pReqAuthenticateField, int nRequestID) = 0;

};

This is a pure virtual function. In the following code, when OnFrontConnected is used, m_pTraderApi is used to execute ReqAuthenticate to send the client authentication request to the server. Among them, m_pTraderApi is a pointer of type CThostFtdcTraderApi, which is obtained through CreateFtdcTraderApi during the initialization phase.

void CTraderSpiImpl::OnFrontConnected()
{<!-- -->
CThostFtdcReqAuthenticateField authenticateField = CThostFtdcReqAuthenticateField();
memset( & amp;authenticateField, 0, sizeof(CThostFtdcReqAuthenticateField));

strcpy(authenticateField.BrokerID, "xxx");
strcpy(authenticateField.UserID, "xxx");
strcpy(authenticateField.AuthCode, "xxx");
strcpy(authenticateField.AppID, "xxx");

int nErrorID = m_pTraderApi->ReqAuthenticate( & amp;authenticateField, 0);
if (nErrorID != 0)
{<!-- -->
        /// Failed to send
}
}

2. CThostFtdcTraderSpi

The situation with CThostFtdcTraderSpi is similar. Client developers can implement a subclass, for example named CTraderSpiImpl, to overload the involved business response functions to implement specific business processing logic.

After the subclass CTraderSpiImpl is instantiated, it is converted into a pointer of the base class type CThostFtdcTraderSpi, and RegisterSpi is registered to the instance of the API function module.

Judging from the order of implementation, the API worker thread comes first, and the client business response processing logic comes last. It is the base class CThostFtdcTraderSpi that uses virtual functions when declared, and the client business processing logic that is implemented later can only be called in the API worker thread that is implemented first.

The following code demonstrates how SPI is registered into the API:

CThostFtdcTraderApi* m_pTraderApi;
m_pTraderApi = CThostFtdcTraderApi::CreateFtdcTraderApi(strFlowPath.c_str());

CTraderSpiImpl* pTraderSpi = new CTraderSpiImpl();
m_pTraderApi->RegisterSpi(pTraderSpi);

When the API function module calls the client business processing module, it uses the base class type CThostFtdcTraderSpi, which is the pointer registered in the API by RegisterSpi.

When receiving information, the API worker thread uses this pointer to execute the relevant SPI function. For example, the definition of OnFrontConnected given earlier. When receiving the OnFrontConnected message, the API worker thread executes the function defined by the client. In the example, it issues an authentication request.

The response function defined in CThostFtdcTraderSpi is not a pure virtual function and has a default implementation, an empty function. Therefore, CThostFtdcTraderSpi can be instantiated, but this is meaningless because it does not perform any operations by default. Subclasses do not need to re-implement all response functions, they only need to implement business-related responses. When responding to irrelevant business, the default empty function is called.

The functions declared in CThostFtdcTraderSpi all start with On, indicating that they are all callback functions. From the naming rules, it can be subdivided into the following categories:

  • OnRspQry/OnRspQuery, the callback function that responds to the query, corresponds to a query request ReqQry/ReqQuery. Only the session connection that initiated the request can be received.

  • OnRsp, a callback function that responds to non-query business, corresponds to a non-query business request Req. Only the session connection that initiated the request can be received.

  • OnRtn/OnErrRtn, the callback when receiving information actively pushed by the server, is also called a notification. All session connections are received.

In different scenarios, we may mention Callback, Response, Notification, Push, Words such as triggerexpress that the API worker thread executes the business processing logic defined by the client when it receives information.