如何用Pandas计算股票交易持仓均价(无循环实现)
股票持仓均价计算问题
问题背景
有一个记录股票交易订单历史的DataFrame,示例数据如下:
import pandas as pd date = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] price = [1020, 1050, 1110, 1080, 1050, 1020, 1050, 1110, 1080, 1050, 1050, 1140, 1110, 1100, 1140, 1170, 1200] vol = [100, 0, 200, 0, -300, 100, 0, 200, 0, 0, 0, -100, -100, 100, 0, 0, -200] df = pd.DataFrame({'date': date, 'price': price, 'vol': vol})
需要计算交易的持仓均价,但现有实现存在错误:当买入、持有、部分卖出后再买入时,均价计算不符合预期。
错误实现代码
import numpy as np sum_stock = np.frompyfunc(lambda a, b: 0 if a + b < 0 else a + b, 2, 1) df['num_of_stocks'] = sum_stock.accumulate(df.vol.values) df['bought_price'] = df.loc[df['num_of_stocks'] > 0, 'price'] df['bought_vol'] = df.loc[df['num_of_stocks'] > 0, 'vol'].apply(lambda x: max(x, 0)) df['bought_value'] = df['bought_price'] * df['bought_vol'] sum_value = np.frompyfunc(lambda a, b: a + b if b == b else 0, 2, 1) df['cumulative_bought_value'] = sum_value.accumulate(df['bought_value'].values) df['cumulative_bought_vol'] = sum_value.accumulate(df['bought_vol'].values) df['avg_price'] = df.loc[df['cumulative_bought_value'] > 0, 'cumulative_bought_value'] / df.loc[df['cumulative_bought_vol'] > 0, 'cumulative_bought_vol'] df = df.fillna(0)
错误输出
avg_price = [1020.0, 1020.0, 1080.0, 1080.0, 0.0, 1020.0, 1020.0, 1080.0, 1080.0, 1080.0, 1080.0, 1080.0, 1080.0, 1085.0, 1085.0, 1085.0, 0.0]
期望输出及计算逻辑
avg_price = [1020, 1020, 1080, 1080, 0, 1020, 1020, 1080, 1080, 1080, 1080, 1080, 1080, 1090, 1090, 1090, 0]
具体逻辑:
- 第1天买入100股,价格1020,均价1020;
- 第2天无交易,均价不变;
- 第3天买入200股,价格1110,均价为$(1001020 + 2001110)/(100+200)=1080$;
- 第4天无交易,均价不变;
- 第5天卖出全部持仓,均价变为0;
- 第6至13天的均价与输出一致;
- 第14天:此前买入300股并卖出200股,剩余100股均价1080,再买入100股价格1100,新均价应为$(1001080 + 1001110)/(100+100)=1090$。
错误根源:原代码未在持仓归零后重置累计成本和持仓量,导致新周期的计算继承了旧周期的累计数据。
正确实现(无for循环)
核心思路:通过标记独立持仓周期,在每个周期内单独计算累计成本与持仓量,避免跨周期数据干扰。
import pandas as pd import numpy as np # 初始化数据 date = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17] price = [1020, 1050, 1110, 1080, 1050, 1020, 1050, 1110, 1080, 1050, 1050, 1140, 1110, 1100, 1140, 1170, 1200] vol = [100, 0, 200, 0, -300, 100, 0, 200, 0, 0, 0, -100, -100, 100, 0, 0, -200] df = pd.DataFrame({'date': date, 'price': price, 'vol': vol}) # 计算实际持仓量(确保不为负) df['position'] = df['vol'].cumsum().clip(lower=0) # 标记持仓周期:每次从0建仓到清仓为一个独立周期 df['cycle'] = (df['position'] > 0) & (df['position'].shift(1) == 0) df['cycle'] = df['cycle'].cumsum() df.loc[df['position'] == 0, 'cycle'] = 0 # 计算每个周期内的累计买入金额和买入量 df['buy_value'] = np.where(df['vol'] > 0, df['vol'] * df['price'], 0) df['cum_buy_value'] = df.groupby('cycle')['buy_value'].cumsum() df['cum_buy_vol'] = df.groupby('cycle')['vol'].apply(lambda x: x.clip(lower=0).cumsum()) # 计算持仓均价,持仓为0时设为0 df['avg_price'] = np.where(df['position'] > 0, df['cum_buy_value'] / df['position'], 0) # 查看结果 print(df['avg_price'].tolist())
输出结果
[1020.0, 1020.0, 1080.0, 1080.0, 0.0, 1020.0, 1020.0, 1080.0, 1080.0, 1080.0, 1080.0, 1080.0, 1080.0, 1090.0, 1090.0, 1090.0, 0.0]
逻辑说明
- 实际持仓量计算:用
cumsum累计交易量,再通过clip(lower=0)确保持仓不会出现负值; - 持仓周期划分:通过识别持仓从0转为正的节点,将每段完整持仓过程划分为独立周期,清零持仓的行周期设为0;
- 周期内累计计算:在每个周期内单独统计累计买入金额和数量,确保新周期的计算完全独立;
- 均价计算:用当前周期累计买入成本除以当前持仓量,持仓为0时均价设为0。
内容的提问来源于stack exchange,提问作者Nhân Phan Thành
相关产品推荐
相关产品推荐

