假设你正在运行一个交易机器人,每秒钟从多个交易所抓取价格,然后快速比对、判断、下单。这时候突然一个交易所接口挂了,你还在苦苦 try...except
?甚至没能正常关闭日志、释放内存、撤销委托?
Python 世界里这对组合就像金庸小说里的张无忌+乾坤大挪移,不仅能帮你优雅地并发处理多个交易任务,还能让系统优雅关闭、资源清理、甚至实现“软中断”。
老派交易系统:
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 密集型(比如机器学习模型的训练),别用错场景哈。
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
停止策略线程或协程
记录日志、备份持仓
通知风控模块
信号 = 程序临终遗言的通知器,你越早掌握它,系统就越有韧性。
好戏来了。我们要把 asyncio
和 signal
糅合在一起,打造一个“可启动、可中止、可恢复”的交易核心主循环。
假设我们有一个主任务不断获取行情、运行策略并下单,如何在收到 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 后并不会暴力退出
策略会优雅收尾
所有资源有机会释放完
在真实交易中,这意味着你有机会在停机前撤回所有挂单、保存持仓状态。
现在我们要在上一节的基础上,引入“策略模块”概念。我们希望系统具备如下特性:
每个策略是一个独立的 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)
优雅、清晰、极具扩展性。
还记得我们 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:
# 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 中
监听文件改动(可用 watchdog
、inotify
)
检测到策略 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()
结合信号量控制。
我们现在进入实操部分,来构建一个健壮的策略运行器,也就是“策略大管家”:负责启动策略、捕捉异常、自动重试或终止。
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)
可集成钉钉机器人通知、日志归档、自动拉起策略等。
我们把之前的内容抽象出来,封装成一个统一的策略调度管理器 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}
这就是我们第一个成型的量化「策略引擎」雏形,干净利落,扩展性极强。