У меня есть фрейм данных с идентификатором безопасности, датой транзакции и количеством.Количество может быть положительным (покупка) или отрицательным (продажа).Я хочу перейти от этого фрейма данных транзакций к фрейму данных холдинга, который в качестве первого прохода выглядит как простой groupby security_id и sum.
НО дата транзакции релевантна для транзакций покупки, потому что при покупке ценной бумаги определяется, когда она выплачивает проценты (1 год с даты покупки).Это означает, что фактически каждая защита имеет многоиндексную комбинацию security_id + транзакция_дата.
Итак, в следующем примере:
df1 = pd.DataFrame({'security_id': [2,2,2,3,3,3,5,5,6], 'transaction_date': ['2015-11-20', '2016-04-01', '2016-11-01', '2015-02-01', '2015-05-01', '2016-03-01', '2015-11-20', '2016-06-01', '2015-02-01'], 'quantity': [20, 30, -40, 25, 35, -15, 50, -50, 35]})
In [28]: df1
Out[28]:
quantity security_id transaction_date
0 20 2 2015-11-20
1 30 2 2016-04-01
2 -40 2 2016-11-01
3 25 3 2015-02-01
4 35 3 2015-05-01
5 -15 3 2016-03-01
6 50 5 2015-11-20
7 -50 5 2016-06-01
8 35 6 2015-02-01
Как вы можете видеть, здесь есть 4 ценные бумаги, которые охватывают каждый соответствующий случай.
У Ценной бумаги 2 сначала есть покупка 20 ценных бумаг, затем 30, а затем продажа 40. Таким образом, чистые авуары для этого должны (с First In First Out) составить 10 ценных бумаг, купленных в 2016-04-01.
Security 3 имеет покупку 25, затем 35, затем продажу 15, поэтому чистые авуары составляют 10 на 2015-02-01 и 35 на 2015-05-01.
Security5 имеет покупку 50, а затем продажу 50, поэтому чистое владение равно 0 (не имеет значения, удалено ли оно из df или осталось там с количеством = 0).
Безопасность 6 имеетпродаж нет, поэтому не должен быть затронут.
Визуально результат, который я хочу, выглядел бы так:
quantity security_id transaction_date
0 10 2 2016-04-01
1 10 3 2015-02-01
2 35 3 2015-05-01
3 35 6 2015-02-01
Я могу добавить столбец "net holdings" с преобразованием:
In [35]: df1['net_holdings'] = df1.groupby('security_id')['quantity'].transform('sum')
In [36]: df1
Out[36]:
quantity security_id transaction_date net_holdings
0 20 2 2015-11-20 10
1 30 2 2016-04-01 10
2 -40 2 2016-11-01 10
3 25 3 2015-02-01 45
4 35 3 2015-05-01 45
5 -15 3 2016-03-01 45
6 50 5 2015-11-20 0
7 -50 5 2016-06-01 0
8 35 6 2015-02-01 35
И удалить продажи, отфильтровав отрицательные строки
In [37]: df1 = df1[df1['quantity']>=0]
Out[37]:
quantity security_id transaction_date net_holdings
0 20 2 2015-11-20 10
1 30 2 2016-04-01 10
3 25 3 2015-02-01 45
4 35 3 2015-05-01 45
6 50 5 2015-11-20 0
8 35 6 2015-02-01 35
И теперь у меня осталось почти то, что я хочу, но все еще нужно сгруппировать ценные бумаги 2 и 3, чтобы 2 убрала однукупленный 2015-11-20, а в случае 3 распределение чистых активов по методу FIFO как 10 длякупленный в 2015-02-01 и 35 для купленного в 2015-05-01.
Я могу как-то увидеть, как куча вложенных циклов if и for справится, но я надеюсь, что большеэлегантное решение существует, прежде чем спуститься в эту кроличью нору.Любая помощь действительно приветствуется!
edit
Так что я решил это, но не очень элегантно.Я создал новый фрейм данных, который отслеживает текущую проданную сумму для каждой ценной бумаги и циклически перебирает каждую строку, дисконтируя эту сумму.работает для каждого варианта, который я тестировал, но это довольно далеко от кошерности.Я с радостью приму предложения по повышению производительности / очистке.это блок кода
df = pd.DataFrame({'security_id': [2,2,2,3,3,3,5,5,6], 'transaction_date': ['2015-11-20', '2016-04-01', '2016-11-01', '2015-02-01', '2015-05-01', '2016-03-01', '2015-11-20', '2016-06-01', '2015-02-01'], 'quantity': [20, 30, -40, 25, 35, -15, 50, -50, 35]})
df['q_sold'] = df.groupby('security_id')['quantity'].transform(lambda x: x[x<0].sum())
#filter out sales from quantity col
df = df[df['quantity']>=0]
df = df.copy()
#sort by transaction date + security_id
df.sort_values(['security_id', 'transaction_date'], inplace=True)
#separate df that tracks running amount sold
df_sales = df.groupby('security_id')['q_sold'].unique().apply(lambda x: x[0])
for idx, sec_id in enumerate(df['security_id']):
df.loc[df.index[idx], 'net_holdings'] = (df.loc[df.index[idx], 'quantity']
+ df_sales[sec_id])
df_sales[sec_id] = df_sales[sec_id] + df.loc[df.index[idx], 'quantity']
if df_sales[sec_id]>=0:
df_sales[sec_id] = 0
df = df[df['net_holdings']>=0]
df.drop(['quantity', 'q_sold'], axis=1, inplace=True)
df.rename(columns={'net_holdings': 'quantity'})
Out[14]:
security_id transaction_date quantity
1 2 2016-04-01 10.0
3 3 2015-02-01 10.0
4 3 2015-05-01 35.0
6 5 2015-11-20 0.0
8 6 2015-02-01 35.0