高频交易系统

第三方库/框架

项目启动 main.py

通过参数调用函数

通过获取 main.py 的启动参数来调用不同的函数 进行编译等操作

编译C++代码

  1. 清理旧的 build bin lib 目录
  2. 切换到build目录然后cmake .. os.system("cmake ..") os.chdir("build")
  3. 最后使用make编译C++代码 有返回值 如果是0则表示编译完成

启动项目

  1. 先kill掉以前的进程 休眠1秒
  2. 查看是否需要rebuild
  3. 创建tmux的会话来达到创建进程的目的 123

bash守护进程脚本

主要是为了监控进程状态

  • while true; do:无限循环,执行指定的程序并等待其退出。
  • 如果程序退出码是 0,表示正常退出;如果是 130,表示用户通过 Ctrl+C 中断,脚本会结束。
  • 如果程序被系统杀死(退出码 137,OOM Killer),脚本会尝试清理 Node.js 进程。
  • 如果程序崩溃,则会等待 0.1 秒后重启程序。

针对每一个交易所都会启动一个监控和进程

核心程序

  1. [[#加载配置文件|加载配置文件]]
  2. [[#初始化共享内存|初始化共享内存]]
  3. 启动WebSocket服务器 监听客户端请求

加载配置文件

使用的是[[#^ea87cf|glaze框架]]解析json文件

初始化共享内存

  1. 通过[[#MemoryManager类]]来管理多个进程 不同语言之间的共享内存
    1. 这里调用时直接给server核心进程分配共享内存
    2. 方便后续其他进程需要共享内存时直接进行分配
  2. 初始化数据结构
    1. 获取配置文件中所有的交易所名称和交易对名称 并且进行去重
    2. 这里去重使用方法是这样的
      1. 先将列表排序sort(vec.begin(), vec.end());
        1. 算法里的sort用的是一种混合排序
          1. 大多数情况用的是快排
          2. 元素少的时候用插入排序
          3. 递归深度过深的时候变成堆排序
      2. 去除相邻的重复元素vec.erase(unique(vec.begin(), vec.end()), vec.end());
        1. unique是用来移动的 把相邻的重复元素全部放到最后 返回值是最后一个有效值的尾部迭代器 也就是第二个3的迭代器

          1. {1, 1, 2, 3, 3, 4, 4} => {1, 2, 3, 4, 3, 4, 4}
          2. 可以用双指针来实现
            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            template<class Iterator>
            Iterator unique(Iterator first, Iterator last)
            {
            if(first == last) // 如果是空容器 直接返回
            return last;
            Iterator result = first; // result 用于指向去重的最后一个元素
            ++ first; // 从第二个元素开始遍历比较 此时的first是待比较的元素
            while(first != last)
            {
            // 因为result之前的元素一定是不重复的,所以可以直接用result位置的元素和first待比较的元素进行比较
            if(*result != *first) // 如果不同 就移动到result的下一个位置
            {
            ++result;
            *result = *first;
            }
            ++ first;
            }
            return ++result;
            }
        2. 然后再用erase进行移除

        3. 好处就是不需要额外开空间

  3. 初始化交易窗口和对照信息
  4. 对每个交易所 交易对 交易窗口 生成唯一的id
  5. 分配内存

MemoryManager类

其实就相当于一个简单的共享内存池,管理的内存单位是字节

成员变量

  • base_ptr 指向进程地址空间的共享内存区域的指针
  • total_size 共享内存总大小
  • allocations 一个小的std::map 用于存储已经分配的内存区域的名称和对应偏移量的大小 ^1a71d1
    • 是从string -> Allocation的映射
    • Allocation 主要包含了offset和size 标记起始位置和偏移量
  • last_pos 记录上一次分配内存的结束位置 确保内存分配不会重叠
  • shm_name 共享内存的名称 用于创建共享内存的区域

成员函数

  • 构造函数
    • shm_open, shm_unlink 可以申请和释放指定名称的共享内存文件对象,返回值是一个文件描述符
    • ftruncate 用来更改共享内存对象大小 也就是给共享内存对象申请空间
    • mmap 内存映射 用于将共享内存对象中的内容映射到进程地址空间 获取到共享内存的指针
  • 析构函数
    • munmap释放共享内存
  • allocate
    • 用于分配一块共享内存并且指定名称
    • 说是分配 其实核心原理就是把指定大小的内存置零 然后返回分配后的起始位置指针
  • get_ptr
    • 获取指定名称的共享内存
    • 这里是通过上面的[[#^1a71d1|allocations]]的map 来获取到对应的共享内存块 然后在通过共享内存的基地址+偏移量 返回指定共享内存块的地址
  • get_names
    • 获取所有的内存块名称
  • get_offset
    • 获取指定内存块的偏移量

LockfreeArray类

高效无锁的并发数据访问模式,实现高效的读写操作,采用version确保在并发读写过程中的数据一致性

项目问题

  1. 实习的工作内容是什么
    1. 进行交易所的API对接
    2. 完成部分重构工作
    3. 实现一个Websocket的pool
  2. 整个交易系统你做了什么优化
    1. 将cerr输出日志改为使用CLOG输出日志
    2. 尽可能使用Websocket进行数据订阅和请求发送
    3. 将Python代码转为C++代码和Rust代码
      1. 调用延迟从0.5ms降低到小于0.1ms
  3. 你觉得还有哪些可以优化的点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
gantt
dateFormat YYYY-MM-DD
title 软件开发甘特图示例
section 设计
需求 :done, des1, 2023-11-06,2023-11-08
原型 :active, des2, 2023-11-09, 3d
UI设计 : des3, after des2, 5d
未来任务 : des4, after des3, 5d
section 开发
学习准备理解需求 :crit, done, 2023-11-06,24h
设计框架 :crit, done, after des2, 2d
开发 :crit, active, 3d
未来任务 :crit, 5d
耍 :2d
section 测试
功能测试 :active, a1, after des3, 3d
压力测试 :after a1 , 20h
测试报告 : 48h