跳转至

类型定义

  • 为了效率,系统内部所有的类型定义统一使用枚举而不是字符串,这会涉及到跨语言转换和网络传输的问题,需要有一套规则保证跨语言和网络传输的一致和高效
  • 跨语言:我们在C++会定义一套enum的数据类型,这套数据类型通过pybind导出给Python使用
  • 网络传输:在protobuf中,定义一套同样的enum数据类型,用于网络传输,保证定义的顺序一致,在传输两端可以直接进行类型的强制转换

Python Enum vs IntEnum

  • 在我们的系统中,涉及到跨语言的部分,并不直接使用Python中的enum,而是使用C++中定义的enum,通过pybind导出给Python来使用,这里只是为了明确下Python enum的行为
  • 在Python中,EnumIntEnum是两种常用的枚举类型

特性对比

特性 Enum IntEnum
name
value
Color(1)
Color["RED"]
int(xxx)
与 int 比较
< > 比较

代码示例

  • 高亮的三行是IntEnum独有的行为,我们通过pybind导出的enum行为和IntEnum一致,可以方便的和int做相互转换
from enum import Enum, IntEnum

# 定义普通 Enum
class ColorEnum(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

# 定义 IntEnum
class ColorIntEnum(IntEnum):
    RED = 1
    GREEN = 2
    BLUE = 3

# 共同特性
print(ColorEnum.RED.name)        # "RED"
print(ColorEnum.RED.value)       # 1
print(ColorEnum(1))              # ColorEnum.RED
print(ColorEnum["RED"])          # ColorEnum.RED

# IntEnum 独有特性
print(int(ColorIntEnum.RED))     # 1 (Enum 不支持)
print(ColorIntEnum.RED == 1)     # True (Enum 返回 False)
print(ColorIntEnum.RED < 2)      # True (Enum 会报错)

C++

下面以exchange为例说明类型的定义方式

enum class Exchange : uint8_t
{
    UNKNOWN = 0,
    BINANCE = 1,
    COINBASE,
    KRAKEN,
    BITSTAMP,
    OKX,
    IB,
    BYBIT,
    BINANCE_FUTURE,
    OKX_SWAP,
    KUCOIN,
    KRAKEN_FUTURE,
    BINANCE_DELIVERY,
    GATE_FUTURE,
    BYBIT_FUTURE,
    UPBIT,
    BITGET,
    BITGET_FUTURE,
    GATE,
    KUCOIN_FUTURE,
    RPC
};
  • 定义的顺序需要和C++保持一致,才可以直接使用强制类型转换
enum Exchange {
  EXCHANGE_UNKNOWN = 0;
  BINANCE = 1;
  COINBASE = 2;
  KRAKEN = 3;
  BITSTAMP = 4;
  OKX = 5;
  IB = 6;
  BYBIT = 7;
  BINANCE_FUTURE = 8;
  OKX_SWAP = 9;
  KUCOIN = 10;
  KRAKEN_FUTURE = 11;
  BINANCE_DELIVERY = 12;
  GATE_FUTURE = 13;
  BYBIT_FUTURE = 14;
  UPBIT = 15;
  BITGET = 16;
  BITGET_FUTURE = 17;
  GATE = 18;
  KUCOIN_FUTURE = 19;
  RPC = 20;
}
  • 在导出的时候,需要加py::arithmetic()才有Python中IntEnum类型的行为
namespace py = pybind11;
PYBIND11_MODULE(hft, m)
{
    py::enum_<hft::Exchange>(m, "Exchange", py::arithmetic(), "HFT Exchange")
        .value("UNKNOWN", hft::Exchange::UNKNOWN)
        .value("BINANCE", hft::Exchange::BINANCE)
        .value("COINBASE", hft::Exchange::COINBASE)
        .value("KRAKEN", hft::Exchange::KRAKEN)
        .value("BITSTAMP", hft::Exchange::BITSTAMP)
        .value("OKX", hft::Exchange::OKX)
        .value("IB", hft::Exchange::IB)
        .value("BYBIT", hft::Exchange::BYBIT)
        .value("BINANCE_FUTURE", hft::Exchange::BINANCE_FUTURE)
        .value("OKX_SWAP", hft::Exchange::OKX_SWAP)
        .value("KUCOIN", hft::Exchange::KUCOIN)
        .value("KRAKEN_FUTURE", hft::Exchange::KRAKEN_FUTURE)
        .value("BINANCE_DELIVERY", hft::Exchange::BINANCE_DELIVERY)
        .value("GATE_FUTURE", hft::Exchange::GATE_FUTURE)
        .value("BYBIT_FUTURE", hft::Exchange::BYBIT_FUTURE)
        .value("UPBIT", hft::Exchange::UPBIT)
        .value("BITGET", hft::Exchange::BITGET)
        .value("BITGET_FUTURE", hft::Exchange::BITGET_FUTURE)
        .value("GATE", hft::Exchange::GATE)
        .value("KUCOIN_FUTURE", hft::Exchange::KUCOIN_FUTURE)
        .value("RPC", hft::Exchange::RPC)
        .export_values();
    m.def("exchangeToStr", &hft::exchangeToStr, py::arg("e"));
    m.def("strToExchange", &hft::strToExchange, py::arg("str"));
}

C++ -> Python

C++通过pybind导出的类型在Python中直接使用

import hft
Exchange = hft.Exchange

exchange = Exchange.BINANCE

C++ -> Protobuf

C++的类型转换成Protobuf的类型

struct alignas(CACHE_LINE_SIZE) Ticker
{
    Exchange exchange;      // 交易所
    CurrencyPair symbol;    // 交易对
    int64_t timestamp;      // 服务器时间戳(微秒)
    int64_t localTimestamp; // 本地时间戳(微秒)
    double askPrice;        // 卖一价
    double askVolume;       // 卖一量
    double bidPrice;        // 买一价
    double bidVolume;       // 买一量
};
message Ticker {
  Exchange exchange = 1;     // 交易所
  CurrencyPair symbol = 2;   // 交易对
  int64 timestamp = 3;       // 服务器时间戳(微秒)
  int64 local_timestamp = 4; // 本地时间戳(微秒)
  double ask_price = 5;      // 卖一价
  double ask_volume = 6;     // 卖一量
  double bid_price = 7;      // 买一价
  double bid_volume = 8;     // 买一量
}
  • 由于enum定义的顺序一致,可以直接static_cast强制转换
void tickerToProto(const Ticker &ticker, proto::Ticker &protoTicker)
{
    protoTicker.set_exchange(static_cast<proto::Exchange>(ticker.exchange));
    protoTicker.set_symbol(static_cast<proto::CurrencyPair>(ticker.symbol));
    protoTicker.set_timestamp(ticker.timestamp);
    protoTicker.set_local_timestamp(ticker.localTimestamp);
    protoTicker.set_ask_price(ticker.askPrice);
    protoTicker.set_ask_volume(ticker.askVolume);
    protoTicker.set_bid_price(ticker.bidPrice);
    protoTicker.set_bid_volume(ticker.bidVolume);
}

Python

Python -> C++

  • Python和C++交互的唯一方式就是调用pybind导出的API,这些C++ API接口的参数会自动做类型转换
import hft

Exchange = hft.Exchange
exchange_to_str = hft.exchangeToStr

exchange = Exchange.BINANCE
exchange_str = exchange_to_str(exchange)

Python -> Protobuf

  • Python的类型转换成Protobuf的类型,由于我们定义的顺序一致,可以直接强制转换,效率最高
  • 由于pybind导出enum的时候增加了py::arithmetic(),导出的enum有类似IntEnum的行为,所以可以直接使用int(exchange)的方式做转换
def hft_to_pb_ticker(hft_ticker: Ticker) -> pb.Ticker:
    pb_ticker = pb.Ticker()
    pb_ticker.exchange = int(hft_ticker.exchange)
    pb_ticker.symbol = int(hft_ticker.symbol)
    pb_ticker.timestamp = hft_ticker.timestamp
    pb_ticker.local_timestamp = hft_ticker.localTimestamp
    pb_ticker.ask_price = hft_ticker.askPrice
    pb_ticker.ask_volume = hft_ticker.askVolume
    pb_ticker.bid_price = hft_ticker.bidPrice
    pb_ticker.bid_volume = hft_ticker.bidVolume
    return pb_ticker

也可以用下面的方式做转换,效率比较低,不采用。优点是对两边enum定义的顺序没有要求,可以不一致

def hft_to_pb_ticker(hft_ticker: Ticker) -> pb.Ticker:
    pb_ticker = pb.Ticker()
    pb_ticker.exchange = pb.Exchange.Value(hft_ticker.exchange.name)
    pb_ticker.symbol = pb.CurrencyPair.Value(hft_ticker.symbol.name)
    ...

Protobuf

Protobuf -> C++

  • Protobuf的类型转换成C++的类型
  • 由于enum定义的顺序一致,可以直接static_cast强制转换
void protoToTicker(const proto::Ticker &protoTicker, Ticker &ticker)
{
    ticker.exchange = static_cast<Exchange>(protoTicker.exchange());
    ticker.symbol = static_cast<CurrencyPair>(protoTicker.symbol());
    ticker.timestamp = protoTicker.timestamp();
    ticker.localTimestamp = protoTicker.local_timestamp();
    ticker.askPrice = protoTicker.ask_price();
    ticker.askVolume = protoTicker.ask_volume();
    ticker.bidPrice = protoTicker.bid_price();
    ticker.bidVolume = protoTicker.bid_volume();
}

Protobuf -> Python

  • Protobuf的类型转换成Python的类型
  • Exchange(int)的转换方式
def pb_to_hft_ticker(pb_ticker: pb.Ticker) -> Ticker:
    return Ticker(
        exchange=Exchange(pb_ticker.exchange),
        symbol=CurrencyPair(pb_ticker.symbol),
        timestamp=pb_ticker.timestamp,
        localTimestamp=pb_ticker.local_timestamp,
        askPrice=pb_ticker.ask_price,
        askVolume=pb_ticker.ask_volume,
        bidPrice=pb_ticker.bid_price,
        bidVolume=pb_ticker.bid_volume
    )