【问题标题】:Recursion: account value with distributions递归:带有分布的账户价值
【发布时间】:2025-12-13 17:50:02
【问题描述】:

更新:不确定如果没有某种形式的loop 是否可行,但np.where 在这里不起作用。如果答案是“你不能”,那就这样吧。如果可以的话,可能会用到scipy.signal的东西。


我想在下面的代码中对循环进行矢量化处理,但由于输出的递归性质,我不确定如何处理。

了解我当前的设置:

获取起始金额(100 万美元)和季度分配金额(5,000 美元):

dist = 5000.
v0 = float(1e6)

按月频率生成一些随机证券/账户回报(十进制形式):

r = pd.Series(np.random.rand(12) * .01,
              index=pd.date_range('2017', freq='M', periods=12))

创建一个空系列来保存每月帐户值:

value = pd.Series(np.empty_like(r), index=r.index)

将“开始月份”添加到 value。此标签将包含v0

from pandas.tseries import offsets
value = (value.append(Series(v0, index=[value.index[0] - offsets.MonthEnd(1)]))
              .sort_index())

我想摆脱的循环在这里:

for date in value.index[1:]:
    if date.is_quarter_end:
        value.loc[date] = value.loc[date - offsets.MonthEnd(1)] \
                        * (1 + r.loc[date]) - dist
    else:
        value.loc[date] = value.loc[date - offsets.MonthEnd(1)] \
                        * (1 + r.loc[date]) 

组合码:

import pandas as pd
from pandas.tseries import offsets
from pandas import Series
import numpy as np

dist = 5000.
v0 = float(1e6)
r = pd.Series(np.random.rand(12) * .01, index=pd.date_range('2017', freq='M', periods=12))
value = pd.Series(np.empty_like(r), index=r.index)
value = (value.append(Series(v0, index=[value.index[0] - offsets.MonthEnd(1)])).sort_index())
for date in value.index[1:]:
    if date.is_quarter_end:
        value.loc[date] = value.loc[date - offsets.MonthEnd(1)] * (1 + r.loc[date]) - dist
    else:
        value.loc[date] = value.loc[date - offsets.MonthEnd(1)] * (1 + r.loc[date]) 

在伪代码中,循环所做的只是:

for each date in index of value:
    if the date is not a quarter end:
        multiply previous value by (1 + r) for that month
    if the date is a quarter end:
        multiply previous value by (1 + r) for that month and subtract dist

问题是,我目前看不到矢量化是如何实现的,因为连续值取决于是否在前一个月进行了分布。我得到了想要的结果,但对于更高频率的数据或更长的时间段来说效率很低。

【问题讨论】:

  • Don't use floats for money. Ever.(除非在您的模型中,这是一个纯理论构造,结果总和不必匹配)
  • 谢谢。让我们暂时忽略那个小细节......
  • 哈哈。这句话让我很开心=)(我只是想象你老板听到的时候的表情)

标签: python python-3.x pandas recursion finance


【解决方案1】:

您可以使用以下代码:

cum_r = (1 + r).cumprod()
result = cum_r * v0
for date in r.index[r.index.is_quarter_end]:
     result[date:] -= cum_r[date:] * (dist / cum_r.loc[date])

你会:

  • 所有月度回报的累积产品。
  • 1向量乘法与标量v0
  • n 标量向量乘法dist / cum_r.loc[date]
  • n向量减法

其中n 是季末数。

基于这段代码我们可以进一步优化:

cum_r = (1 + r).cumprod()
t = (r.index.is_quarter_end / cum_r).cumsum()
result = cum_r * (v0 - dist * t)

这是

  • 累计1个产品(1 + r).cumprod()
  • 两个系列之间的1个划分r.index.is_quarter_end / cum_r
  • 上述除法累计1次
  • 上述和与标量dist的1次乘法
  • 标量v0dist * t的减法1
  • cum_rv0 - dist * t 的 1 次逐点乘法

【讨论】:

  • 要应用于 DataFrames 而不是 Series,您可以使用 r.index.is_quarter_end.reshape((-1,1))
  • 是的。我的观点是,如果r 是DataFrame 而不是Series,则需要.reshape((-1,1))。但我没有在我的问题中具体说明,你的回答已经是钱了
  • 嗯,好的。感谢您的提示!
【解决方案2】:

好的...我正在尝试解决这个问题。

import numpy as np 
import pandas as pd

#Define a generator for accumulating deposits and returns
def gen(lst):
    acu = 0
    for r, v in lst:
        yield acu * (1 + r) +v
        acu *= (1 + r)
        acu += v


dist = 5000.
v0 = float(1e6)
random_returns = np.random.rand(12) * 0.1

#Create the index. 
index=pd.date_range('2016-12-31', freq='M', periods=13)
#Generate a return so that the value at i equals the return from i-1 to i
r = pd.Series(np.insert(random_returns, 0,0), index=index, name='Return')
#Generate series with deposits and withdrawals
w = [-dist if is_q_end else 0 for is_q_end in index [1:].is_quarter_end]
d = pd.Series(np.insert(w, 0, v0), index=index, name='Movements')

df = pd.concat([r, d], axis=1)
df['Value'] = list(gen(zip(df['Return'], df['Movements'])))

现在,你的代码

#Generate some random security/account returns (decimal form) at monthly freq:
r = pd.Series(random_returns,
          index=pd.date_range('2017', freq='M', periods=12))
#Create an empty Series that will hold the monthly account values:
value = pd.Series(np.empty_like(r), index=r.index)
#Add a "start month" to value. This label will contain v0.
from pandas.tseries import offsets
value = (value.append(pd.Series(v0, index=[value.index[0] - offsets.MonthEnd(1)])).sort_index())
#The loop I'd like to get rid of is here:

def loopy(value) :
    for date in value.index[1:]:
        if date.is_quarter_end:
            value.loc[date] = value.loc[date - offsets.MonthEnd(1)] \
                           * (1 + r.loc[date]) - dist
        else:
           value.loc[date] = value.loc[date - offsets.MonthEnd(1)] \
                           * (1 + r.loc[date]) 

   return value

比较和计时

(loopy(value)==list(gen(zip(r, d)))).all()
Out[11]: True

返回相同的结果

%timeit list(gen(zip(r, d)))
%timeit loopy(value)
10000 loops, best of 3: 72.4 µs per loop
100 loops, best of 3: 5.37 ms per loop

并且似乎更快一些。希望对您有所帮助。

【讨论】:

  • 这看起来是系列输入的更快解决方案,但无法将其应用于 DataFrame
  • 嗨。伟大的。编辑了我的回复以显示我将如何做(假设我正确理解了您的问题)。由于某种原因,当它被分配给数据帧时,执行速度会减慢很多。也许存储中间列表更快?