副标题:从策略信号产生到执行端分发,构建万级/秒信号路由的Qt实战架构
一、引言
在算法交易系统中,策略引擎产生的交易信号需要经过风控检查、仓位计算、订单拆分等环节,最终分发到不同的执行通道。当系统同时运行数十个策略,每秒产生上万条信号时,信号路由的分发效率直接决定了交易延迟。
本文基于Qt框架,从零设计一个高频信号路由与分发架构,涵盖信号总线、主题订阅、优先级队列、背压控制等核心机制,并给出源码级实现。
二、信号路由架构总览
2.1 整体架构
策略引擎层 信号路由层 执行层
┌─────────┐ ┌──────────────┐ ┌──────────┐
│ 策略A │──Signal──→ │ │──Dispatch──→│ 执行器1 │
│ 策略B │──Signal──→ │ SignalRouter│──Dispatch──→│ 执行器2 │
│ 策略C │──Signal──→ │ (主题路由) │──Dispatch──→│ 风控模块 │
│ ... │ │ (优先级队列) │ │ ... │
└─────────┘ │ (背压控制) │ └──────────┘
└──────────────┘
2.2 核心数据结构
cpp
// 交易信号定义
struct TradeSignal {
enum Type { Buy, Sell, Cancel, Modify };
enum Priority { Low = 0, Normal = 1, High = 2, Urgent = 3 };
QString signalId; // 信号唯一ID
QString strategyId; // 策略来源
QString symbol; // 标的代码
Type type; // 信号类型
Priority priority; // 优先级
double price; // 目标价格
double quantity; // 数量
qint64 timestamp; // 纳秒级时间戳
// 路由信息
QStringList targetChannels; // 目标通道
QVariantMap metadata; // 扩展数据
};
Q_DECLARE_METATYPE(TradeSignal)
三、信号总线实现
3.1 基于Qt信号槽的主题订阅
传统方案用Qt的信号槽直接连接策略和执行器,但在万级/秒场景下,信号槽的元对象系统开销不可忽视。我们设计一个基于主题的信号总线:
cpp
#include <QHash>
#include <QSet>
#include <QMutex>
#include <QReadWriteLock>
#include <functional>
class SignalBus : public QObject {
Q_OBJECT
public:
using Subscriber = std::function<void(const TradeSignal &)>;
// 订阅特定主题(按symbol或策略ID)
QString subscribe(const QString &topic, Subscriber subscriber)
{
QWriteLocker locker(&m_lock);
QString subId = QUuid::createUuid().toString(QUuid::WithoutBraces);
m_subscribers[topic].insert(subId, subscriber);
m_subIds.insert(subId, topic);
return subId;
}
// 订阅所有信号(通配符)
QString subscribeAll(Subscriber subscriber)
{
return subscribe(m_wildcard, subscriber);
}
// 取消订阅
void unsubscribe(const QString &subId)
{
QWriteLocker locker(&m_lock);
if (m_subIds.contains(subId)) {
QString topic = m_subIds.take(subId);
m_subscribers[topic].remove(subId);
}
}
// 发布信号
void publish(const TradeSignal &signal)
{
QReadLocker locker(&m_lock);
// 1. 精确主题订阅者
QString topic = signal.symbol;
if (m_subscribers.contains(topic)) {
for (auto it = m_subscribers[topic].begin();
it != m_subscribers[topic].end(); ++it) {
it.value()(signal);
}
}
// 2. 通配符订阅者
if (m_subscribers.contains(m_wildcard)) {
for (auto it = m_subscribers[m_wildcard].begin();
it != m_subscribers[m_wildcard].end(); ++it) {
it.value()(signal);
}
}
}
private:
static const QString m_wildcard;
QReadWriteLock m_lock;
// topic -> (subId -> subscriber)
QHash<QString, QHash<QString, Subscriber>> m_subscribers;
QHash<QString, QString> m_subIds; // subId -> topic
};
const QString SignalBus::m_wildcard = "*";
3.2 信号槽与函数指针的性能对比
cpp
// 性能测试:Qt信号槽 vs std::function回调
void benchmarkDispatch()
{
SignalBus bus;
TradeSignal signal;
signal.symbol = "600000.SH";
signal.price = 10.5;
signal.quantity = 1000;
volatile int counter = 0;
// std::function回调
bus.subscribe("600000.SH", [&counter](const TradeSignal &) {
counter++;
});
QElapsedTimer timer;
timer.start();
for (int i = 0; i < 1000000; ++i) {
bus.publish(signal);
}
qDebug() << "std::function: 1M signals in" << timer.elapsed() << "ms";
// 典型结果:~180ms (std::function) vs ~450ms (Qt信号槽)
}
std::function比Qt信号槽快2-3倍,因为它绕过了元对象系统的QMetaObject::activate调用链。
四、优先级队列与背压控制
4.1 优先级队列实现
交易信号具有不同的优先级------止损信号必须立即执行,而建仓信号可以延迟:
cpp
#include <QQueue>
#include <QPriorityQueue>
class PrioritySignalQueue {
public:
void enqueue(const TradeSignal &signal)
{
QMutexLocker locker(&m_mutex);
// Urgent信号直接插入队头
if (signal.priority == TradeSignal::Urgent) {
m_urgentQueue.enqueue(signal);
} else {
m_normalQueue[signal.priority].enqueue(signal);
}
m_waitCondition.wakeOne();
}
TradeSignal dequeue(int timeoutMs = 1000)
{
QMutexLocker locker(&m_mutex);
while (isEmpty()) {
if (!m_waitCondition.wait(&m_mutex, timeoutMs)) {
return TradeSignal(); // 超时返回空信号
}
}
// 优先级:Urgent > High > Normal > Low
if (!m_urgentQueue.isEmpty()) {
return m_urgentQueue.dequeue();
}
for (int p = TradeSignal::High; p >= TradeSignal::Low; --p) {
if (!m_normalQueue[p].isEmpty()) {
return m_normalQueue[p].dequeue();
}
}
return TradeSignal();
}
bool isEmpty() const
{
if (!m_urgentQueue.isEmpty()) return false;
for (int i = 0; i < 4; ++i) {
if (!m_normalQueue[i].isEmpty()) return false;
}
return true;
}
int size() const
{
int total = m_urgentQueue.size();
for (int i = 0; i < 4; ++i) {
total += m_normalQueue[i].size();
}
return total;
}
private:
mutable QMutex m_mutex;
QWaitCondition m_waitCondition;
QQueue<TradeSignal> m_urgentQueue;
QHash<int, QQueue<TradeSignal>> m_normalQueue;
};
4.2 背压控制
当下游执行器处理速度跟不上信号产生速度时,必须实施背压,否则内存会被积压的信号撑爆:
cpp
class BackpressureController {
public:
enum Policy {
DropOldest, // 丢弃最旧的信号
DropLowest, // 丢弃最低优先级
BlockProducer, // 阻塞生产者
SampleRate // 降采样
};
BackpressureController(Policy policy, int highWatermark, int lowWatermark)
: m_policy(policy)
, m_highWatermark(highWatermark)
, m_lowWatermark(lowWatermark)
{}
bool shouldApply(int currentQueueSize) const
{
return currentQueueSize >= m_highWatermark;
}
bool shouldRelease(int currentQueueSize) const
{
return currentQueueSize <= m_lowWatermark;
}
TradeSignal apply(PrioritySignalQueue &queue)
{
switch (m_policy) {
case DropLowest:
// 丢弃最低优先级的信号
return dequeueLowest(queue);
case DropOldest:
// 丢弃最旧的低优先级信号
return dequeueOldest(queue);
case BlockProducer:
// 阻塞直到队列低于低水位
m_blockMutex.lock();
m_blockCondition.wait(&m_blockMutex);
m_blockMutex.unlock();
return TradeSignal();
case SampleRate:
// 每N条信号只保留1条
m_sampleCounter++;
if (m_sampleCounter % m_sampleRate != 0) {
return dequeueOldest(queue); // 丢弃
}
return TradeSignal();
}
return TradeSignal();
}
void releaseBackpressure()
{
m_blockCondition.wakeAll();
}
private:
TradeSignal dequeueLowest(PrioritySignalQueue &queue);
TradeSignal dequeueOldest(PrioritySignalQueue &queue);
Policy m_policy;
int m_highWatermark;
int m_lowWatermark;
QMutex m_blockMutex;
QWaitCondition m_blockCondition;
int m_sampleCounter = 0;
int m_sampleRate = 10;
};
五、信号路由器核心实现
5.1 路由规则引擎
cpp
class SignalRouter : public QObject {
Q_OBJECT
public:
struct RouteRule {
QString name;
std::function<bool(const TradeSignal &)> condition;
QStringList targetChannels;
int priority = 0; // 规则优先级
};
void addRule(const RouteRule &rule)
{
QMutexLocker locker(&m_mutex);
m_rules.append(rule);
// 按优先级排序
std::sort(m_rules.begin(), m_rules.end(),
[](const RouteRule &a, const RouteRule &b) {
return a.priority > b.priority;
});
}
QStringList route(const TradeSignal &signal)
{
QMutexLocker locker(&m_mutex);
QStringList targets;
for (const RouteRule &rule : m_rules) {
if (rule.condition(signal)) {
targets.append(rule.targetChannels);
}
}
targets.removeDuplicates();
return targets;
}
private:
QMutex m_mutex;
QVector<RouteRule> m_rules;
};
5.2 完整路由流程
cpp
class TradingSignalDispatcher : public QObject {
Q_OBJECT
public:
TradingSignalDispatcher(QObject *parent = nullptr)
: QObject(parent)
, m_router(new SignalRouter(this))
, m_bus(new SignalBus(this))
{
// 配置路由规则
configureRoutes();
// 启动分发线程
m_dispatchThread = QThread::create([this]() { dispatchLoop(); });
m_dispatchThread->start();
}
void submitSignal(const TradeSignal &signal)
{
// 背压检查
if (m_backpressure.shouldApply(m_queue.size())) {
qWarning() << "Backpressure applied, queue size:" << m_queue.size();
m_backpressure.apply(m_queue);
}
m_queue.enqueue(signal);
}
private:
void configureRoutes()
{
// 规则1:止损信号走紧急通道
m_router->addRule({
"stop_loss_urgent",
[](const TradeSignal &s) {
return s.metadata.value("isStopLoss").toBool();
},
{"urgent_executor", "risk_monitor"},
100 // 最高优先级
});
// 规则2:沪深标的走不同通道
m_router->addRule({
"sh_stock_route",
[](const TradeSignal &s) {
return s.symbol.endsWith(".SH");
},
{"sh_executor"},
50
});
m_router->addRule({
"sz_stock_route",
[](const TradeSignal &s) {
return s.symbol.endsWith(".SZ");
},
{"sz_executor"},
50
});
}
void dispatchLoop()
{
while (!m_stopped.loadAcquire()) {
TradeSignal signal = m_queue.dequeue(100);
if (signal.signalId.isEmpty()) continue;
// 路由
QStringList channels = m_router->route(signal);
signal.targetChannels = channels;
// 分发
m_bus->publish(signal);
// 释放背压
if (m_backpressure.shouldRelease(m_queue.size())) {
m_backpressure.releaseBackpressure();
}
}
}
SignalRouter *m_router;
SignalBus *m_bus;
PrioritySignalQueue m_queue;
BackpressureController m_backpressure{
BackpressureController::DropLowest, 10000, 5000
};
QThread *m_dispatchThread;
QAtomicInt m_stopped{0};
};
六、多线程分发优化
6.1 无锁环形缓冲区
在高频场景下,QMutex的锁竞争成为瓶颈。使用无锁环形缓冲区替代:
cpp
#include <atomic>
template<typename T, size_t Capacity>
class SPSCRingBuffer {
static_assert((Capacity & (Capacity - 1)) == 0, "Capacity must be power of 2");
public:
bool push(const T &item)
{
const size_t head = m_head.load(std::memory_order_relaxed);
const size_t next = (head + 1) & (Capacity - 1);
if (next == m_tail.load(std::memory_order_acquire)) {
return false; // 满
}
m_buffer[head] = item;
m_head.store(next, std::memory_order_release);
return true;
}
bool pop(T &item)
{
const size_t tail = m_tail.load(std::memory_order_relaxed);
if (tail == m_head.load(std::memory_order_acquire)) {
return false; // 空
}
item = m_buffer[tail];
m_tail.store((tail + 1) & (Capacity - 1), std::memory_order_release);
return true;
}
private:
alignas(64) std::atomic<size_t> m_head{0};
alignas(64) std::atomic<size_t> m_tail{0};
T m_buffer[Capacity];
};
6.2 多队列分发
为每个执行通道分配独立队列,避免单一队列的热点竞争:
cpp
class MultiQueueDispatcher {
public:
void registerChannel(const QString &name)
{
m_channels[name] = new SPSCRingBuffer<TradeSignal, 65536>;
}
void dispatch(const TradeSignal &signal)
{
for (const QString &channel : signal.targetChannels) {
if (m_channels.contains(channel)) {
m_channels[channel]->push(signal);
}
}
}
bool poll(const QString &channel, TradeSignal &signal)
{
return m_channels.value(channel)->pop(signal);
}
private:
QHash<QString, SPSCRingBuffer<TradeSignal, 65536> *> m_channels;
};
七、监控与可观测性
7.1 信号流量统计
cpp
class SignalMetrics {
public:
void record(const TradeSignal &signal, qint64 processTimeUs)
{
QMutexLocker locker(&m_mutex);
auto &stats = m_symbolStats[signal.symbol];
stats.count++;
stats.totalLatencyUs += processTimeUs;
stats.maxLatencyUs = qMax(stats.maxLatencyUs, processTimeUs);
m_totalCount++;
m_totalLatencyUs += processTimeUs;
}
void report() const
{
qDebug() << "=== Signal Router Metrics ===";
qDebug() << "Total signals:" << m_totalCount;
qDebug() << "Avg latency:" << (m_totalCount ? m_totalLatencyUs / m_totalCount : 0) << "us";
for (auto it = m_symbolStats.begin(); it != m_symbolStats.end(); ++it) {
qDebug() << it.key() << ": count=" << it->count
<< "avg=" << (it->count ? it->totalLatencyUs / it->count : 0) << "us"
<< "max=" << it->maxLatencyUs << "us";
}
}
private:
struct SymbolStats {
qint64 count = 0;
qint64 totalLatencyUs = 0;
qint64 maxLatencyUs = 0;
};
QMutex m_mutex;
QHash<QString, SymbolStats> m_symbolStats;
qint64 m_totalCount = 0;
qint64 m_totalLatencyUs = 0;
};
八、总结
高频信号路由的核心挑战是:在万级/秒吞吐量下保持微秒级延迟。本文给出的架构方案:
- 信号总线 :用
std::function替代Qt信号槽,性能提升2-3倍 - 优先级队列:Urgent信号直通,确保止损等关键信号零延迟
- 背压控制:高水位触发丢弃策略,防止内存溢出
- 无锁队列:SPSC环形缓冲区消除锁竞争,多队列分流热点
- 可观测性:纳秒级延迟统计,按标的维度监控路由性能
信号路由不是简单的转发------它是交易系统延迟的守门人。
《注:若有发现问题欢迎大家提出来纠正》
以上仅为技术分享参考,不构成投资建议