Reequilibrio de la cartera con el método de ancho de banda en Python

Necesitamos calcular una cartera continuamente rebalanceada de 2 acciones. Llamémoslos A y B. Ambos tendrán una parte igual de la cartera. Entonces, si tengo 100 $ en mi cartera, 50 $ se invierten en A y 50 $ en B. Como ambas acciones tienen un rendimiento muy diferente, no mantendrán sus pesos iguales (después de 3 meses, A puede valer 70 $ mientras que B se redujo a 45 PS El problema es que tienen que mantener su parte de la cartera dentro de un cierto ancho de banda de tolerancia. Este ancho de banda es del 5%. Entonces necesito una función que lo haga: Si A> B * 1.05 o A * 1.05 <B, entonces reequilibra.

Esta primera parte solo sirve para obtener de la manera más rápida algunos datos para tener una base común de discusión y hacer que los resultados sean comparables, de modo que solo puede copiar y pegar todo este código y funciona para usted.

import pandas as pd from datetime import datetime import numpy as np df1 = pd.io.data.get_data_yahoo("IBM", start=datetime(1970, 1, 1), end=datetime.today()) df1.rename(columns={'Adj Close': 'ibm'}, inplace=True) df2 = pd.io.data.get_data_yahoo("F", start=datetime(1970, 1, 1), end=datetime.today()) df2.rename(columns={'Adj Close': 'ford'}, inplace=True) df = df1.join(df2.ford, how='inner') del df["Open"] del df["High"] del df["Low"] del df["Close"] del df["Volume"] 

Ahora comience a calcular el rendimiento relativo de cada acción con la fórmula: df.ibm/df.ibm[0]. El problema es que tan pronto como rompemos el primer ancho de banda, necesitamos restablecer el 0 en nuestra fórmula: df.ibm/df.ibm[0], ya que reequilibramos y necesitamos comenzar a calcular a partir de ese momento. Así que usamos df.d para esta función de marcador de posición y la igualamos a df.t tan pronto como se rompe el ancho de banda, df.t básicamente solo cuenta la longitud del dataframe y, por lo tanto, siempre puede decirnos “dónde estamos”. Así que aquí comienza el cálculo real:

 tol = 0.05 #settintg the bandwidth tolerance df["d"]= 0 # df["t"]= np.arange(len(df)) tol = 0.3 def flex_relative(x): if df.ibm/df.ibm.iloc[df.d].values  df.ford/df.ford.iloc[df.d].values * (1+tol): return df.iloc[df.index.get_loc(x.name) - 1]['d'] == df.t else: return df.ibm/df.ibm.iloc[df.d].values, df.ford/df.ford.iloc[df.d].values df["ibm_performance"], df["ford_performance"], = df.apply(flex_relative, axis =1) 

El problema es que recibo este error desde la última línea de código, donde trato de aplicar la función con df.apply(flex_relative, axis =1)

ValueError: ('The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().', u'occurred at index 1972-06-01 00:00:00') El problema es que ninguna de las opciones dadas de la statement de error resuelve mi problema, así que realmente no sé qué hacer …

Lo único que encontré hasta ahora fue el enlace a continuación, pero llamar a una función R no funcionará porque necesito aplicarlo a conjuntos de datos bastante grandes y también puedo implementar una optimización en esta función, por lo que definitivamente debe ser construido en python. Aquí está el enlace de todos modos: Finance Lib con el método de optimización de cartera en python

Manualmente (lo que no es una buena manera de manejar big data), calculé que la primera fecha para un rebalanceo sería: 03.11.1972 00:00:00

La salida del dataframe en el primer rebalanceo debería tener este aspecto:

  ibm ford dt ibm_performance ford_performance 1972-11-01 00:00:00 6,505655 0,387415 0 107 1,021009107 0,959552418 1972-11-02 00:00:00 6,530709 0,398136 0 108 1,017092172 0,933713605 1972-11-03 00:00:00 6,478513 0,411718 0 109 1,025286667 0,902911702 # this is the day, the rebalancing was detected 1972-11-06 00:00:00 6,363683 0,416007 109 110 1,043787536 0,893602752 # this is the day the day the rebalancing is implemented, therefore df.d gets set = df.t = 109 1972-11-08 00:00:00 6,310883 0,413861 109 111 1,052520384 0,898236364 1972-11-09 00:00:00 6,227073 0,422439 109 112 1,066686226 0,879996875 

¡Muchas gracias por tu ayuda!

@Alexander: Sí, el rebalanceo tendrá lugar al día siguiente.

@maxymoo: Si implementa este código después del suyo, obtiene los pesos de cartera de cada acción y no se encuentran entre el 45 y el 55%. Es más bien entre el 75% y el 25%:

 df["ford_weight"] = df.ford_prop*df.ford/(df.ford_prop*df.ford+df.ibm_prop*df.ibm) #calculating the actual portfolio weights df["ibm_weight"] = df.ibm_prop*df.ibm/(df.ford_prop*df.ford+df.ibm_prop*df.ibm) print df print df.ibm_weight.min() print df.ibm_weight.max() print df.ford_weight.min() print df.ford_weight.max() 

Intenté no por una hora más o menos para arreglarlo, pero no lo encontré.

¿Puedo hacer algo para aclarar esta pregunta?

La idea principal aquí es trabajar en términos de dólares en lugar de proporciones. Si realiza un seguimiento de la cantidad de acciones y los valores relativos en dólares de las acciones de ibm y ford, entonces puede express el criterio de rebalanceo como

 mask = (df['ratio'] >= 1+tol) | (df['ratio'] <= 1-tol) 

donde la razón es igual

  df['ratio'] = df['ibm value'] / df['ford value'] 

y df['ibm value'] , y df['ford value'] representan valores reales en dólares.


 import datetime as DT import numpy as np import pandas as pd import pandas.io.data as PID def setup_df(): df1 = PID.get_data_yahoo("IBM", start=DT.datetime(1970, 1, 1), end=DT.datetime.today()) df1.rename(columns={'Adj Close': 'ibm'}, inplace=True) df2 = PID.get_data_yahoo("F", start=DT.datetime(1970, 1, 1), end=DT.datetime.today()) df2.rename(columns={'Adj Close': 'ford'}, inplace=True) df = df1.join(df2.ford, how='inner') df = df[['ibm', 'ford']] df['sh ibm'] = 0 df['sh ford'] = 0 df['ibm value'] = 0 df['ford value'] = 0 df['ratio'] = 0 return df def invest(df, i, amount): """ Invest amount dollars evenly between ibm and ford starting at ordinal index i. This modifies df. """ c = dict([(col, j) for j, col in enumerate(df.columns)]) halfvalue = amount/2 df.iloc[i:, c['sh ibm']] = halfvalue / df.iloc[i, c['ibm']] df.iloc[i:, c['sh ford']] = halfvalue / df.iloc[i, c['ford']] df.iloc[i:, c['ibm value']] = ( df.iloc[i:, c['ibm']] * df.iloc[i:, c['sh ibm']]) df.iloc[i:, c['ford value']] = ( df.iloc[i:, c['ford']] * df.iloc[i:, c['sh ford']]) df.iloc[i:, c['ratio']] = ( df.iloc[i:, c['ibm value']] / df.iloc[i:, c['ford value']]) def rebalance(df, tol, i=0): """ Rebalance df whenever the ratio falls outside the tolerance range. This modifies df. """ c = dict([(col, j) for j, col in enumerate(df.columns)]) while True: mask = (df['ratio'] >= 1+tol) | (df['ratio'] <= 1-tol) # ignore prior locations where the ratio falls outside tol range mask[:i] = False try: # Move i one index past the first index where mask is True # Note that this means the ratio at i will remain outside tol range i = np.where(mask)[0][0] + 1 except IndexError: break amount = (df.iloc[i, c['ibm value']] + df.iloc[i, c['ford value']]) invest(df, i, amount) return df df = setup_df() tol = 0.05 invest(df, i=0, amount=100) rebalance(df, tol) df['portfolio value'] = df['ibm value'] + df['ford value'] df['ibm weight'] = df['ibm value'] / df['portfolio value'] df['ford weight'] = df['ford value'] / df['portfolio value'] print df['ibm weight'].min() print df['ibm weight'].max() print df['ford weight'].min() print df['ford weight'].max() # This shows the rows which trigger rebalancing mask = (df['ratio'] >= 1+tol) | (df['ratio'] <= 1-tol) print(df.loc[mask]) 

Puede utilizar este código para calcular su cartera en cada momento.

 i = df.index[0] df['ibm_prop'] = 0.5/df.ibm.ix[i] df['ford_prop'] = 0.5/df.ford.ix[i] while i: try: i = df[abs(1-(df.ibm_prop*df.ibm + df.ford_prop*df.ford)) > tol].index[0] except IndexError: break df['ibm_prop'].ix[i:] = 0.5/df.ibm.ix[i] df['ford_prop'].ix[i:] = 0.5/df.ford.ix[i] 

Solo una mejora matemática en la respuesta de maxymoo:

 i = df.index[0] df['ibm_prop'] = df.ibm.ix[i]/(df.ibm.ix[i]+df.ford.ix[i]) df['ford_prop'] = df.ford.ix[i]/(df.ibm.ix[i]+df.ford.ix[i]) while i: try: i = df[abs((df.ibm_prop*df.ibm - df.ford_prop*df.ford)) > tol].index[0] except IndexError: break df['ibm_prop'].ix[i:] = df.ibm.ix[i]/(df.ibm.ix[i]+df.ford.ix[i]) df['ford_prop'].ix[i:] = df.ford.ix[i]/(df.ibm.ix[i]+df.ford.ix[i]) 

¿Qué pasa con esto?

 df["d"]= [0,0,0,0,0,0,0,0,0,0] df["t"]= np.arange(len(df)) tol = 0.05 def flex_relative(x): if df.ibm/df.ibm.iloc[df.d].values < df.ford/df.ford.iloc[df.d].values * (1+tol): return df.iloc[df.index.get_loc(x.name) - 1]['d'] == df.t elif df.ibm/df.ibm.iloc[df.d].values > df.ford/df.ford.iloc[df.d].values * (1+tol): return df.iloc[df.index.get_loc(x.name) - 1]['d'] == df.t