第三章:时间度量基础

做量化交易,时间就是金钱。这句话一点都不夸张。

我见过太多策略,逻辑完美,回测漂亮,一上实盘就崩。为什么?因为时间度量出了问题。你想想看,如果连 tick 的到达时间都量不准,那你的策略执行、滑点计算、延迟分析,全都会变成空中楼阁。

这一章,我们就来彻底搞懂 Python 里的时间度量工具。我会把我在实盘开发中踩过的坑、积累的经验,全都摊开来讲。

3.1 time 模块:最基础的时间工具

time 模块是 Python 标准库中最古老的时间模块。它直接调用操作系统底层的 C 库函数,性能很高,但精度和功能都比较原始。

3.1.1 核心函数

函数 返回值 精度 用途
time.time() Unix 时间戳(秒) 毫秒级(Windows 上约 1ms) 获取当前时间、计算耗时
time.sleep(secs) 取决于操作系统调度 暂停执行指定秒数
time.perf_counter() 性能计数器(秒) 纳秒级 高精度计时
time.process_time() 进程 CPU 时间(秒) 微秒级 测量 CPU 执行时间
注意:time.time() 返回的是墙上时间(wall-clock time)。如果系统时间被 NTP 同步调整了,它可能会往回跳!我在实盘交易中遇到过这种情况,导致策略误判了行情时间,差点造成损失。

3.1.2 代码示例:基础计时

import time

# 获取当前时间戳
now = time.time()
print(f"当前 Unix 时间戳:{now}")

# 格式化时间
local_time = time.localtime(now)
formatted = time.strftime("%Y-%m-%d %H:%M:%S", local_time)
print(f"格式化时间:{formatted}")

# 简单计时
start = time.time()
# 模拟一些计算
for _ in range(1000000):
    _ = 1 + 1
end = time.time()
print(f"耗时:{end - start:.6f} 秒")

3.2 timeit 模块:精准测量代码性能

time.time() 手动计时,有个大问题:单次测量受系统负载影响太大。你跑一次可能 0.1 秒,再跑一次可能 0.3 秒。这怎么判断性能?

timeit 模块就是来解决这个问题的。它会自动多次运行你的代码,取最小值或平均值,排除系统干扰。

3.2.1 基本用法

import timeit

# 测量单条语句
t = timeit.timeit('"-".join(str(n) for n in range(100))', number=10000)
print(f"执行 10000 次耗时:{t:.6f} 秒")

# 测量函数
def test_func():
    return sum(range(1000))

t = timeit.timeit(test_func, number=10000)
print(f"函数执行 10000 次耗时:{t:.6f} 秒")
我的习惯:在对比两种实现方式时,我会用 timeit.repeat() 跑 3-5 轮,每轮 10000 次,然后取最小值。这样能最大程度排除偶然因素。

3.2.2 避坑指南

我曾经犯过一个低级错误:在 timeit 的代码字符串里引用了外部变量,结果报错。后来才知道,timeit 默认是在独立的命名空间中执行的。

# 错误写法
x = 42
t = timeit.timeit('x + 1', number=1000)  # 会报错:NameError

# 正确写法
t = timeit.timeit('x + 1', globals=globals(), number=1000)

3.3 datetime 与 timedelta:时间运算的利器

做量化交易,经常要处理时间加减。比如「获取过去 5 分钟的 K 线数据」、「计算距离收盘还有多少秒」。这时候 datetimetimedelta 就派上用场了。

3.3.1 精度对比

模块 最小精度 适用场景
time.time() 毫秒级 简单计时、时间戳获取
datetime.now() 微秒级 时间运算、格式化输出
time.perf_counter() 纳秒级 高精度性能测量
time.process_time() 微秒级 CPU 时间测量

3.3.2 实战:计算 K 线时间窗口

from datetime import datetime, timedelta

# 获取当前时间
now = datetime.now()
print(f"当前时间:{now}")

# 计算 5 分钟前
five_min_ago = now - timedelta(minutes=5)
print(f"5 分钟前:{five_min_ago}")

# 计算距离下一个整点还有多少秒
next_hour = now.replace(minute=0, second=0, microsecond=0) + timedelta(hours=1)
seconds_until_next_hour = (next_hour - now).total_seconds()
print(f"距离下一个整点还有:{seconds_until_next_hour:.2f} 秒")
重要提示:datetime 的精度是微秒级,但 timedelta 内部使用 dayssecondsmicroseconds 三个字段存储。如果你需要纳秒级精度,必须用 perf_counter

3.4 高精度计时器:perf_counter 与 process_time

在量化交易中,我们经常要测量「从收到行情到发出信号」的延迟。这个延迟可能只有几十微秒。用 time.time() 根本量不准。

3.4.1 perf_counter:墙上时间的高精度版本

perf_counter 返回的是性能计数器,精度通常能达到纳秒级。它不受系统时间调整的影响,只用于测量时间间隔。

import time

# 测量极短代码段的执行时间
start = time.perf_counter()
# 模拟一个微秒级的操作
_ = [x**2 for x in range(1000)]
end = time.perf_counter()

print(f"耗时:{(end - start) * 1_000_000:.2f} 微秒")
注意:perf_counter 的起始值是不确定的。你不能用它来获取「当前时间」,只能用来计算时间差。

3.4.2 process_time:只算 CPU 时间

有时候我们想知道「代码本身花了多少 CPU 时间」,而不是墙上时间。比如你的代码被操作系统挂起了 1 秒,墙上时间多了 1 秒,但 CPU 时间没变。这时候用 process_time

import time

# 测量 CPU 执行时间
start = time.process_time()
# 模拟 CPU 密集型计算
for _ in range(10_000_000):
    _ = 1 + 1
end = time.process_time()

print(f"CPU 时间:{end - start:.6f} 秒")

3.4.3 实战对比:perf_counter vs process_time

import time

def io_simulation():
    # 模拟 I/O 等待
    time.sleep(0.1)
    # 模拟计算
    for _ in range(1_000_000):
        _ = 1 + 1

# 用 perf_counter 测量
start_wall = time.perf_counter()
io_simulation()
end_wall = time.perf_counter()
print(f"墙上时间:{(end_wall - start_wall) * 1000:.2f} 毫秒")

# 用 process_time 测量
start_cpu = time.process_time()
io_simulation()
end_cpu = time.process_time()
print(f"CPU 时间:{(end_cpu - start_cpu) * 1000:.2f} 毫秒")

你会发现,墙上时间比 CPU 时间多了大约 100 毫秒。这 100 毫秒就是 time.sleep() 造成的 I/O 等待。在量化交易中,如果你用 process_time 去测量网络延迟,那结果会严重失真。

3.5 知识体系总览

下面这张图,是我自己总结的时间度量工具选择逻辑。每次写新代码前,我都会看一眼。

Python 时间度量工具选择流程图 需要测量什么? 获取当前时间点 测量时间间隔 需要时间运算? datetime time.time() 需要 CPU 时间? process_time perf_counter 核心原则: 时间点用 datetime,时间差用 perf_counter,CPU 时间用 process_time

3.6 我的选择原则

说了这么多,到底该用哪个?我总结了一个简单的判断逻辑:

  • 获取当前时间:用 datetime.now()。它支持时区、格式化、运算,功能最全。
  • 测量代码耗时:用 timeit 模块。它自动处理了多次测量和统计。
  • 测量微秒级延迟:用 perf_counter。精度最高,不受系统时间影响。
  • 测量 CPU 使用率:用 process_time。它能告诉你代码到底吃了多少 CPU。
  • 简单计时:用 time.time()。够用就行,别过度设计。
我的经验:在实盘交易系统中,我会同时记录 perf_counterdatetime。前者用于性能监控,后者用于日志记录。两者配合,既能定位延迟问题,又能追溯历史事件。

嗯,时间度量这块就讲到这里。记住一句话:选对工具,比用对工具更重要。下一章,我们会把这些工具用在真正的性能基准测试中。

专注资料整理