量化交易系统技术方案设计

✅ 策略实例生命周期阶段

  1. 创建(Created)
    • 用户创建策略配置(如币种、价格范围、策略类型等)
    • 状态:未运行,存储在数据库中
  2. 启动(Running)
    • 系统调度策略模板 + 参数生成策略实例
    • 开始监听行情、风控、交易信号等
  3. 暂停(Paused)
    • 用户主动暂停或系统触发暂停(如断网、限仓)
    • 停止执行交易逻辑,但保留状态和内存
  4. 恢复(Resumed)
    • 恢复已暂停策略的运行状态
  5. 终止(Stopped)
    • 用户主动终止或策略运行结束(比如止盈止损)
    • 完全释放资源,写入执行日志
  6. 异常(Error)
    • 策略在运行中出现异常,如数据丢失、风控失败等

✅ Java 中如何管理这些生命周期?

可以用一个策略实例管理器来统一调度,比如:

public class StrategyInstanceManager {
    private final Map<String, StrategyRunner> activeStrategies = new ConcurrentHashMap<>();

    public void startStrategy(String strategyId, StrategyConfig config) {
        if (activeStrategies.containsKey(strategyId)) return;

        StrategyContext context = new StrategyContext(config);
        Strategy strategy = StrategyFactory.createStrategy(config.getType(), context);
        StrategyRunner runner = new StrategyRunner(strategy);

        activeStrategies.put(strategyId, runner);
        runner.start(); // 启动线程或调度器
    }

    public void stopStrategy(String strategyId) {
        StrategyRunner runner = activeStrategies.remove(strategyId);
        if (runner != null) runner.stop();
    }

    public void pauseStrategy(String strategyId) {
        StrategyRunner runner = activeStrategies.get(strategyId);
        if (runner != null) runner.pause();
    }

    public void resumeStrategy(String strategyId) {
        StrategyRunner runner = activeStrategies.get(strategyId);
        if (runner != null) runner.resume();
    }
}

✅ 策略状态的持久化与恢复(可选)

  • 每个策略状态可以保存在 Redis 或数据库中;
  • 系统重启时从持久化层恢复;
  • 某些策略(如马丁格尔)需要恢复中间状态,如持仓数量、上次下单价格等。

✅ 本地内存 + Redis 协同方案

✅ Redis 在量化交易系统中的适用场景

  1. 策略配置缓存共享
    • 如果有多个节点,策略配置(如策略参数、交易对、限额)可以存储在 Redis 中,做到共享。
    • 例如:
      Key: strategy:config:strategyId123
      Value: { "type": "Grid", "symbol": "ETH/USDT", "gridCount": 20, ... }
      
  2. 策略状态同步
    • 如果一个策略实例需要在多个节点之间迁移或恢复,可以把它的运行状态保存在 Redis(如当前仓位、最近成交价等)。
    • 避免单节点崩溃导致状态丢失。
  3. 分布式协调与调度
    • 用 Redis 锁来协调多个实例之间的抢占式策略调度。
    • 也可用于 Leader 选举、策略分片元数据存储等。
  4. 订阅推送
    • 可以用 Redis 的 Pub/Sub 模块做行情推送、策略热更新等通知类广播。

❌ 不推荐的用途:

  1. 策略对象存储(如 StrategyInstance)
    • Redis 是键值型缓存系统,不适合存储复杂、有状态、可执行的 Java 对象(如含线程、回调逻辑)。
    • Java 的策略对象应始终保存在本地内存中,只有其**状态(持仓、参数、结果)**可以同步到 Redis。
  2. 高频调度核心逻辑
    • Redis 是远程访问,虽然快,但网络调用 + 序列化还是比内存慢几个数量级。
    • 高频策略触发、价格推送后的策略执行等必须本地完成,Redis 不能做中间调度层。

✅ 最佳实践:本地内存 + Redis 协同方案

类型存储位置用途
策略逻辑代码本地(Java 类)执行策略逻辑
策略运行实例本地内存快速响应行情,保存运行上下文
策略配置Redis(+DB)多节点共享加载,启动时拉取
策略状态快照Redis(或 DB)恢复、监控、同步
行情/通知Redis Pub/Sub广播新行情,热更新策略
任务分配元数据Redis 哈希多节点负载协调

Redis 是分布式缓存的好帮手,但不是实时策略执行的核心容器。

  • ✅ 适合存配置、状态、通信。
  • ❌ 不适合存运行中策略对象或执行逻辑。

量化交易系统如果是分布式部署的,本地缓存确实会遇到几个关键问题:

❗ 本地缓存的问题(在分布式部署中)

  1. 策略数据不一致:不同节点各自持有本地缓存,策略更新、下线、修改参数时很难同步。
  2. 节点漂移问题:Kubernetes 等平台可能动态扩缩容,导致策略实例在某节点突然失效或迁移。
  3. 热更新困难:无法集中控制策略的启停、调整,必须广播通知所有节点。
  4. 内存负担大:每个节点可能需要缓存全部策略实例,造成资源浪费。

✅ 使用分布式缓存(如 Redis)的可行方案

👉 建议使用 分布式缓存 + 本地运行态解耦 的设计模式:

模块描述
Redis 分布式缓存存储策略配置、参数、分配信息、状态快照。统一读写。
本地内存仅保存当前节点所调度的部分策略的“运行时实例对象”(如 Java 类)。
策略注册表(Redis 哈希)记录哪个策略运行在哪个节点,由中心调度器维护。
策略负载中心自动将策略按规则分片下发给节点(例如使用一致性哈希或分片算法)。
Redis Pub/Sub用于广播行情、策略更新、重新调度等事件。

🔧 示例架构(简化):

[ MySQL ]  <--持久化-->
   |
[ Redis ]
   | (配置/状态/策略注册表)
   ↓
[调度中心](统一分配策略给各节点)
   |
   |--> 节点A:[持有 5万个策略实例,运行中,本地缓存]
   |--> 节点B:[持有 5万个策略实例,运行中,本地缓存]
   |
   +--> Redis Pub/Sub:行情/指令广播

✅ 如何动态调度 + 分片?

  • 使用 Redis 存储每个策略的分片信息(策略ID → 所属节点)
  • 每个节点仅从 Redis 加载自己负责的策略
  • 新节点加入或旧节点宕机时,重新分配策略并通知更新缓存
  • 可参考:一致性哈希 / 分段式平均调度 / 分布式调度框架(如 Apache ShardingSphere)

🚫 直接把策略实例对象放入 Redis 是不行的

  • Java 的策略类对象(包含业务逻辑和线程)不能存入 Redis(Redis 不支持 Java 对象图)
  • 所以:Redis 存配置 + 本地运行状态对象 才是标准做法

✅ 最佳实践

内容存储方式
策略配置 / 参数Redis(可落盘到 MySQL)
策略运行实例本地内存,仅运行该策略的节点持有
策略状态 / 中间结果Redis(或数据库)
策略分配元数据Redis 哈希或 ZSet
策略事件通知Redis Pub/Sub / Kafka / MQ

这种 “本地运行 + 分布式缓存协调” 的架构正是当今多数大型虚拟货币交易所量化平台策略托管服务中采用的主流设计方案。

✅ 为什么是主流?

🚀 满足性能 + 扩展性的双重要求:

需求解决方案
高频行情触发,低延迟策略执行使用本地内存缓存策略实例,避免远程 I/O
策略数据共享、多节点部署用 Redis(或 etcd/ZooKeeper)集中管理配置、状态和调度元数据
自动扩缩容、弹性部署支持 Kubernetes 等环境动态添加或移除节点,同时通过分布式协调机制(如 Redis 哈希表或一致性哈希)分配策略
策略热更新、灰度发布使用 Redis Pub/Sub 或消息队列广播策略更新事件,各节点实时同步
容灾与恢复能力强策略状态定期快照至 Redis 或数据库,支持容器或节点宕机恢复

✅ 实际案例参考

交易平台 / 框架使用模式
Binance、OKX、Coinbase使用微服务架构,行情与策略执行解耦,多语言组合部署
OneQuant、SuperQuant提供策略托管服务,用 Redis + Kubernetes + 本地内存做分布式策略调度
国内某大型交易平台(保密)将策略状态缓存在 Redis,策略对象驻留本地,通过 MQ 做行情广播与策略更新

🚫 非主流的老旧方案:

  • 单体部署或策略执行全靠数据库调度(性能差、不可扩展)
  • 策略全部驻留 Redis(不支持逻辑与线程)或文件(难维护)

现代量化交易系统(尤其在交易所场景)几乎都采用:分布式缓存协调 + 本地运行实例 + 消息广播机制 的组合架构。

哪怕你用了 Redis 这样的分布式缓存系统,如果策略运行时实例(即策略逻辑、状态)仍然保存在各节点的本地内存中,理论上仍然会存在一致性问题。但注意,这并不意味着“本地内存就错了”——恰恰相反,合理使用本地内存 + 分布式缓存协调机制,是经过权衡后的主流做法

✅ 为什么仍然使用本地内存?

  1. 策略必须低延迟响应(毫秒级),Redis 的访问延迟(即便是 1ms)在高频场景下仍然太高。
  2. 策略对象是有状态的 Java 实例(例如持有定时器、线程、逻辑栈),不适合直接存入 Redis。
  3. 每个节点仅维护自己分配的部分策略,并不会所有节点都保存全部数据,数据本身是分片的,不是复制的。

❗ 那数据一致性怎么解决?

不是让所有节点都同步“策略实例”,而是通过以下机制让一致性“可控”

🔸1. 策略分片调度机制(分布式分配策略到节点)

  • 每个策略只被一个节点加载并运行,其他节点不会持有它,从源头避免并发冲突。
  • 典型做法:使用 Redis 哈希表记录 {strategy_id -> node_id},调度中心统一分发。
  • 当节点上下线时,重新分配策略并通知其他节点清除/加载缓存。

🔸2. 策略状态持久化

  • 每个策略的关键状态(持仓、盈亏、网格位置等)定期快照保存到 Redis/MySQL。
  • 一旦节点宕机,策略在其他节点恢复时可读取状态重启,达到最终一致性

🔸3. 热更新与配置同步

  • Redis Pub/Sub 或 Kafka 负责发布 “策略更新/下线/上线” 指令,所有节点监听后实时调整本地内存。
  • 可设计 TTL + 校验机制避免策略“缓存污染”。

✅ 如何看待一致性与性能的权衡?

模型描述一致性性能
全部 Redis所有策略数据实时存 Redis强一致性🚫 极差
本地缓存 + Redis 元信息运行时状态本地,控制/配置走 Redis最终一致性✅ 高
分布式状态机(Raft)极高一致性,复杂实现强一致性🚫 性能弱,复杂度高

现代交易所普遍接受“最终一致性”模型,以获得实时执行能力与高吞吐。

  • 使用本地内存会带来一致性挑战。
  • 但只要设计了清晰的分片调度机制 + 状态快照恢复 + 配置广播同步,问题是可以控制的
  • 本地内存换取的是 毫秒级性能,这是交易系统不可妥协的要求。
  • 所以,这不是“违背一致性”,而是基于性能要求下的架构折中 —— 这正是现代交易所的主流做法。

Kubernetes 的自动扩容与缩容机制确实会给量化交易系统中“策略分布与运行状态”的一致性与调度带来挑战。但现代量化系统已经有一套成熟、稳健的设计模式来解决这个问题。

✅ 问题本质

Kubernetes 自动扩缩容会导致:

  • 节点数量变化,策略运行实例需要动态迁移。
  • 某个策略运行节点突然消失,其本地内存状态丢失。
  • 新节点加入后需要分配新策略,避免策略“无人运行”或“多节点重复运行”。

✅ 解决方案:引入“策略调度中心”+“分布式状态协调”

🧠 1. 引入统一的策略调度中心(Strategy Coordinator)

这个组件通常作为一个单例服务运行,负责:

  • 记录哪些策略运行在哪个节点(例如保存在 Redis 的 strategy_id -> node_id 映射中)
  • 监听 Kubernetes 节点变化(通过 ServiceRegistry 或 k8s API)
  • 重新分配策略(例如采用一致性哈希或平均分布)
示例调度流程:
  • 节点扩容:调度器感知新节点 → 抽取部分策略 → 更新分配表 → 通知老节点释放、新节点加载
  • 节点缩容或挂掉:感知节点失联 → 重分配其策略 → 通知新节点加载状态 → 恢复运行

🔁 2. 策略实例状态支持“热迁移”(支持重启)

为实现“节点失效时的快速恢复”:

  • 策略运行时的状态(如持仓、挂单、价格历史)定期存 Redis 或 MySQL 快照
  • 每个策略类必须实现:
    void restoreFrom(StateSnapshot snapshot);
    StateSnapshot snapshot();
    

这样新节点加载策略实例后,可以立即恢复状态并继续运行。

📣 3. 利用 Kubernetes 生命周期钩子 + Redis 广播机制

  • 使用 PreStop Hook:节点缩容前通知调度中心清理策略绑定
  • 使用 PostStart Hook:节点启动后自动注册自身能力、接收新策略
  • Redis Pub/Sub 广播“策略迁移”、“行情同步”、“策略下线”等事件

✅ 实战架构图(简化)

┌─────────────┐
│ Strategy DB │◄────────────┐
└────▲────────┘             │
     │                      ▼
┌────┴────┐   Redis    ┌──────────────┐
│调度中心 │◄──────────►│ Strategy Map │
└────▲────┘             └──────────────┘
     │                        ▲
     │        Pub/Sub         │
     │      ┌──────────┐      │
     └─────►│节点A(Pod)│◄────┘
            │节点B(Pod)│
            └──────────┘

在 Kubernetes 自动扩缩容环境中,只要你设计了策略“可迁移”、状态“可恢复”、调度“中心化”且同步“及时”,完全可以实现高可用、高一致、可扩展的量化系统。

在量化交易系统中运行的本地缓存必须是线程安全的,尤其是在满足以下条件之一时:

✅ 你需要线程安全缓存的典型场景:

  1. 多线程调度策略执行
    如果你使用线程池(如 ExecutorService)异步地去执行多个策略实例,比如:
    threadPool.submit(() -> strategy.onPrice(price));
    
    那么多个线程可能会同时访问或修改缓存(如策略注册表、运行状态、策略实例),此时必须线程安全。
  2. WebSocket 或行情服务在异步推送数据
    多个异步推送线程/回调线程都可能触发策略调度,访问缓存。
  3. 策略动态热更新
    例如,后台管理员下发指令让某个策略动态上线、下线或切换参数,会修改缓存结构。

✅ 常见线程安全缓存方案:

1. 使用 ConcurrentHashMap

private final ConcurrentMap<String, StrategyInstance> strategyMap = new ConcurrentHashMap<>();

这是最常见也最简单的线程安全缓存容器,适合读多写少的场景。

2. 使用 Guava Cache(线程安全 + 过期 + 最大容量)

Cache<String, StrategyInstance> cache = CacheBuilder.newBuilder()
        .expireAfterAccess(10, TimeUnit.MINUTES)
        .maximumSize(100_000)
        .build();

适合做策略实例缓存或行情快照缓存。

3. 使用 Spring 的 @Cacheable + @EnableCaching

如果你是基于 Spring Boot 开发的,也可以接入 Spring Cache,配合 Caffeine 或 Redis 实现本地/分布式缓存一致性。

4. 使用Caffeine

Caffeine 是线程安全的,并且是目前 Java 中性能非常优秀的一种本地缓存库。

  • Caffeine 内部基于高性能的并发数据结构(如分段锁、无锁 CAS 操作、Striped Locking 等),
  • 所有公共方法(如 get(), put(), invalidate() 等)都是线程安全的,
  • 支持并发读写、高吞吐,是 Guava Cache 的性能继承者和升级版。
import com.github.benmanes.caffeine.cache.*;

Cache<String, StrategyInstance> strategyCache = Caffeine.newBuilder()
    .maximumSize(200_000)
    .expireAfterAccess(30, TimeUnit.MINUTES)
    .build();

// 并发访问是安全的
strategyCache.put("strategy-123", new GridStrategy());
StrategyInstance instance = strategyCache.getIfPresent("strategy-123");

你可以放心在多线程环境中并发调用 put()get()invalidate() 等操作,无需额外加锁。

✅ Caffeine 还能干什么?
  • 自动过期(基于时间或引用)
  • 最大容量限制(LRU-like 淘汰)
  • 支持 CacheLoader 的懒加载机制
  • 异步刷新、监听缓存驱逐事件

Caffeine 是线程安全的,并且非常适合在量化交易系统中用作策略缓存行情缓存风控缓存

❗ 如果不线程安全,会出现什么问题?

  • 多线程同时注册或销毁策略,导致缓存不一致
  • 多线程同时读取和写入策略状态,可能出现数据竞争
  • 导致某些策略被重复执行或根本不执行
  • 难以排查的并发 BUG,系统稳定性差

线程安全是本地缓存设计中的刚需。无论你采用哪种缓存方式,只要你启用了并发行情调度、策略线程池或分布式协调,就必须保证本地缓存的数据结构具有线程安全性。

是否可以只采用本地缓存的形式

在现代交易所中,完全只用本地缓存、完全不用 Redis 的设计很少见,即使它性能高,也会在一致性、扩缩容、故障恢复等方面留下致命隐患。

✅ 为什么不能只依赖本地缓存?

1. Kubernetes 环境中的 Pod 无状态 + 动态调度

  • 每次部署、扩容、崩溃都会导致实例变化。
  • 本地缓存中的策略信息无法跨节点同步
  • 一旦某个节点掉线或重启,策略就“丢失”了。

2. 多节点运行时策略分布一致性问题

  • 多个节点运行,需要协调策略在哪个节点上执行。
  • 不能保证一个策略只运行一次(避免重复执行或漏执行)。

3. 节点崩溃时策略状态无法恢复

  • 如果策略持仓、挂单状态仅存在本地,一旦节点宕机就无法恢复。

✅ Redis 的作用(为什么现代系统几乎都会用)

功能Redis 扮演的角色
策略分布表哪些策略在哪个节点运行(strategy_id -> node_id
策略状态快照实时状态记录,供迁移和恢复
节点注册可用节点列表(如心跳、负载)
消息广播策略迁移、更新、行情同步(Pub/Sub)
保证一致性所有节点统一获取策略、统一调度

✅ 典型部署模型

┌─────────────┐
│  Redis      │ ← 策略映射表 & 快照 & Pub/Sub
└─────┬───────┘
      │
┌─────▼────────────┐
│ Strategy Node A  │ ← 本地缓存 + 策略线程池
├──────────────────┤
│ Strategy Node B  │ ← 本地缓存 + 策略线程池
└──────────────────┘

每个节点用本地缓存提升运行时性能,但元数据和状态都依赖 Redis 保证全局一致性和容错性

✅ 性能瓶颈问题如何解决?

担心使用 Redis 影响性能,现代系统的做法是:

  1. 只在初始化时查 Redis,运行时用本地缓存
  2. 状态变更批量异步写 Redis,降低写压;
  3. 避免频繁依赖 Redis 调度,调度中心主动推送策略。

这样可以达到 高性能 + 高一致性 + 高容错 的平衡。

方案是否主流风险场景
纯本地缓存,不用 Redis❌ 否数据丢失、重复执行、无法迁移仅适合单节点开发/测试环境
本地缓存 + Redis 同步✅ 是控制得当无大风险现代量化交易系统主流方案

本地缓存和 Redis 中的数据

✅ Redis 中存什么?
✅ 本地缓存中存什么?
✅ 二者如何协同工作?

✅ 1. Redis 中存储的内容(全局共享、跨节点可见

Redis 存的是“全局元数据”和“轻量状态”,主要包括:

Redis Key类型内容说明
strategy:meta:{strategyId}Hash策略基础信息(如参数、绑定symbol、执行类名、节点分配)
strategy:node:{nodeId}Set当前节点运行的策略 ID 列表(用于分配、迁移、下线)
strategy:status:{strategyId}Hash当前策略状态(如启用/暂停、持仓、止盈止损等)
strategy:params:{strategyId}Hash可修改的运行参数(如价格区间、网格数等)
strategy:heartbeat:{nodeId}String心跳时间戳或负载指标(用于节点健康检查)
strategy:updatesPub/Sub策略更新/上线/下线 通知(广播给各节点)

✳️ Redis 是协调器,不负责高频行情调度

  • Redis 不存行情数据(频繁更新太重);
  • 也不存策略实例(运行时状态太大);
  • 只记录必要的 状态、参数、节点分配、通知信息

✅ 2. 本地缓存中存储的内容(高性能执行用

本地缓存用于运行时快速访问,每个节点仅缓存它负责的策略。

缓存项类型说明
Map<String, StrategyInstance>并发缓存策略实例对象(含业务逻辑和状态)
Map<String, StrategyMeta>轻量缓存策略元信息(symbol、用户ID、状态等)
Map<String, SymbolData>行情快照当前行情,用于触发回调
Map<String, StrategyParam>策略参数调整用的运行参数

✅ 3. 协同机制(运行模型)

          Redis                      节点 A(本地缓存)         节点 B
  ┌──────────────────┐          ┌────────────────────┐     ┌────────────┐
  │ strategy:meta:*  │ ───────→ │  加载策略信息       │     │            │
  │ strategy:node:*  │ ←─────── │  注册、回写执行节点 │     │            │
  │ Pub/Sub 更新通知 │ ───────→ │  热更新/停止策略    │     │            │
  └──────────────────┘          └────────────────────┘     └────────────┘

✅ 举例说明:一个策略的生命周期流程

  1. 策略上线(新建)
    • 后台写入 Redis 中的 strategy:metastrategy:params
    • 分配给某个节点,更新 strategy:node:{nodeId} 集合
    • 发布 Pub/Sub 通知该节点加载策略
  2. 节点接收到通知
    • 拉取 Redis 的策略元数据 + 参数
    • 本地构建 StrategyInstance 并注册到本地缓存
    • 开始运行(通过线程池、事件驱动等方式)
  3. 策略运行中
    • 每次收到行情,匹配本地缓存的策略
    • 策略内部状态(如仓位、触发时间等)仅保存在内存中
    • 定期异步写回 Redis(如果需要持久化)
  4. 策略更新
    • 改变参数或状态(如暂停)
    • 写入 Redis → 通知 → 节点更新本地缓存
  5. 策略下线
    • 从 Redis 和本地缓存中移除
    • 停止线程、释放资源

策略模版个数

在现代虚拟货币交易所或量化平台中,常用的策略模板大致在 10~50 个之间,这取决于平台的业务目标和用户层级。

✅ 常见策略模板分类

类型策略模板示例数量估算
趋势类均线交叉、MACD、动量、通道突破5~10 个
震荡类网格交易、区间套利、反转策略5~8 个
量价类成交量突破、OBV 策略、资金流量指标3~5 个
风控型马丁格尔、反马丁、金字塔加仓3~5 个
套利类跨期套利、跨品种套利、三角套利3~6 个
统计类均值回复、协整对冲3~5 个
自定义脚本Python/DSL 自由策略1 个(泛化)

✅ 平台策略模板数量示意

平台类型模板数量特点
专业机构交易所10~30 个偏重机构和 API 用户,策略灵活度高
面向散户平台20~50 个模板多样、可视化配置,策略简洁易懂
API 或策略托管平台10~20 个内置 + 开放自定义用户可上传策略代码或 DSL

✅ 示例策略模板列表(部分)

  • 网格策略(Grid)
  • 马丁格尔策略(Martingale)
  • 均线交叉(MA Cross)
  • 趋势突破(Breakout)
  • 均值回复(Mean Reversion)
  • 双币套利(Stat Arb)
  • 资金流量反应(Money Flow)
  • 区间震荡识别
  • 阶梯建仓
  • 手动辅助策略(止盈止损)
  • 限价挂单策略(Maker Taker 切换)

策略模板 ≠ 策略实例
一个模板(如网格)可以衍生出成千上万不同参数的策略实例。

所以在交易所内部,只需维护有限个“可插拔的策略模板类”,通过参数驱动生成大量策略实例。

对于像 Gate.io 这样的大型虚拟货币交易所,考虑到它提供的 交易产品用户群体的多样性,策略模板的数量会更多一些,且会根据不同的用户需求提供多种定制化选项。

  • 基础策略模板(核心模板):大概 15-30 个
    这些模板覆盖常见的基础交易策略,用户可以直接选择并进行配置。
    例如:

    • 网格交易(Grid Trading)
    • 趋势追踪(Trend Following)
    • 均值回归(Mean Reversion)
    • 马丁格尔(Martingale)
    • 跨期套利(Arbitrage)
    • 动量交易(Momentum Trading)
    • 高频交易(High Frequency Trading)
    • 震荡区间(Range Trading)
    • 跨品种套利(Cross-pair Arbitrage)
    • 成交量策略(Volume-based)
    • 动态止盈止损(Dynamic TP/SL)
  • 高级策略模板:大概 5-10 个
    这些策略适合更有经验的用户,涵盖一些复杂的逻辑和算法,需要较为灵活的参数配置。
    例如:

    • 高频套利(High-Frequency Arbitrage)
    • 机器学习策略(基于模型的交易)
    • 对冲策略(Hedging Strategies)
    • 自适应策略(Adaptive Strategies)
    • 基于数据流的策略(Order Flow Strategy)
    • 风险管理策略(例如止损、止盈动态调整)
  • 用户自定义策略模板1-5 个
    提供脚本功能,用户可以通过 PythonDSL其他语言 来实现完全自定义的策略。

  • 风控策略模板3-5 个
    主要用于风险控制,比如:

    • 最大亏损控制(Maximum Drawdown Control)
    • 最大持仓限制(Maximum Position Limit)
    • 风险值调整(Risk Adjustment)

总策略模板数量估算

  • 基础策略模板:约 15-30 个
  • 高级策略模板:约 5-10 个
  • 用户自定义脚本:约 1-5 个
  • 风控策略:约 3-5 个

总数:大约 24 ~ 50 个策略模板。这个数字可以根据不同交易所的产品需求、目标市场和平台的复杂性有所浮动。

  • Gate.io 这样的交易所,预计会有 30~50 个策略模板,涵盖基本策略、风控、用户自定义脚本等多个方面,满足不同交易者的需求。

策略快照存储

策略执行快照(或状态)可以选择性地存储在 Redis 或 MySQL 中之一,并不是必须同时存储在两者中。

✅ 为什么不是必须“双写”到 Redis 和 MySQL?

这是因为这两种存储之间有明显的权衡:

存储位置优点缺点适用场景
Redis读取极快、支持高并发写入、适合实时计算内存成本高、不持久(除非配置持久化)实时策略调度、频繁状态变更、缓存为主
MySQL持久存储、安全可靠、易于查询分析读写性能相对低、更新频繁会拖慢系统策略审计、历史追踪、冷启动恢复

✅ 常见设计方案:

方案 1:只用 Redis 存储快照(主流)

  • 实时策略运行期间,快照状态全部保存在 Redis。
  • 只有在特定事件(如停机、异常)时,将快照落盘至 MySQL。
  • 适用于注重性能的系统。

方案 2:Redis + 定期持久化至 MySQL

  • Redis 做实时存储,MySQL 做冷备。
  • 每 N 秒 / 每笔关键操作 后异步写入 MySQL。
  • 适合数据较敏感、有合规/审计需求的系统。

方案 3:只用 MySQL 存储(很少见)

  • 所有快照存入 MySQL。
  • 实时性较差,基本不用于高频策略系统。

✅ 所以是否“双写”依赖于具体需求:

  • 如果系统对性能要求极高用 Redis 即可
  • 如果系统对策略状态一致性 & 可追溯性要求高Redis + MySQL 双写
  • 如果系统不运行实时策略,仅用于回测或低频策略 → 直接用 MySQL 也可以。

策略实例设计模式

采用“无状态策略模板 + 实例运行时参数”这种设计模式,使用 Spring 默认的单例 Bean 完全可以搞定,这是目前在很多现代量化平台中广泛采用的方案。

✅ 为什么单例 Bean 没问题?

因为:

  • 策略模板类(比如 GridStrategyTemplate)只是逻辑载体,不持有任何实例状态。
  • 所有状态(如买入价格、网格间距、最大持仓等)都封装在策略实例对象里
  • 实例在调度中执行时,会把自己的参数传入模板类方法,进行策略逻辑计算。

这种模式下,多个策略实例“共享”一个模板对象但不共享运行状态,所以不会冲突。

✅ 简化类图:

┌────────────────────┐
│ GridStrategyTemplate │   <-- 单例 Spring Bean,无状态
└────────────────────┘
         ▲
         │
 ┌───────┴──────────────┐
 │ StrategyInstance(id=001) │  <-- 各自保存不同参数、状态
 │ StrategyInstance(id=002) │
 │ StrategyInstance(id=003) │
 └─────────────────────────┘
  • Spring 管理简单:无须使用原型或手动创建 Bean
  • 线程安全:所有运行状态不在共享 Bean 中,线程之间互不影响
  • 性能高:避免频繁创建对象,节省 GC 和上下文切换

是的,只要策略模板类是无状态的,使用 Spring 默认的单例 Bean 就是主流、安全、性能最优的设计方案。你无需担心冲突或报错。

触发策略实例的运行

分布式量化系统中,策略实例的运行通常由“调度系统”或“事件驱动系统”触发

✅ 策略实例不会主动运行,而是被动等待触发
它们像是“待命状态”的逻辑单元,等着被市场数据、定时任务等“叫醒”。

👇 通常有哪些触发源?

1. 行情推送(最常见)

  • 比如 ETH/USDT 最新成交价更新了
  • 行情系统通过 WebSocket/Kafka/Redis pubsub 通知调度系统
  • 调度系统查找订阅了 ETH/USDT 的所有策略实例
  • 然后将事件派发到各个策略实例的 onPrice() 方法上

典型调用链:

行情系统 ➝ 调度系统 ➝ 策略实例.onPrice(price)

2. 定时触发(如:网格、马丁格尔补仓)

  • 系统有定时任务模块(如 Quartz、Spring Task、Timer)
  • 每分钟、每小时触发一次某些策略的 onTimer() 方法
  • 比如:判断是否需要重新挂单、调整网格

3. 风控事件

  • 系统检测到某个账户浮亏超过阈值
  • 通知调度系统取消策略、调整仓位
  • 调用策略的 onRiskTrigger() 方法

4. 用户操作事件

  • 用户手动启停某个策略
  • 页面调用后台 API,调度系统找到该策略实例并执行 start()stop()

调度系统

在现代虚拟货币交易所中,调度系统通常和量化交易系统属于同一个微服务体系,甚至就集成在同一个微服务中,作为内部模块协同工作,这样可以极大简化调度链路,提高实时性和可靠性

绝大多数交易所(尤其是自研私有系统)使用这种方式:

方案是否独立微服务是否主流适用场景
✅ 集成在量化微服务中✅ 是主流中小型量化平台,大多数交易所
可独立部署次主流超大规模系统,或复杂 SaaS 架构

量化交易系统接入行情数据

行情接入服务独立于量化交易系统之外,在现代交易所中是分布式部署的,原因在于:

目标原因
💡 高可用任意一个节点挂了,不影响整体行情接收
🚀 高性能行情数据量巨大(每秒上千次 tick),需多节点并行处理、节流、广播
⚙️ 负载均衡有的行情源连接数有限(如交易所 WS 限制),需多节点拆分连接
🔄 热更新接入新交易对/新交易所时不影响老节点运行
⛓️ 分区处理不同交易对分配到不同节点,减小每个节点压力

✅ 实际部署规模:参考范围

平台类型接入服务节点数(估算)
🧪 中小型交易所 / 量化平台2 ~ 5 个副本
⚙️ 中大型量化平台(如 Pionex、3Commas)5 ~ 10 个副本
💰 顶级交易所(如 Binance 内部系统)按交易对、市场区域分区部署,几十个节点以上

数量取决于以下因素:

  1. 接入的交易对数量(几百 vs 几万)
  2. 每秒行情更新频率
  3. 是否只做本地现货行情,还是还包括外盘期货、期权、DEX 等
  4. 是否支持多交易所行情聚合(Binance、OKX、Gate、Bybit…)

行情接入服务几乎总是分布式部署的。在中大型平台中,通常部署 3~10个副本,每个副本负责一部分交易对或接入源,并将统一格式的行情广播到系统中其他模块使用。

在现代量化系统中,行情接入服务通过 Kafka 将行情广播给所有量化系统节点,而每个节点根据自己的分配策略,从这些广播中筛选并执行真正相关的策略逻辑。这种架构既满足高性能,又满足可扩展性,是现代虚拟货币交易所中最主流、可扩展性最强的做法。

优点说明
🔁 可扩展新增量化节点不影响现有系统结构
🎯 精准执行每个节点只处理自己的策略实例
⚡ 实时性高Kafka 的高吞吐和低延迟保障实时策略响应
🧱 解耦架构行情接入服务和量化系统解耦,职责分明

Kafka 广播设计

✅ Kafka 是可以做“广播”的 —— 取决于消费者组的配置方式

Kafka 本身并不是“广播系统”,但通过合理使用 多个消费者组,可以模拟广播效果

🎯 关键概念:Kafka 中“广播” vs “组消费”

模式消费者组 ID消费效果场景
组消费(Group Consumption)相同组 ID多个消费者共享消息,每条消息只被1个消费者消费多线程处理、负载均衡
广播消费(Fan-out)每个消费者用不同组 ID每个消费者都能消费同样的消息多个系统或节点都需要处理同样数据

如果希望 Kafka 消息被所有量化交易系统节点都消费到,就不能将它们放到同一个消费者组中。

🔁 正确做法:每个节点使用不同的 group.id

Properties props = new Properties();
props.put("group.id", "quant-node-A"); // 每个节点不一样

✅ 应用到量化系统的场景

假设:

  • 行情接入服务将 BTC/USDT 的价格推送到 Kafka topic: tick.BTCUSDT
  • 有 3 个量化节点,每个节点都负责独立的一批策略实例
  • 希望所有节点都能收到该消息、并自行判断是否处理

那么应该这样做:

  • 每个节点启动 Kafka consumer,使用不同的 group.id
    • quant-node-1
    • quant-node-2
    • quant-node-3
  • Kafka 将同样的消息推送给所有三个 group
  • 每个节点判断该 symbol 是否归属于自己,并决定是否执行策略

📌 Kafka 广播 vs 真正的广播中间件(如 NATS、RabbitMQ)

系统是否真正支持广播特点
Kafka✅(通过多个 group 实现)可回溯、高可靠、分区控制
RabbitMQ(Fanout exchange)原生广播支持
Redis Pub/Sub轻量但不持久化,重启即丢
NATS高性能消息广播中间件

Kafka 的广播虽然不是“原生”,但在大型系统中更稳定、易于管理、可回放重播,所以非常主流。

Kafka 支持广播消费,但需要 每个量化节点使用不同的消费者组 ID。如果多个节点在同一个组中,那就是负载均衡(每条消息只被消费一次),不是广播。

✅ 量化交易系统广播设计

  • ✅ 所有量化系统节点运行的是相同的代码包
  • ✅ 每个节点都需要作为Kafka 的独立消费者(用于实现广播消费);
  • ✅ 同时需要支持 Kubernetes 自动扩容/缩容 —— Pod 数量是动态的;
  • ❗但 Kafka 的广播消费要求每个节点使用唯一的 group.id,否则就会被当作负载均衡模式处理,导致消息只被一个节点消费。

✅ 解决方案:使用动态唯一的 group.id,结合 Kubernetes Pod 身份标识

✅ 1. 每个节点使用唯一的 group.id

用以下这些方法生成唯一的 group.id:

方法说明
使用 Pod 名字在启动脚本中注入:group.id = "quant-group-" + $(hostname)
使用 UUID启动时生成唯一 UUID:group.id = "quant-node-" + UUID.randomUUID()
使用配置模板application-quant.yaml 中自动占位:group.id = quant-#{random.value}

推荐使用 Kubernetes 的 Pod 名或 StatefulSet index,这样即使重启也稳定。

✅ 2. 在 Kubernetes 中实现 group.id 的自动注入

🛠️ 方法一:使用 Pod 环境变量注入 hostname
env:
  - name: GROUP_ID
    valueFrom:
      fieldRef:
        fieldPath: metadata.name

然后在 Java 应用里读取:

String groupId = "quant-group-" + System.getenv("GROUP_ID");
props.put("group.id", groupId);
🛠️ 方法二:使用 init script 或 Helm 模板注入唯一参数
- name: GROUP_ID
  value: "quant-{{ .Release.Name }}-{{ .Pod.Name }}"

✅ 自动扩容缩容怎么影响?

Kubernetes 扩容时:

  • 新 Pod 自动获得唯一 group.id → 成为独立 Kafka 广播消费者 → 自然接入系统。

Kubernetes 缩容时:

  • 被杀掉的 Pod 的 group.id 不再消费消息 → Kafka 自动回收该订阅者。

🎯 所以:这种模式天然适配 Kubernetes 动态扩缩容

行情系统 Kafka topic 设计

行情接入服务将数据发送到 Kafka,topic 的划分方式直接决定了:

  • 下游量化系统如何订阅(按币种、品种、频道等);
  • Kafka 的分区数、性能、资源消耗;
  • 消息路由和消费逻辑的复杂度。

✅ 主流做法:按“频道类型 + 分片”划分 topic,不是一个币种一个 topic

❌ 不推荐做法:一个币种一个 topic

例如:

tick.BTCUSDT
tick.ETHUSDT
tick.DOGEUSDT

这在币种较少时可以,但现代交易所动辄几千个币对,Kafka topic 可能高达几千个,管理复杂、性能下降、不可扩展

✅ 推荐做法:聚合 topic + 分区控制 + 消息过滤

主流交易所(如 Binance、OKX)使用以下方式:

🟢 示例:统一 topic(如 tickers) + 分区 + 消息体中包含 symbol 字段

Topic: tickers
Message payload:
{
  "symbol": "BTCUSDT",
  "price": 63682.1,
  "ts": 1715151210000
}

特点:

优点说明
✅ 可控 topic 数量一般 1~10 个,便于 Kafka 集群维护
✅ 分区可扩展每个分区可能按字母/哈希对 symbol 分片
✅ 消费者过滤灵活下游按 symbol 精准消费,Kafka topic 层结构简洁
✅ 支持广播所有节点可订阅同一 topic,按需判断是否处理
✅ 支持 Kafka 高性能特性Compact、retention、replay 更容易实现

📌 Kafka 分区建议(配合聚合 topic 使用)

为了高性能,topic 通常配置多个分区(如 10、50、100),再按 symbol 的 hash 值将数据均匀分布到各个分区。

int partition = Math.abs(symbol.hashCode()) % numPartitions;
producer.send(new ProducerRecord<>("tickers", partition, key, value));

✅ 下游量化系统如何处理?

  • 所有节点订阅 tickers
  • 消息体中包含 symbol;
  • 节点本地过滤:若 symbol 属于我负责的策略实例 → 触发执行。
if (localCache.hasSymbol(record.value().getSymbol())) {
    strategyDispatcher.dispatch(record.value());
}

✅ 常见行情类 Kafka Topics 示例:

Topic 名称说明消息内容示例
tickers最新价格快照(每对一条)BTCUSDT 最新成交价
depth.deltas委托薄增量更新增加/取消挂单(level 2)
depth.snapshots委托薄快照每 x 秒一份全量订单簿
trades实时成交明细成交价格、数量、方向等
klines.1m每分钟 K 线数据开高低收、volume 等
klines.5m每 5 分钟 K 线数据同上
mark_price标记价格推送杠杆合约/永续合约专用
liquidations爆仓单信息强平记录,风险管理用
funding_rates资金费率更新合约策略常用
system.heartbeat心跳或延迟监控等用于系统健康监控

✅ 数量控制建议

系统规模推荐 topic 数量划分维度说明
中小型交易所5~10 个ticker, depth, trade, kline 等
大型交易所10~20 个(含备份、内部用途)可能为不同市场(现货、合约)分 topic

这样下游可以按需订阅不同数据维度,节省流量和内存。

现代交易所主流做法是:聚合 topic(如 tickers),消息中附带 symbol 字段,下游按需过滤。这样 Kafka 主题数少、扩展性强,完全支持行情广播和大规模量化系统。

Kafka 消费者组中只有一个消费者,消费有多个 partition 的 topic

如果一个 Kafka topic 有多个分区,而消费者组中只有一个消费者实例这个消费者会自动消费该 topic 所有分区的消息,并且是并发轮询而不是“一个一个排队”。

✅ 场景说明:

  • Kafka topic:tickers,有 4 个分区。
  • 消费者组:quant-group,只有一个消费者(consumer-1)。

➤ Kafka 会自动把所有 4 个分区都分配给 consumer-1

然后这个消费者会:

  • 并发轮询(poll)多个分区的消息
  • Kafka 客户端内部有 多分区缓冲区 + 自动轮询机制,性能非常高;
  • 最终消息处理由代码中线程池或逻辑控制。
条件消费行为说明
1 个 topic + 多个分区Kafka 会把所有分区分配给唯一的消费者
消费者组内只有 1 个消费者该消费者将 轮询所有分区并发消费,不会漏消息
并不会“等一个分区消费完再消费另一个”Kafka 是高吞吐流式设计,多个分区的数据会并发调度

并发调度 vs 并行调度

在 Kafka 客户端中,“并发调度”并不等于随机调度,也不是并行调度,而是指:

一个线程以轮询方式依次从多个分区中拉取消息,按批次交给业务线程池执行,形成“逻辑上并发”,**物理上依旧是单线程轮询 + 多线程处理”。

🧠 Kafka 消费客户端机制(比如 Java 的 KafkaConsumer):

  1. KafkaConsumer 本身 不是多线程安全的,通常只会起一个线程进行轮询(poll())。
  2. 这个线程会:
    • 循环拉取它所分配的所有分区的数据;
    • 将消息放入本地缓冲区;
    • 然后将这些消息交由用户代码处理(通常是线程池异步处理);
  3. 处理过程不是“并行拉取”,而是逐个分区进行 poll 拉取,但由于消息处理逻辑可以异步,所以表现为“并发”。

📌 举个例子:

有 1 个消费者,被分配了 4 个分区(P0~P3):

  • KafkaConsumer 会轮询:
    poll(P0) → poll(P1) → poll(P2) → poll(P3) → repeat...
    
  • 每次 poll 会拉到一批消息(比如 100 条),可能会:
    for (ConsumerRecord record : records) {
        threadPool.submit(() -> process(record));
    }
    
  • 这样就形成了 拉取是串行,处理是并发 的效果。

✅ 因此:

概念在 Kafka 中表现
并发调度(逻辑并发)单线程轮询多分区 + 异步处理
并行调度(物理并行)多个消费者实例消费不同分区
随机调度❌不存在。Kafka 分区分配是有规则的(按轮询或 sticky)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值