我正在计算一个包含价格数据的DataFrame
中每行的盈亏金额的DataFrame
。
逻辑如下:
- 我们在当前时间段买入/卖出资产。
- 我们持有资产
holding_period
的时间。 - 如果在持有期内,价格超过
take_profit
,则以该价格退出并盈利。 - 如果在持有期内,价格超过
stop_loss
,则以该价格退出并亏损。 - 在持有期内首次达到的
take_profit
或stop_loss
水平决定我们是盈利还是亏损退出。 - 如果既未达到止盈也未达到止损,则在持有期的最后一个价格退出。
我实现这个的方法是使用pandas.rolling_apply
,它将提供的函数应用于DataFrame
中每个系列的滚动窗口上。
考虑到rolling_apply
会对DataFrame
中的每一行和每一列组合调用函数,这是一个严重的瓶颈。
我想知道是否有更好的方法使用其他pandas/numpy功能来实现这个目标?
这是当前的实现:
def potential_pnl(prices, side, periods, take_profit=np.nan, stop_loss=np.nan): # 根据买入/卖出所需的价格变动方向设置符号 if side == Side.SELL: take_profit *= -1 else: stop_loss *= -1 def period_potential_pnl(window): # 以第一个价格进入,窗口的其余部分是可能的退出价格 entry_price = window[0] exit_prices = window[1:] take_profit_price = entry_price + take_profit stop_loss_price = entry_price + stop_loss # 计算一个布尔数组,显示达到止盈/止损的位置 if side == Side.BUY: filtered = exit_prices[ (exit_prices >= take_profit_price) | (exit_prices <= stop_loss_price) ] else: filtered = exit_prices[ (exit_prices <= take_profit_price) | (exit_prices >= stop_loss_price) ] # 如果既未达到止盈也未达到止损,则以最后一个价格退出 # 否则以首次超过止盈/止损的价格退出 if len(filtered) == 0: exit_price = exit_prices[-1] else: exit_price = filtered[0] exit_pnl = exit_price - entry_price if side == Side.SELL: exit_pnl *= -1 return exit_pnl # 将`period_potential_pnl`应用到数据框上 pnl = pd.rolling_apply(prices, periods + 1, period_potential_pnl) # 向后移动periods个周期,使退出盈亏与进入价格对齐 pnl = pnl.shift(-periods)[:-periods] return pnl
我尝试过的方法:
我最初使用pandas.rolling_max
和pandas.rolling_min
来确定是否达到了take_profit
或stop_loss
。
这种方法的问题有两个方面:
- 你不能使用最大值作为
take_profit
的退出价格,因为take_profit
很可能在更低的价格就已经达到;在实时情况下无法知道持有期的最大值会是多少。 - 你无法确定
take_profit
或stop_loss
哪个先达到。
问题:
是否有更有效的方法来计算每个周期的盈亏?
回答:
这是一种处理方法:
from datetime import datetime, timedeltafrom dateutil.relativedelta import relativedeltafrom pandas_datareader.data import DataReader
样本数据:
prices = DataReader('IBM', 'yahoo', datetime(2015, 1, 1), datetime.today().utcnow())['Open'].resample('D').fillna(method='ffill')prices.head()Date2015-01-02 161.3099982015-01-03 161.3099982015-01-04 161.3099982015-01-05 161.2700042015-01-06 159.669998Freq: D, Name: Open, dtype: float64
计算pnl
的函数 – 获取首次达到止盈、止损或持有期结束的日期,并使用相应的退出价格计算盈亏(对于sell
策略,反转profit_goal
和cut_loss
):
def get_pnl(prices, start_date, holding_period=90, profit_goal=0.10, cut_loss=.10): end_date = start_date + timedelta(days=holding_period) data = prices[start_date: end_date] start_price = data.iloc[0] take_profit = start_price * (1 + profit_goal) cut_loss = start_price * (1 - cut_loss) exit_date = end_date if (data > take_profit).any(): exit_date = data[data > take_profit].index[0] if (data[:exit_date] < cut_loss).any(): exit_date = data[data < cut_loss].index[0] exit_price = data.loc[exit_date] print('Entered on {0} at: {1:.2f}, exited on {2} at {3:.2f} for {4:.2f}%'.format(start_date.strftime('%Y-%b-%d'), start_price, exit_date.strftime('%Y-%b-%d'), exit_price, (exit_price/start_price-1)*100))
测试运行:
for start_date in [datetime(2015, 1, 1) + relativedelta(months=i) for i in range(12)]: get_pnl(prices, start_date)