如何加速或并行化含多序列过滤的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
相关产品推荐
相关产品推荐

