You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何加速或并行化含多序列过滤的Django股票回测函数?

优化股票回测的Django数据库查询性能

1. 批量加载所有股票价格并本地分组(核心提速方案)

原循环每次单查一只股票的Price数据,会产生500-1000次数据库查询,这是最大的性能瓶颈。改成一次性加载所有目标股票的价格数据后在内存中分组,能把查询次数压缩到1次,直接大幅减少数据库IO开销,是实现10-20倍提速的关键。

示例代码:

# 获取目标股票的主键集合(避免关联查询,提升效率)
stock_ids = [stock.id for stock in stock_list]
# 批量加载所有对应股票的价格,按股票ID+日期排序(方便后续分组)
all_prices = Price.objects.filter(stock_id__in=stock_ids).order_by('stock_id', 'date')

# 用字典按股票ID分组,内存中完成数据划分
from collections import defaultdict
prices_by_stock = defaultdict(list)
for price in all_prices:
    prices_by_stock[price.stock_id].append(price)

# 遍历回测
for stock in stock_list:
    prices = prices_by_stock.get(stock.id, [])
    backtest = run_backtest(prices)

如果Price模型的stock字段是ForeignKey,用stock_id__in直接查询主键,比stock__in更高效,不需要关联Stock表。

2. 给Price模型添加复合索引

数据库查询的效率依赖合适的索引,针对你的查询场景,给Price表添加stock+date的复合索引,能让批量查询和排序操作的速度显著提升:

class Price(models.Model):
    stock = models.ForeignKey(Stock, on_delete=models.CASCADE)
    date = models.DateField()
    price = models.FloatField()

    class Meta:
        # 复合索引匹配批量查询+排序的场景
        indexes = [models.Index(fields=['stock', 'date'])]
        # 你提到的股票+日期唯一约束,本身也会生成索引,可保留
        unique_together = ('stock', 'date')

添加索引后执行makemigrations和migrate即可,回测场景以读操作为主,索引带来的写入开销可以忽略。

3. 并行回测叠加提速

如果run_backtest是CPU密集型操作,在批量加载数据后,可以用多线程/多进程并行执行回测,进一步压缩整体耗时:

from concurrent.futures import ThreadPoolExecutor

# 先完成批量分组(同方案1)
stock_ids = [stock.id for stock in stock_list]
all_prices = Price.objects.filter(stock_id__in=stock_ids).values_list('stock_id', 'date', 'price').order_by('stock_id', 'date')
prices_by_stock = defaultdict(list)
for stock_id, date, price in all_prices:
    prices_by_stock[stock_id].append((date, price))

# 定义回测任务函数
def backtest_task(stock):
    prices = prices_by_stock.get(stock.id, [])
    return stock.symbol, run_backtest(prices)

# 并行执行,线程数根据CPU核心数调整
with ThreadPoolExecutor(max_workers=8) as executor:
    results = list(executor.map(backtest_task, stock_list))

注意:如果回测过程中不再涉及数据库操作,线程安全问题无需担心;若有数据库操作,需确保每个线程重新初始化连接。

4. 其他进阶优化

  • 轻量化数据获取:如果回测只需要日期和价格,用values_list直接获取元组,避免加载完整的模型对象,减少内存占用和序列化开销:
    all_prices = Price.objects.filter(stock_id__in=stock_ids).values_list('stock_id', 'date', 'price')
    
  • 缓存复用数据:如果同一批股票需要多次回测,把分组后的价格数据缓存到Redis或Django缓存中,下次直接读取内存缓存,避免重复查询数据库。
  • 只读副本分流:若回测流量极大,将查询指向数据库只读副本,减轻主库压力同时提升查询速度。

效果预期

方案1(批量加载+本地分组)结合方案2(复合索引),能直接将查询次数从数百次降到1次,加上索引优化,单查询速度也会提升,完全能达到你预期的10-20倍提速;叠加并行回测后,整体效率还能进一步提升。

内容的提问来源于stack exchange,提问作者Lance Bloom

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 20:42:14