We have a low-latency C++ trading framework which allows us to deploy various trading strategies relatively easily.

Most of our traders, however, don’t know C++, which means they are often waiting on IT resource to develop and deploy a new strategy.

A lot of them do know python though, so I wanted to find a way to allow them to write the core logic of their strategies in python, whilst still leveraging our low-latency trading infrastructure

Here is my embedding python in C++ proof-of-concept.

There is nothing new here, but I found existing documentation and tutorials somewhat lacking, hence this blog post.

Step 1: Define the C++ types which represents the trading framework

An enum to represent buying or selling an order

enum Side { BUY , SELL };

Order

A struct with the details of a single order

struct Order { std :: string symbol ; Side side ; int size ; double price ; int order_id ; };

StrategyServer

A class which provides the trading functionality, such as sending an order

class StrategyServer { public: void sendOrder ( StrategyInstance & , const std :: string & symbol , Side , int size , double price ); private: int _next_order_id = 0 ; };

StrategyInstance

A pure virtual base class which represents a single strategy

Subclasses must override eval() , the entry-point into the strategy. This is where the strategy’s logic is evaluated and orders potentially sent to market.

Subclasses must also override onOrder(const Order&) which is the callback for the resulting order sent to market

class StrategyInstance { public: StrategyInstance ( StrategyServer & ); virtual ~ StrategyInstance () = default ; // evaluates the strategy's logic virtual void eval () = 0 ; // callback for an acknowledged order virtual void onOrder ( const Order & ) = 0 ; // asks the StrategyServer to send an order void sendOrder ( const std :: string & symbol , Side , int size , double price ); private: StrategyServer & _server ; };

Step 2: Create a python wrapper for our virtual StrategyInstance

Use boost::python::wrapper to allow a python class to subclass StrategyInstance

The virtual overrides look up the python class’s implementation and call those

class PyStrategyInstance final : public StrategyInstance , public bp :: wrapper < StrategyInstance > { using StrategyInstance :: StrategyInstance ; void eval () override { // call through to the python class's `eval` method get_override ( "eval" )(); } void onOrder ( const Order & order ) override { // call through to the python class's `on_order` method get_override ( "on_order" )( order ); } };

Step 3: Expose these types to python

We use BOOST_PYTHON_MODULE to define a python module containing all these types

BOOST_PYTHON_MODULE ( StrategyFramework ) { bp :: enum_ < Side > ( "Side" ) . value ( "BUY" , BUY ) . value ( "SELL" , SELL ) ; bp :: class_ < Order > ( "Order" ) . def_readonly ( "symbol" , & Order :: symbol ) . def_readonly ( "side" , & Order :: side ) . def_readwrite ( "size" , & Order :: size ) . def_readwrite ( "price" , & Order :: price ) . def_readonly ( "order_id" , & Order :: order_id ) ; bp :: class_ < StrategyServer > ( "StrategyServer" ) ; bp :: class_ < PyStrategyInstance , boost :: noncopyable > ( "StrategyInstance" , bp :: init < StrategyServer &> ()) . def ( "send_order" , & StrategyInstance :: sendOrder ) ; }

Step 4: define a function which imports a python file as a module

This is taken from the python wiki on boost::python , from the tip on loading a module by path.

What is does is allow us to specify a python file and load it as if we called import module

bp :: object import ( const std :: string & module , const std :: string & path , bp :: object & globals ) { bp :: dict locals ; locals [ "module_name" ] = module ; locals [ "path" ] = path ; bp :: exec ( "import imp

" "new_module = imp.load_module(module_name, open(path), path, ('py', 'U', imp.PY_SOURCE))

" , globals , locals ); return locals [ "new_module" ]; }

Step 5: load a python file containing our strategy and execute it

This is where we pull it all together.

We initialise the python runtime, register the python module with our C++ code in it, import the python file which contains our strategy, and then run it

int main () try { Py_Initialize (); // register the python module we created, so our script can import it PyImport_AppendInittab ( "StrategyFramework" , & initStrategyFramework ); StrategyServer server ; // import the __main__ module and obtain the globals dict bp :: object main = bp :: import ( "__main__" ); bp :: object globals = main . attr ( "__dict__" ); // import our strategy.py file bp :: object module = import ( "strategy" , "strategy.py" , globals ); // obtain the strategy class and instantiate one bp :: object Strategy = module . attr ( "Strategy" ); bp :: object strategy = Strategy ( server ); // eval the strategy strategy . attr ( "eval" )(); return 0 ; } catch ( const bp :: error_already_set & ) { std :: cerr << ">>> Error! Uncaught exception:

" ; PyErr_Print (); return 1 ; }

Step 6: define a python strategy

This is the sample strategy written in python.

As a proof of concept, all it does is send an order when the strategy is evaluated.

We print the resulting order we receive in our callback

from StrategyFramework import * class Strategy ( StrategyInstance ) : def __init__ ( self , server ) : StrategyInstance . __init__ ( self , server ) def eval ( self ) : print "strategy.eval" self . send_order ( "GOOG" , Side . BUY , 100 , 759.11 ) def on_order ( self , o ) : print "order for {} {} {}@{} has order_id={}" . format ( o . symbol , "buy" if o . side == Side . BUY else "sell" , o . size , o . price , o . order_id )

Step 7: run it!

$ ./strategy strategy.eval sending order to market order for GOOG buy 100@759.11 has order_id=1

Complete code listing:

Here is the complete code listing which you can use to test out the code yourself

main.cpp :

#include <iostream> #include <boost/python.hpp> enum Side { BUY , SELL }; struct Order { std :: string symbol ; Side side ; int size ; double price ; int order_id ; }; // forward declaration class StrategyInstance ; class StrategyServer { public: void sendOrder ( StrategyInstance & , const std :: string & symbol , Side , int size , double price ); private: int _next_order_id = 0 ; }; class StrategyInstance { public: StrategyInstance ( StrategyServer & ); virtual ~ StrategyInstance () = default ; virtual void eval () = 0 ; virtual void onOrder ( const Order & ) = 0 ; void sendOrder ( const std :: string & symbol , Side , int size , double price ); private: StrategyServer & _server ; }; /////////////////////////////////// // implementation void StrategyServer :: sendOrder ( StrategyInstance & instance , const std :: string & symbol , Side side , int size , double price ) { // simulate sending an order, receiving an acknowledgement and calling back to the strategy instance std :: cout << "sending order to market

" ; Order order { symbol , side , size , price , ++ _next_order_id }; instance . onOrder ( order ); } /////////////////////////////////// StrategyInstance :: StrategyInstance ( StrategyServer & server ) : _server ( server ) {} void StrategyInstance :: sendOrder ( const std :: string & symbol , Side side , int size , double price ) { _server . sendOrder ( * this , symbol , side , size , price ); } //////////////////////////////////// // export to python namespace bp = boost :: python ; class PyStrategyInstance final : public StrategyInstance , public bp :: wrapper < StrategyInstance > { using StrategyInstance :: StrategyInstance ; void eval () override { get_override ( "eval" )(); } void onOrder ( const Order & order ) override { get_override ( "on_order" )( order ); } }; BOOST_PYTHON_MODULE ( StrategyFramework ) { bp :: enum_ < Side > ( "Side" ) . value ( "BUY" , BUY ) . value ( "SELL" , SELL ) ; bp :: class_ < Order > ( "Order" ) . def_readonly ( "symbol" , & Order :: symbol ) . def_readonly ( "side" , & Order :: side ) . def_readwrite ( "size" , & Order :: size ) . def_readwrite ( "price" , & Order :: price ) . def_readonly ( "order_id" , & Order :: order_id ) ; bp :: class_ < StrategyServer > ( "StrategyServer" ) ; bp :: class_ < PyStrategyInstance , boost :: noncopyable > ( "StrategyInstance" , bp :: init < StrategyServer &> ()) . def ( "send_order" , & StrategyInstance :: sendOrder ) ; } bp :: object import ( const std :: string & module , const std :: string & path , bp :: object & globals ) { bp :: dict locals ; locals [ "module_name" ] = module ; locals [ "path" ] = path ; bp :: exec ( "import imp

" "new_module = imp.load_module(module_name, open(path), path, ('py', 'U', imp.PY_SOURCE))

" , globals , locals ); return locals [ "new_module" ]; } ////////////////////////////////// int main () try { Py_Initialize (); PyImport_AppendInittab ( "StrategyFramework" , & initStrategyFramework ); StrategyServer server ; bp :: object main = bp :: import ( "__main__" ); bp :: object globals = main . attr ( "__dict__" ); bp :: object module = import ( "strategy" , "strategy.py" , globals ); bp :: object Strategy = module . attr ( "Strategy" ); bp :: object strategy = Strategy ( server ); strategy . attr ( "eval" )(); return 0 ; } catch ( const bp :: error_already_set & ) { std :: cerr << ">>> Error! Uncaught exception:

" ; PyErr_Print (); return 1 ; }

strategy.py :