让量化交易再快一点——消息队列 RabbitMQ

317 阅读3分钟

背景

最近在搞量化,其中一个刚需就是:策略触发之后给我的个人微信发消息。发私人消息这个功能(后文统一用 「Msg Server」 称呼),我用一种取巧的方式实现了,但是发一条消息需要 5~8s 的时间,还不能优化。这就带来了一个很大的问题:

量化交易里,时间就是金钱,必须争分夺秒。但是策略都是用 Python 写的,基本上都是同步操作,如果因为一个发消息的功能,耽误这么长时间,那黄花菜都凉了。

当然也有一些 Python 的异步调用方法,但是一是比较麻烦,而是我写的那个发私人消息的服务不支持并发。在折腾了一番后,终于找到了一个性价比比较高的解决方案:

阿里云的 云消息队列 RabbitMQ 版,选它的原因很简单,太便宜了,一个队列大概 ¥0.2/月,这不随便用嘛。

RabbitMQ 服务实现

原理简介

原理很简单,Msg Server 我也是用阿里云的「函数计算 FC」实现的,现在把它的触发方式仅设为「消息队列 RabbitMQ 版」即可。

image.png

这样我们在发消息的时候,只需要往 MQ 里发一条消息就结束了。调用 Msg Server 的工作,就交给 MQ 慢慢串行调用就可以了。而往 MQ 里发一条消息的操作,也就需要 100~300ms,这个速度对于我来说,可以接受了。

调用 RabbitMQ

所以整个调用流程应该是:

HTTP Request => Rabbit MQ Server => Msg Server

MQ 调用 Msg 这一步就不展开了,这个是吃饭的东西,不方便多说,但是 HTTP 调用 MQ 这步可以拿出来说说。

官方文档 只给出了 Java 的调用方式,但是安装环境太费劲了,还是 Python 或者 Nodejs 方便。于是尝试问了下 ChatGPT,果然被我试出来了,Python 用第三方库 pika 即可,这里就不放上来了。放上我用的 Nodejs 版本:

const amqp = require('amqplib');

async function sendMessage(options) {
  const { username, password, host, vhost, queue, routeKey, exchange, message } = options;
  try {
    const url = `amqp://${username}:${password}@${host}/${vhost}`;
    const connection = await amqp.connect(url);
    const channel = await connection.createChannel();
    await channel.assertQueue(queue, { durable: true });
    channel.publish(exchange, routeKey, Buffer.from(message));

    await channel.close();
    await connection.close();
    return `[Success] Sent '${message}' to exchange ${exchange} with routing key '${routeKey}'`;
  } catch (error) {
    console.error('Error:', error);
    return `[Fail] Sent '${message}' to exchange ${exchange} with routing key '${routeKey}'`;
  }
}

// USAGE:
// {
//   "vhost": "xxx",
//   "queue": "xxx",
//   "exchange": "xxx",
//   "routeKey": "1",
//   "message": "xxx"
// }

可以看到需要传入的参数还是很多的,一眼看上去有点懵。其实不用怕,跟着 官方文档 一步一步走,自然就知道这些参数是什么,应该填什么了。

提示:

关于 routeKey 参数,在配置 exchange 和 queue 绑定的时候,如果没有手动填 Routing Key,那么默认值是 1,我在这里小卡了一下,特此提醒。

image.png

image.png

封装成 Server

接下来用阿里云函数计算 FC 封装成 API 就可以了,代码如下:

'use strict';
const amqp = require('amqplib');
const { USERNAME, PASSWORD, HOST } = process.env;

exports.handler = async (event, context, callback) => {
  try {
    const eventObj = JSON.parse(event);
    const payload = getPayload(eventObj);
    console.log(payload);
    const { vhost, queue, routeKey, exchange, message } = payload;
    const msg = await sendMessage({
      username: USERNAME,
      password: PASSWORD,
      host: HOST,
      vhost,
      queue,
      routeKey,
      exchange,
      message,
    });

    callback(null, {
      statusCode: 200,
      body: msg,
    });
  } catch (e) {
    console.log(e);
    callback(e, null);
  }
};

async function sendMessage(options) {
  // code
}

const getPayload = (eventObj) => {
  // http
  if (Object.prototype.hasOwnProperty.call(eventObj, 'requestContext')) {
    if (Object.prototype.hasOwnProperty.call(eventObj, 'body')) {
      let objStr = eventObj.body;
      if (eventObj.isBase64Encoded) {
        objStr = Buffer.from(objStr, 'base64').toString('utf-8');
      }
      return JSON.parse(objStr);
    }
  }
  // normal
  return eventObj;
};

注意:

  1. usernamepasswordhost 这几个固定的值,写死在环境变量里就可以了,既方便又安全;
  2. 这里封装的 getPayload 可以保证无论是 HTTP 调用还是其它云产品的调用都能够正常处理,算是个小技巧。

总结

这次的内容就这么多,最有价值的实际上是那个给个人微信发消息的实现,想知道得话可得交学费,哈哈。

经过这次的折腾,笔者对于阿里云的 Serverless 服务运用的已经非常熟练的,函数计算 FC、云消息队列 RabbitMQ 版、对象存储 OSS、云工作流这几个产品,可以玩出特别多的花活。

比如每天定时给我发送股票打新信息,每天自动下载可转债数据等等。

最关键是省钱啊,每个月往多了用也用不了 10 块钱,真香~~