Подтвердить что ты не робот

Рекурсия: значение учетной записи с распределением

Обновить: не уверен, что это возможно без какой-либо формы цикла , но 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

Проблема заключается в том, что в настоящее время я не вижу, как векторизация возможна, поскольку последовательное значение зависит от того, было ли распределение сделано за предыдущий месяц. Я получаю желаемый результат, но довольно неэффективно для более высоких частотных данных или больших периодов времени. введите описание изображения здесь

4b9b3361

Ответ 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

и выглядит несколько быстрее. Надеюсь, что это поможет.