信号路由风暴:Qt算法交易系统的高频信号分发架构

副标题:从策略信号产生到执行端分发,构建万级/秒信号路由的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;
};

八、总结

高频信号路由的核心挑战是:在万级/秒吞吐量下保持微秒级延迟。本文给出的架构方案:

  1. 信号总线 :用std::function替代Qt信号槽,性能提升2-3倍
  2. 优先级队列:Urgent信号直通,确保止损等关键信号零延迟
  3. 背压控制:高水位触发丢弃策略,防止内存溢出
  4. 无锁队列:SPSC环形缓冲区消除锁竞争,多队列分流热点
  5. 可观测性:纳秒级延迟统计,按标的维度监控路由性能

信号路由不是简单的转发------它是交易系统延迟的守门人。


《注:若有发现问题欢迎大家提出来纠正》

以上仅为技术分享参考,不构成投资建议

相关推荐
阿文的代码库4 小时前
一文读懂GROUP BY 1,2 VS GROUP BY column_1, column_2 的区别
算法
2301_780789664 小时前
手游遇到攻击为什么要用SDK游戏盾手游遇到攻击为什么要用 SDK 游戏盾?
安全·web安全·游戏·架构·kubernetes·ddos
008爬虫实战录5 小时前
【码上爬】 题十:魔改算法 堆栈分析,找加密值过程详解
前端·python·算法
chao1898445 小时前
基于狮蚁群算法(ALO)的火电机组功能调度实现
人工智能·算法
Deep-w5 小时前
【MATLAB】含光伏 - 储能的家庭/工业微电网能量管理仿真研究
开发语言·算法·matlab
中小企业实战军师刘孙亮5 小时前
小微企业生存发展指南:从求稳到扩张的实战策略-佛山鼎策创局破局增长咨询
架构·产品运营·音视频·制造·业界资讯
阿文的代码库5 小时前
换根技巧实例分析:最小高度树
算法·动态规划
dyxal5 小时前
Louvain 算法:让网络自己“报团取暖”的发现者
开发语言·算法
计算机安禾5 小时前
【c++面向对象编程】第41篇:函数模板与类模板:泛型编程的基石
开发语言·c++·算法