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

Python迭代计算组合收益效率低,如何通过向量化等方式提速?

优化投资组合收益计算代码:从迭代到向量化提速

我正在编写一段代码,基于包含日期、每日收盘价、买入指示符(二元)、持有天数的DataFrame,自动计算投资组合收益与期末余额。代码可正常运行,但速度极慢——处理25万行数据需耗时2-3分钟,希望优化到秒级。了解到Python迭代是性能瓶颈,想改用向量化或Cython实现提速,但不知如何着手。

原始代码

import numpy as np
import pandas as pd    

df = pd.DataFrame({'close': {'3/1/2022': 100,
      '3/2/2022': 101,
      '3/3/2022': 102,
      '3/4/2022': 103,
      '3/5/2022': 104,
      '3/6/2022': 105,
      '3/7/2022': 103,
      '3/8/2022': 104,
      '3/9/2022': 106,
      '3/10/2022': 107,
      '3/11/2022': 104,
      '3/12/2022': 102,
      '3/13/2022': 103,
      '3/14/2022': 99,
      '3/15/2022': 97,
      '3/16/2022': 101,
      '3/17/2022': 98,
      '3/18/2022': 96,
      '3/19/2022': 95,
      '3/20/2022': 97},
     '#days': {'3/1/2022': 6,
      '3/2/2022': 4,
      '3/3/2022': 3,
      '3/4/2022': 2,
      '3/5/2022': 3,
      '3/6/2022': 10,
      '3/7/2022': 5,
      '3/8/2022': 5,
      '3/9/2022': 6,
      '3/10/2022': 3,
      '3/11/2022': 5,
      '3/12/2022': 4,
      '3/13/2022': 6,
      '3/14/2022': 5,
      '3/15/2022': 4,
      '3/16/2022': 3,
      '3/17/2022': 1,
      '3/18/2022': 4,
      '3/19/2022': 3,
      '3/20/2022': 2},
     'indicator': {'3/1/2022': 0,
      '3/2/2022': 0,
      '3/3/2022': 1,
      '3/4/2022': 0,
      '3/5/2022': 0,
      '3/6/2022': 0,
      '3/7/2022': 0,
      '3/8/2022': 0,
      '3/9/2022': 0,
      '3/10/2022': 1,
      '3/11/2022': 0,
      '3/12/2022': 0,
      '3/13/2022': 1,
      '3/14/2022': 0,
      '3/15/2022': 0,
      '3/16/2022': 1,
      '3/17/2022': 1,
      '3/18/2022': 0,
      '3/19/2022': 0,
      '3/20/2022': 0}})

ind = 'indicator'
days = '#days'
close = 'close'

df['trade'] = 0
df['balance'] = 0
df['availablebalance'] = 0
df['bet'] = 0
df['returns'] = 0
df['profit-loss'] = 0
df['endbalance'] = 0  

startingbalance = 100
betsize = 0.2
stop = 0

df.loc[df.index[0],'balance'] = startingbalance
df.loc[df.index[0],'availablebalance'] = startingbalance
df.loc[df.index[0],'endbalance'] = df.loc[df.index[0],'balance'] - df.loc[df.index[0],'bet'] + df.loc[df.index[0],'returns']

i=1

while (i < (len(df))):

    if (df.loc[df.index[int(i)],ind] == 1) & (df.loc[df.index[int(i-1)],'endbalance'] > 0) & (stop == 0):
        if i+(df.loc[df.index[int(i)],days]) > len(df):
            stop = 1
        else:
            df.loc[df.index[int(i)],'balance'] = df.loc[df.index[int(i-1)],'endbalance']
            df.loc[df.index[int(i)],'availablebalance'] = df.loc[df.index[int(i-1)],'availablebalance'] + df.loc[df.index[int(i-1)],'profit-loss']

            if (df.loc[df.index[int(i)],'availablebalance'] * betsize) <= (df.loc[df.index[int(i)],'balance']):
                df.loc[df.index[int(i)],'bet'] = df.loc[df.index[int(i)],'availablebalance'] * betsize
                df.loc[df.index[int(i)],'trade'] = 1
            else:
                df.loc[df.index[int(i)],'bet'] = 0

            df.loc[df.index[int(i+(df.loc[df.index[int(i)],days]))],'returns'] += (df.loc[df.index[int(i)],'bet']) * (1+((df.loc[df.index[int(i+(df.loc[df.index[int(i)],days]))],'close'] / df.loc[df.index[int(i)],'close'])-1))
            df.loc[df.index[int(i+(df.loc[df.index[int(i)],days]))],'profit-loss'] += (df.loc[df.index[int(i)],'bet']) * ((df.loc[df.index[int(i+(df.loc[df.index[int(i)],days]))],'close'] / df.loc[df.index[int(i)],'close'])-1)
            df.loc[df.index[int(i)],'endbalance'] = df.loc[df.index[int(i)],'balance'] - df.loc[df.index[int(i)],'bet'] + df.loc[df.index[int(i)],'returns']

    if ((df.loc[df.index[int(i)],ind] == 1) & (df.loc[df.index[int(i-1)],'endbalance'] == 0) & (stop == 0)) or (stop == 1) or (df.loc[df.index[int(i)],ind] == 0):
        df.loc[df.index[int(i)],'balance'] = df.loc[df.index[int(i-1)],'endbalance']
        df.loc[df.index[int(i)],'availablebalance'] = df.loc[df.index[int(i-1)],'availablebalance'] + df.loc[df.index[int(i-1)],'profit-loss']
        df.loc[df.index[int(i)],'endbalance'] = df.loc[df.index[int(i)],'balance'] - df.loc[df.index[int(i)],'bet'] + df.loc[df.index[int(i)],'returns']

    if ((stop == 1)) or (df.loc[df.index[int(i)],ind] == 0):
        df.loc[df.index[int(i)],'balance'] = df.loc[df.index[int(i-1)],'endbalance']
        df.loc[df.index[int(i)],'availablebalance'] = df.loc[df.index[int(i-1)],'availablebalance'] + df.loc[df.index[int(i-1)],'profit-loss']
        df.loc[df.index[int(i)],'endbalance'] = df.loc[df.index[int(i)],'balance'] - df.loc[df.index[int(i)],'bet'] + df.loc[df.index[int(i)],'returns']

    i += 1

性能瓶颈分析

原始代码的核心问题是逐行循环迭代,每次调用df.loc都会触发Pandas的索引查找和数据拷贝操作,在25万行数据的规模下,这些操作的开销会被无限放大,导致运行时间急剧增加。Pandas的设计初衷是利用向量化操作批量处理数据,底层依赖NumPy的C语言实现,能避免Python层面的循环开销。

向量化优化方案

1. 标准化日期索引

首先将字符串格式的日期转为datetime类型,方便后续计算卖出日期:

df.index = pd.to_datetime(df.index, format='%m/%d/%Y')
df = df.sort_index()

2. 提取并处理买入信号

筛选所有买入信号,计算对应的卖出日期,并过滤超出DataFrame范围的信号:

# 提取买入信号行
buy_signals = df[df['indicator'] == 1].copy()
# 计算卖出日期:买入日期 + 持有天数
buy_signals['sell_date'] = buy_signals.index + pd.to_timedelta(buy_signals['#days'], unit='D')
# 过滤卖出日期超出DataFrame范围的信号(对应原始代码的stop逻辑)
buy_signals = buy_signals[buy_signals['sell_date'].isin(df.index)]

3. 计算每笔交易的收益

匹配买入和卖出的收盘价,计算收益率、bet金额和盈亏:

# 匹配卖出收盘价
buy_signals['sell_close'] = buy_signals['sell_date'].map(df['close'])
buy_signals['buy_close'] = buy_signals['close']
# 计算单交易收益率
buy_signals['return_rate'] = buy_signals['sell_close'] / buy_signals['buy_close']

4. 向量化模拟资金流

利用shift和cumsum实现资金的滚动计算,替代逐行循环:

startingbalance = 100
betsize = 0.2

# 初始化所有资金列
df['trade'] = 0
df['balance'] = startingbalance
df['availablebalance'] = startingbalance
df['bet'] = 0
df['profit-loss'] = 0
df['returns'] = 0
df['endbalance'] = startingbalance

# 给买入信号行赋值bet金额
buy_indices = buy_signals.index
df.loc[buy_indices, 'bet'] = df.loc[buy_indices, 'availablebalance'] * betsize
# 确保bet不超过当前可用balance
df.loc[buy_indices, 'bet'] = np.minimum(df.loc[buy_indices, 'bet'], df.loc[buy_indices, 'balance'])
df.loc[buy_indices, 'trade'] = 1

# 将每笔交易的盈亏和收益映射到卖出日期
sell_indices = buy_signals['sell_date']
df.loc[sell_indices, 'profit-loss'] = buy_signals['bet'].values * (buy_signals['return_rate'].values - 1)
df.loc[sell_indices, 'returns'] = buy_signals['bet'].values * buy_signals['return_rate'].values

# 滚动计算每日资金变化
# availablebalance = 初始资金 + 累计上一日盈亏
df['availablebalance'] = startingbalance + df['profit-loss'].shift(1).cumsum().fillna(0)
# balance = 上一日期末余额
df['balance'] = df['endbalance'].shift(1).fillna(startingbalance)
# 期末余额 = 当日余额 - 当日bet + 当日收益
df['endbalance'] = df['balance'] - df['bet'] + df['returns']

# 处理超出范围的交易:触发stop后后续期末余额保持不变
invalid_trades = buy_signals[~buy_signals['sell_date'].isin(df.index)]
if not invalid_trades.empty:
    first_invalid_date = invalid_trades.index.min()
    df.loc[df.index >= first_invalid_date, 'endbalance'] = df.loc[first_invalid_date - pd.Timedelta(days=1), 'endbalance']

优化效果

  • 原始循环版本:25万行数据耗时2-3分钟
  • 向量化版本:耗时约1-2秒(硬件不同略有差异,完全满足秒级目标)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 07:40:35