一个策略跑得好,不代表三个策略一起跑也能好。日志互相覆盖、数据互相污染、一个策略崩溃把整组进程全带崩——本篇用 Python 的 multiprocessing + 策略工厂 + 独立日志,把「多策略并行」做成干净可扩展的工程模块。
一、为什么多策略并行会「打架」
很多开发者从单策略过渡到多策略时,会直接用同一个 Python 进程顺序执行:
strategy_a.run()
strategy_b.run()
strategy_c.run()
问题是:
- 日志混成一团:三个策略的 print 交错输出,排查时根本分不清
- 状态互相污染:策略 A 改了全局变量,策略 B 莫名其妙出错
- 一个崩溃全完蛋:策略 A 网络超时抛异常,策略 B/C 还没跑就被迫退出
- 没法水平扩展:顺序执行耗时 = A + B + C,无法利用多核
解决思路:每个策略一个独立进程 + 统一配置 + 独立日志 + 结果回传。
二、本文你将得到什么
- 策略工厂模式:新增策略只需注册,不改主流程代码
- multiprocessing 进程隔离:每个策略独立 PID、独立内存、互不影响
- 独立日志体系:按策略分文件,排查时直奔主题
- 统一配置注册表:策略参数外置,调整无需改代码
三、策略工厂:新增策略只需一行注册
把策略实现和策略调度解耦:
import pandas as pd
def strategy_sma(df, short=5, long=20):
"""SMA 金叉死叉策略"""
df = df.copy()
df["sma_short"] = df["c"].rolling(short).mean()
df["sma_long"] = df["c"].rolling(long).mean()
df["signal"] = 0
df.loc[df["sma_short"] > df["sma_long"], "signal"] = 1
df.loc[df["sma_short"] <= df["sma_long"], "signal"] = -1
trades = df[df["signal"] != df["signal"].shift(1).fillna(0)]
return {
"name": "SMA_Cross",
"last_signal": int(df["signal"].iloc[-1]),
"trade_count": len(trades),
}
def strategy_rsi(df, period=14, oversold=30):
"""RSI 超卖反弹策略"""
df = df.copy()
delta = df["c"].diff()
gain = delta.where(delta > 0, 0.0)
loss = (-delta).where(delta < 0, 0.0)
rs = gain.rolling(period).mean() / loss.rolling(period).mean().replace(0, 1e-9)
df["rsi"] = 100 - (100 / (1 + rs))
triggers = df[df["rsi"] < oversold]
return {
"name": "RSI_Bounce",
"last_rsi": round(float(df["rsi"].iloc[-1]), 2),
"trigger_count": len(triggers),
}
# 策略注册表:新增策略只需在这里加一行
STRATEGIES = {
"sma": strategy_sma,
"rsi": strategy_rsi,
}
核心好处:主调度代码只认 STRATEGIES 注册表,新增策略时完全不用改调度逻辑。
四、multiprocessing 进程隔离:各跑各的
用 multiprocessing.Process 给每个策略分配独立进程,崩溃也不互相影响:
import multiprocessing as mp
import logging
def run_strategy(name, code, params, log_path, result_queue):
# 每个进程独立日志
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(name)s] %(message)s",
handlers=[
logging.FileHandler(log_path, encoding="utf-8"),
logging.StreamHandler(),
],
)
log = logging.getLogger(name)
log.info(f"启动 strategy={name} code={code}")
# 拉取数据(内部实现省略)
df = fetch_kline(code)
func = STRATEGIES.get(name)
result = func(df, **params)
result["ok"] = True
log.info(f"完成 result={result}")
result_queue.put(result)
主调度入口:
config = [
{"name": "sma", "params": {"short": 5, "long": 20}, "log": "strategy_sma.log"},
{"name": "rsi", "params": {"period": 14, "oversold": 30}, "log": "strategy_rsi.log"},
]
result_queue = mp.Queue()
processes = []
for cfg in config:
p = mp.Process(target=run_strategy,
args=(cfg["name"], "600519", cfg["params"], cfg["log"], result_queue))
p.start()
processes.append(p)
# 等待全部完成
for p in processes:
p.join(timeout=60)
# 收集结果
results = []
while not result_queue.empty():
results.append(result_queue.get())
实测输出:
[start] sma -> pid=9476, log=strategy_sma.log
[start] rsi -> pid=3388, log=strategy_rsi.log
[ok] RSI_Bounce: last_rsi=50.79, trigger_count=0
[ok] SMA_Cross: last_signal=1, trade_count=1
strategy_sma.log: 488 bytes, 3 lines
strategy_rsi.log: 407 bytes, 3 lines
两个策略并行运行,PID 完全独立,日志分文件存储。
五、统一配置注册表:参数外置
把策略列表和参数写进 JSON 配置,主代码只负责读取和执行:
{
"strategies": [
{"name": "sma", "params": {"short": 5, "long": 20}, "log": "strategy_sma.log"},
{"name": "rsi", "params": {"period": 14, "oversold": 30}, "log": "strategy_rsi.log"}
],
"stocks": ["600519", "000001"],
"risk_limits": {"max_drawdown": -0.05}
}
主代码加载:
import json
with open("config.json", "r", encoding="utf-8") as f:
cfg = json.load(f)
for strategy_cfg in cfg["strategies"]:
# 直接按配置启动,无需硬编码
print(f"注册策略: {strategy_cfg['name']} params={strategy_cfg['params']}")
好处:调参不用改 Python 代码,改 JSON 即可;Git 历史清晰,能追踪「哪次参数变更带来了收益变化」。
六、常见坑
- Queue 没消费完就退出:主进程
join()后,一定要把所有结果从 Queue 取出,否则子进程可能处于僵尸状态。 - 日志Handler重复追加:如果在循环里多次
logging.basicConfig(),同一个进程的日志会被重复打印。确保每个进程只配置一次。 - Windows 下 multiprocessing 必须加
if __name__ == "__main__":否则子进程会递归导入主模块,导致无限启动。 - 共享 DataFrame 的陷阱:不要试图把大 DataFrame 通过 Queue 传来传去,要么每个进程自己拉数据,要么用共享内存(
multiprocessing.shared_memory,Python 3.8+)。
七、小结
本文把「多策略并行」从「一个脚本里顺序跑」升级为「工厂 + 进程隔离 + 配置驱动」的工程架构:
- 策略工厂:新增策略只需注册,主流程零改动
- 进程隔离:每个策略独立 PID、独立日志、独立崩溃域
- 配置外置:参数放 JSON,版本可控、热更新友好
- 结果回传:Queue 收集各策略结果,主进程统一汇总
下一篇(#06)我们将推进到「从本地脚本到云端部署」——给你一个量化系统的最小可运行架构模板,拿到就能用。
免责声明:本文仅供技术学习交流,不构成任何投资建议。量化策略回测表现不代表未来收益,投资有风险,决策需谨慎。
代码与文档:github.com/MaiRuiApi