Рекурсия: значение учетной записи с распределением
Обновить: не уверен, что это возможно без какой-либо формы цикла , но np.where
здесь не работает. Если ответ: "вы не можете", то пусть будет так. Если это можно сделать, оно может использовать что-то из scipy.signal
.
Я хотел бы векторизовать цикл в коде ниже, но не знаю, как из-за рекурсивного характера вывода.
Прогулка - хотя из моей текущей настройки:
Возьмите стартовую сумму (1 миллион долларов США) и ежеквартальное распределение доллара (5000 долларов США):
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])
В psuedocode то, что делает цикл, это просто:
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
Проблема заключается в том, что в настоящее время я не вижу, как векторизация возможна, поскольку последовательное значение зависит от того, было ли распределение сделано за предыдущий месяц. Я получаю желаемый результат, но довольно неэффективно для более высоких частотных данных или больших периодов времени.
![введите описание изображения здесь]()
Ответы
Ответ 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 совокупный продукт для всех ежемесячных возвратов.
- 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 суммарная сумма указанного раздела
- 1 умножение указанной суммы со скаляром
dist
- 1 вычитание скалярного
v0
с помощью dist * t
- 1 точечное умножение
cum_r
с v0 - dist * t
Ответ 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
и выглядит несколько быстрее. Надеюсь, что это поможет.