用 asyncio 和 signal 解锁量化交易系统的隐秘力量

假设你正在运行一个交易机器人,每秒钟从多个交易所抓取价格,然后快速比对、判断、下单。这时候突然一个交易所接口挂了,你还在苦苦 try...except?甚至没能正常关闭日志、释放内存、撤销委托?

Python 世界里这对组合就像金庸小说里的张无忌+乾坤大挪移,不仅能帮你优雅地并发处理多个交易任务,还能让系统优雅关闭、资源清理、甚至实现“软中断”。

 1、asyncio 到底为量化交易带来了什么?

老派交易系统:

while True:
    fetch_price()
    analyze()
    trade()
    time.sleep(1)

好家伙,看似简单,实则有坑:

  • 吃满 CPU

  • 多个交易所一起跑就难以同步

  • 出错一个任务,可能全挂

  • 没法优雅终止

那 asyncio 能带来什么?

关键词:并发、可中断、结构清晰、节省资源

我们先来看个味儿正的代码片段:

import asyncio

async def fetch_price(exchange):
    # 模拟请求
    await asyncio.sleep(0.1)
    print(f"[{exchange}] 已获取价格")
    return {"price": 100.0}

async def main():
    tasks = [fetch_price("Binance"), fetch_price("OKX"), fetch_price("Bybit")]
    results = await asyncio.gather(*tasks)
    print(results)

asyncio.run(main())

输出:

[Binance] 已获取价格
[OKX] 已获取价格
[Bybit] 已获取价格
[{"price": 100.0}, {"price": 100.0}, {"price": 100.0}]

这是 asyncio 最简单的形态:同时请求,彼此不阻塞,效率蹭蹭蹭往上涨

如果你这会还在写多线程或多进程,不妨静下心来,重构一下核心逻辑。

小技巧:asyncio 适合 I/O 密集的场景,比如网络请求、数据库读写,不适合 CPU 密集型(比如机器学习模型的训练),别用错场景哈。

2、信号 signal 是啥,为啥我一直忽略它?

signal 模块是 Python 与操作系统打交道的桥梁。简单说,你可以用它接收来自系统的“终止信号”(如 Ctrl+C、kill 等),然后自定义如何优雅关闭系统。

举个例子:

import signal
import time

def handler(signum, frame):
    print("我收到了终止信号,开始优雅退出...")
    exit(0)

signal.signal(signal.SIGINT, handler)

while True:
    print("运行中...")
    time.sleep(1)

你 Ctrl+C,程序不是一秒停,而是会优雅告别。结合 asyncio,这个功能会变得更香。

而我们量化交易系统中,信号的作用远不止优雅退出:

  • 自动关闭订单簿、WebSocket

  • 停止策略线程或协程

  • 记录日志、备份持仓

  • 通知风控模块

信号 = 程序临终遗言的通知器,你越早掌握它,系统就越有韧性

 3、asyncio + signal 联合大法,打造稳如老狗的主循环

好戏来了。我们要把 asynciosignal 糅合在一起,打造一个“可启动、可中止、可恢复”的交易核心主循环。

假设我们有一个主任务不断获取行情、运行策略并下单,如何在收到 Ctrl+C 时:

  1. 通知所有任务停止

  2. 保存状态

  3. 关闭资源

 实战示例:支持 Ctrl+C 优雅退出的主循环

import asyncio
import signal

stop_signal = asyncio.Event()

def handle_exit(sig_num, frame):
    print(f"收到退出信号 {sig_num},即将停止...")
    stop_signal.set()

signal.signal(signal.SIGINT, handle_exit)

async def run_strategy():
    while not stop_signal.is_set():
        print("策略运行中...")
        await asyncio.sleep(1)
    print("策略已停止")

async def main():
    await run_strategy()
    print("系统安全退出!")

asyncio.run(main())

你会发现:

  • 按下 Ctrl+C 后并不会暴力退出

  • 策略会优雅收尾

  • 所有资源有机会释放完

在真实交易中,这意味着你有机会在停机前撤回所有挂单、保存持仓状态。

4:打造“可插拔”策略系统框架

现在我们要在上一节的基础上,引入“策略模块”概念。我们希望系统具备如下特性:

  • 每个策略是一个独立的 async 协程,可随时启停

  • 系统能集中管理多个策略的生命周期

  • 每个策略有自己的初始化逻辑、处理逻辑、退出逻辑

  • 可轻松新增或下线策略,适配实盘和回测

有点像“热插拔的 USB 策略”,插上就跑,拔掉也不崩。

框架核心设计思路

我们定义每个策略必须继承 BaseStrategy,具备如下结构:

class BaseStrategy:
    def __init__(self, name):
        self.name = name
        self.running = True

    async def on_start(self):
        pass

    async def run(self):
        pass

    async def on_stop(self):
        pass

主控模块统一调度:

async def launch_strategy(strategy: BaseStrategy):
    await strategy.on_start()
    while strategy.running and not stop_signal.is_set():
        await strategy.run()
    await strategy.on_stop()

运行多个策略:

async def main():
    from my_strategies import MeanReversion, BreakoutStrategy
    strategies = [MeanReversion("均值回归"), BreakoutStrategy("突破策略")]
    tasks = [launch_strategy(s) for s in strategies]
    await asyncio.gather(*tasks)

优雅、清晰、极具扩展性。

5、策略注册中心 + 配置驱动 + 热更新机制

还记得我们 Part 4 搭建的策略基类吗?现在是时候将它“工业化”了。

一个大型量化系统可能有几十甚至上百个策略,靠人工硬编码每次运行哪个,太不优雅。我们得有这么几样法宝:

  • 策略注册中心:自动发现、登记策略类

  • 配置驱动:通过 config.yaml 决定加载哪些策略

  • 热更新支持:不重启即可 reload 某个策略(例如高频场景中挂单逻辑临时切换)

 一、策略注册中心怎么搞?

这部分用 Python 的元类机制或者装饰器就能实现,推荐后者,简单直观。

# registry.py
STRATEGY_REGISTRY = {}

def register_strategy(cls):
    STRATEGY_REGISTRY[cls.__name__] = cls
    return cls

def get_strategy(name):
    return STRATEGY_REGISTRY[name]

然后在每个策略文件中:

from registry import register_strategy

@register_strategy
class MeanReversion(BaseStrategy):
    ...

注册中心的意义在于 解耦主程序与策略定义,策略作者可以只管写策略,不用改主控代码。

二、用 YAML 驱动你的策略配置

接入配置管理有无数种方式,这里选最朴素但实用的 YAML:

# config.yaml
strategies:
  - name: MeanReversion
    args:
      symbol: "BTC/USDT"
  - name: BreakoutStrategy
    args:
      symbol: "ETH/USDT"

然后在主程序中读取这个配置:

import yaml
from registry import get_strategy

with open("config.yaml") as f:
    config = yaml.safe_load(f)

strategies = []
for sconf in config['strategies']:
    cls = get_strategy(sconf['name'])
    instance = cls(**sconf.get('args', {}))
    strategies.append(instance)

是不是立刻有了种“配置即编程”的优雅感?以后策略团队出新策略,只要写好类+注册好名字+配置 YAML,主控系统零改动

 三、策略热更新的姿势

这块有点难度,但也是全场最刺激的地方。

目标:运行时不重启服务即可更新策略代码,比如上线新版均值回归模型。

核心思路如下:

  • 每个策略运行在独立 Task 中

  • 监听文件改动(可用 watchdoginotify

  • 检测到策略 py 文件变动 =>

    • cancel 原 task

    • reload 模块(importlib.reload

    • 重新构建实例并调度运行

先来看监听策略文件的逻辑(简版):

from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler

class ReloadHandler(FileSystemEventHandler):
    def __init__(self, reload_callback):
        self.reload_callback = reload_callback

    def on_modified(self, event):
        if event.src_path.endswith("mean_reversion.py"):
            self.reload_callback()

配合 asyncio 监听器:

def start_watcher():
    event_handler = ReloadHandler(reload_strategy)
    observer = Observer()
    observer.schedule(event_handler, path="./strategies", recursive=True)
    observer.start()

reload_strategy 需要自己管理旧 Task 停止、新 Task 启动、清理缓存等操作。这部分稍复杂,建议用 asyncio.create_task()task.cancel() 结合信号量控制。

6、异常处理、自动恢复、超时保护机制

我们现在进入实操部分,来构建一个健壮的策略运行器,也就是“策略大管家”:负责启动策略、捕捉异常、自动重试或终止。

 封装策略守护器(StrategyRunner)

import asyncio
import traceback
import logging

class StrategyRunner:
    def __init__(self, strategy, name=None, timeout=5, max_retries=3):
        self.strategy = strategy
        self.name = name or strategy.__class__.__name__
        self.timeout = timeout
        self.max_retries = max_retries
        self._task = None
        self._running = False

    async def _runner(self):
        retries = 0
        await self.strategy.on_start()
        while self._running and retries < self.max_retries:
            try:
                await asyncio.wait_for(self.strategy.run(), timeout=self.timeout)
                retries = 0  # 如果正常运行,清空重试计数
            except asyncio.TimeoutError:
                logging.warning(f"[{self.name}] 执行超时,准备重试...")
                retries += 1
            except asyncio.CancelledError:
                logging.info(f"[{self.name}] 被取消...退出")
                break
            except Exception as e:
                logging.error(f"[{self.name}] 异常退出:{e}\n{traceback.format_exc()}")
                retries += 1
        await self.strategy.on_stop()
        logging.info(f"[{self.name}] 退出策略,状态:{'异常退出' if retries >= self.max_retries else '正常'}")

    def start(self):
        self._running = True
        self._task = asyncio.create_task(self._runner())

    def stop(self):
        self._running = False
        if self._task:
            self._task.cancel()

    def is_running(self):
        return self._task and not self._task.done()

 使用方式

runner = StrategyRunner(MeanReversion(...), timeout=3)
runner.start()

主控系统可以统一维护所有 runner 列表,集中 stop、monitor。

定期状态巡检器(非必要但极推荐)

async def monitor_strategies(runners):
    while True:
        for r in runners:
            if not r.is_running():
                logging.warning(f"[{r.name}] 已经退出,考虑重启")
        await asyncio.sleep(10)

可集成钉钉机器人通知、日志归档、自动拉起策略等。

 7、整合篇 - 组装你的「策略引擎中枢」

我们把之前的内容抽象出来,封装成一个统一的策略调度管理器 StrategyEngine

StrategyEngine 初版结构

class StrategyEngine:
    def __init__(self):
        self.runners = []

    def load_from_config(self, config):
        for item in config['strategies']:
            cls = get_strategy(item['name'])
            strategy = cls(**item.get('args', {}))
            runner = StrategyRunner(strategy, timeout=item.get('timeout', 5))
            self.runners.append(runner)

    async def start_all(self):
        for r in self.runners:
            r.start()

    async def stop_all(self):
        for r in self.runners:
            r.stop()
        await asyncio.sleep(0.5)  # 给任务一点时间释放资源

    def get_status(self):
        return {r.name: r.is_running() for r in self.runners}

这就是我们第一个成型的量化「策略引擎」雏形,干净利落,扩展性极强。

你可能感兴趣的:(asyncio,signal,后端面试,python并发,量化交易,程序化开发,宽客)