Relación riesgo-recompensa: definida y probada

La relación riesgo-recompensa mide la ganancia potencial por cada dólar arriesgado. Es la relación entre el valor en riesgo y el objetivo de beneficio. Por ejemplo, si compra una acción por 10 con un objetivo de ganancias de 12 y establece un stop-loss en 9, la relación riesgo-recompensa es 1:2 porque está arriesgando 1 para ganar $2.

En esta publicación, desarrollaré la definición anterior de la relación riesgo-recompensa, por qué es solo la mitad de la ecuación y probaré cómo funcionan varias relaciones riesgo-recompensa para una estrategia de impulso de acciones. Bastante trading los libros dicen que trading es fácil: si es disciplinado y siempre obtiene ganancias mayores que sus pérdidas, puede perder con más frecuencia de lo que gana y aun así ganar dinero. Pongamos esto a prueba.

Además, gracias a Aswath Damodaran por presentarme una gran representación del riesgo: el símbolo chino de crisis y oportunidad.

¿Qué es la relación riesgo-recompensa?

La relación riesgo-recompensa, a menudo abreviada como RRR, es la cantidad de dólares en riesgo en comparación con la ganancia potencial. La mayoría de los comerciantes apuntan a un RRR, como 1:2, antes de colocar un trade. Colocando un “RRR” dirigido trade tomará tres órdenes o una orden de soporte. Repasemos estas órdenes en detalle.

La cantidad de dólares en riesgo, o el valor en riesgo, es la cantidad total de dinero que una trader puede perder en un trade determinado por el detener la pérdida deNo incluído deslizamiento:

Riesgo = (Precio de compra – Stop-Loss) * Acciones

El beneficio potencial, o el objetivo de beneficio, es la diferencia entre la orden de venta, generalmente colocada como una orden limitada, y el precio de compra:

Beneficio potencial = (Orden limitada – Precio de compra) * Acciones

Vea la imagen de ejemplo a continuación para ver una captura de pantalla de AMD de TradingView que demuestra una relación de riesgo (rojo):recompensa (verde) de 2.0.

Ejemplo de órdenes de relación riesgo-recompensa

Necesitaríamos colocar tres órdenes con nuestro corredor para apuntar a un RRR cuando «vamos en largo»:

  • Una orden de entrada generalmente establecida como límite o stop-limit
  • Una orden de lado bajo que limita el riesgo, generalmente colocada como un límite de pérdida
  • Una orden de lado alto de toma de ganancias, generalmente establecida como una orden limitada para tomar ganancias
  • Colocaríamos lo contrario al “ir corto”:

  • Una orden de entrada generalmente establecida como límite o stop-limit
  • Una orden de lado alto que limita el riesgo, generalmente colocada como un límite de pérdida
  • Una orden de lado bajo para obtener ganancias, generalmente establecida como una orden limitada para obtener ganancias.
  • Algunos corredores admiten órdenes de soporte, que simplifica la gestión del targeting de riesgo-recompensa. En lugar de realizar tres pedidos, puede crear un pedido de paréntesis.

    Entonces, ¿cuál debería ser su relación riesgo-recompensa? Sin conocer tu porcentaje de victorias, no puedes establecer un RRR óptimo:

    Relación riesgo-recompensa: solo la mitad de la imagen

    Una relación riesgo-recompensa de 1:2 puede parecer excelente en teoría, pero si solo tiene éxito con 1:4 de sus operaciones, perderá dinero constantemente. ¡Eso no es lo que queremos!

    Tenemos que traer el porcentaje promedio de victorias para el trade. Tenemos un RRR de 1:2 y un porcentaje de victorias del 50 %. Una vez más, una RRR de 1:2 significa que estamos arriesgando 1,00 para hacer 2,00. Si nuestras operaciones tienen éxito el 50 % de las veces, podemos calcular lo que esperamos hacer en cada una de ellas. trade en el largo plazo:

    Expectativa = Ganancia promedio _ Porcentaje de ganancia – Pérdida promedio _ Porcentaje de pérdida
    Expectativa = 2.0 _ 50% – 1.0 _ 50%
    Expectativa = 100% – 50%
    Expectativa = 50%

    Esta expectativa indicaría que deberíamos esperar 0,50 por cada 1,00 de riesgo. Las matemáticas también tienen sentido lógico. En promedio, tendremos uno ganador trade y uno perdiendo trade. Si ganamos 2,00 en uno trade y pierde 1.00 en el siguiente tradeharemos 1,00 en dos operaciones o 0,50 por trade. Recuerde, solo estamos haciendo 2,00 y no 3,00 ya que 3,00 es nuestro objetivo, pero 3,00 – $1,00 es nuestra ganancia.

    El tiempo también es un factor crucial aquí. si dos trade las configuraciones tienen la misma expectativa, pero una trade ocurre el doble de frecuente, la mayor frecuencia trade hará el doble de la ganancia.

    La relación riesgo-recompensa óptima

    Veamos cómo se comporta nuestra estrategia de impulso de la renta variable en diversas relaciones riesgo-recompensa.

    Nuestra estrategia parece vencer al SPY. Vea las imágenes a continuación para una parada del 10% y una parada de 2 ATR usando una relación riesgo-recompensa de 1:2. Ordené los resultados primero por P&L y luego por porcentaje de ganancias.Rrrwin%stoppnldrawdown40.58%25%$ 44,411.8027.41%30.58%25%$ 43,979.3927.40%20.58%20%$ 43,1111.4925.52%30.58%20%$ 43,098.2926.11.11%40.58%20%) 54%10.59%25%$ 42,697.6227.72%20.58%15%$ 41,317.2028.68%10.58%20%$ 40,882.0626.02%10.59%15%$ 39,924.1327.72%40.58%15%$ 38,58.5128. 40.56%10%$ 37,107.7626.50%30.56%10%$ 36,983.2926.95%20.56%10%$ 36,174.6225.71%10.58%10%$ 29,568.0025.95%40.47%5%$ 28,370.3021.49%30.47%5%$ 261.12. 5%$ 23,676.8719.53%10.56%5%$ 17,756.5620.65%rrrwin%stoppnldrawdown159%15%$ 39,924.1327.72%159%25%$ 42,697.6227.72%158%20%$ 40,4820626.02 25%$43,979.3927.40%458%25%$44,411.827.41%358%20%$43,098.2926.11%158%10%$29,568.025.95%258%20%$43,111.4925.52%458%20%$42,881.65%28% $ 41,317.2028.68%458%15%$ 38,878.5128.17%358%15%$ 38,766.1328.49%156%5%$ 17,756.5620.65%256%10%$ $ 36,6225.71%356%10%$ 36,983. 50%249%5%$23.676,8719,53%347%5%$26.661,1822,11%447%5%$28.370,3021,49%RRRWIN%ATRP & LMAX DISFOWN10.59%5 $ 41,386.9627.10%20.57%5 $ 41,157.9927.75%30.57%5 $ 40,034.0728.22%10.58%4 $ 38,716.6826.62%30.56%%48,23. $37,881.3426.51%40.57%5$37,637.3127.92%40.53%3$35,955.1326.12%30.53%3$35,838.2225.18%20.54%3$35,420.9524.89%40.48%2$30,487.5622.30%10.57%3$29,660.2722.45%30.48%2$29,253.4622 .47%20.49%2 $ 28,330.3921.87%10.56%2 $ 27,552.520.18%40.36%1 $ 21,250.917.18%30.38%1 $ 19,331.2117.27%20.42%1 $ 16,188.8917.34%) 5 $ 41,386.9627.10%158%4 $ 38,716.6826.62%257%5 $ 41,157.9927.75%357%5 $ 40,034.0728.22%457%5 $ 37,637.3127.92%157%3 $ 299660.27.27. $ 27,552.520.18%356%4 $ 38,639.2327.63%456%4 $ 38,422.2227.85%155%1 $ 10,905.3416.06%254%3 $ 35,420.9524.89%353%3 $ 35,2225.1818% .87%348%2$29,253.4622.47%448%2$30,487.5622.30%242%1$16,188.8917.34%338%1$19,331.2117.27%436%1$21,250.9017.18%

    Relación riesgo-recompensa resumida

    Si bien nuestra estrategia genera dinero y supera al índice de referencia, no se debe a ninguna magia de relación riesgo-recompensa o stop-loss: se debe al impulso. Esto se ve fácilmente a continuación al establecer nuestro stop-loss en 99 %, que es un indicador de que no hay stop-loss o toma de ganancias RRR:RRRWin%StopP&LMax Drawdown20.58%99%$45,448.7224.60%

    Así que la próxima vez que alguien te diga trading es fácil y todo lo que necesita es una excelente administración del dinero, profundice. Tome lo que dicen y pruébelo en múltiples marcos de tiempo, instrumentos, estrategias y mercados. No estoy diciendo que la orientación RRR y las órdenes de soporte no funcionen, ¡pero estoy diciendo que pruebe todo! Además, invertir y trading puede ser el juego más competitivo de la ciudad; en otras palabras, no es fácil.

    El código

    Como siempre, el código estará en el Analizando Alfa Github

    El código es casi idéntico al stop loss óptimo para acciones publicadas anteriormente. La única diferencia es que rebalanceamos semanalmente en lugar de mensualmente; He añadido una orden de toma de ganancias y una trade porcentaje de ganancia. Elegí mostrar cada uno de los pedidos individuales, pero podríamos haber usado un solo orden de paréntesis en cambio.

    from datetime import datetime, timedelta
    import math
    import backtrader as bt
    from positions.securities import get_security_data, get_securities_data,\
                                                        get_sp500_tickers
    from indicators.momentum import momentum
    
    START_DATE = '2010-01-01'
    END_DATE = '2019-12-31'
    START = datetime.strptime(START_DATE, '%Y-%m-%d')
    END = datetime.strptime(END_DATE, '%Y-%m-%d')
    BENCHMARK_TICKER = 'SPY'
    
    EXCLUDE_WINDOW = 10
    MOMENTUM_WINDOW = 90
    MINIMUM_PERIOD = MOMENTUM_WINDOW + EXCLUDE_WINDOW
    POSITIONS = 20
    USE_ATR = False
    
    class Momentum(bt.ind.OperationN):
        lines = ('trend',)
        params = dict(period=MINIMUM_PERIOD,
                      exclude_window=EXCLUDE_WINDOW)
        func = momentum
    
        def __init__(self):
            self.addminperiod(self.p.period)
            self.exclude_window = self.p.exclude_window
    
    
    class Strategy(bt.Strategy):
        params = dict(
            num_positions=POSITIONS,
            use_atr=USE_ATR,
            rrr=2.0,
            stop_loss=0.05,
            atr_factor=3.0,
            when=bt.timer.SESSION_START,
            timer=True,
            weekdays=[1],
            weekcarry=True,
            momentum=Momentum,
            momentum_period=MINIMUM_PERIOD
        )
    
        def __init__(self):
            self.d_with_len = []
            self.orders = {}
            self.inds = {}
            self.rebalance_date = None
            self.add_timer(
                when=self.p.when,
                weekdays=self.p.weekdays,
                weekcarry=self.p.weekcarry
            )
            for d in self.datas[1:]:
                self.orders[d] = []
                self.inds[d] = {}
                self.inds[d]['momentum'] = self.p.momentum(d,
                                                           period=MINIMUM_PERIOD,
                                                           plot=False)
                self.inds[d]['atr'] = bt.indicators.ATR(d,
                                                        period=14)
    
        def prenext(self):
            # Add data for datas that meet preprocessing requirements
            # And call next even though data is not available for all tickers
            self.d_with_len = [d for d in self.datas[1:] if len(d)]
    
            if len(self.d_with_len) >= self.p.num_positions:
                self.next()
    
        def nextstart(self):
            # This is only called once when all data is present
            # So we are not unnecessarily calculating d_with_len
            self.d_with_len = self.datas[1:]
            self.next()
            print("All datas loaded")
    
        def next(self):
            if self.rebalance_date:
                today = self.data.datetime.date(ago=0)
                buy_date = self.rebalance_date + timedelta(days=1)
                if today == buy_date:
                    #print("BUY DATE: ", buy_date)
                    self.rebalance_buy()
    
        def notify_timer(self, timer, when, *args, **kwargs):
            if len(self.d_with_len) >= self.p.num_positions:
                self.rebalance_sell()
    
        def rebalance_sell(self):
            self.rebalance_date = self.data.datetime.date(ago=0)
            self.rankings = list(self.d_with_len)
            self.rankings.sort(key=lambda s: self.inds[s]['momentum'][0],
    
                               reverse=True)
            for i, d in enumerate(self.rankings):
                if self.getposition(d).size != 0:
                    if i >= self.p.num_positions:
                        self.close(d, ticker=d.p.name)
                        for o in self.orders[d]:
                            if o and o.status == o.Accepted and \
                                    (o.getordername() == 'Stop' or
                                     o.getordername() == 'Limit'):
                                self.cancel(o)
    
            # Rank according to momentum and return stock list
            # Buy stocks with remaining cash
    
        def rebalance_buy(self):
            positions = 0
            for d in self.datas:
                if self.getposition(d).size != 0:
                    positions += 1
    
            if positions < self.p.num_positions:
                pos_value = self.broker.get_cash() / (self.p.num_positions - positions)
                for i, d in enumerate(self.rankings[:self.p.num_positions]):
                    if self.getposition(d).size == 0 and \
                            not math.isnan(self.inds[d]['momentum'][0]) > 0 and \
                            pos_value > d.close[0]:
                        buy_size = pos_value // d.close[0]
    
                        buy_order = self.buy(d,
                                             size=buy_size,
                                             transmit=False,
                                             ticker=d.p.name)
    
                        if self.p.use_atr:
                            sell_price = d.close[0] + self.inds[d]['atr'][0] * self.p.atr_factor * self.p.rrr
                            stop_price = d.close[0] - self.inds[d]['atr'][0] * self.p.atr_factor
                            stop_loss = (self.inds[d]['atr'][0] * self.p.atr_factor) / d.close[0]
                        else:
                            sell_price = (1.0 + self.p.stop_loss * self.p.rrr) * d.close[0]
                            stop_price = (1.0 - self.p.stop_loss) * d.close[0]
                            stop_loss = self.p.stop_loss
    
                        sell_order = self.sell(d,
                                                price=sell_price,
                                                size=buy_order.size,
                                                exectype=bt.Order.Limit,
                                                transmit=False,
                                                parent=buy_order,
                                                ticker=d.p.name)
    
                        stop_order = self.sell(d,
                                                price=stop_price,
                                                size=buy_order.size,
                                                exectype=bt.Order.Stop,
                                                transmit=True,
                                                parent=buy_order,
                                                ticker=d.p.name)
    
                        self.orders[d].append(sell_order)
                        self.orders[d].append(stop_order)
    
        def stop(self):
            self.ending_value = round(self.broker.get_value(), 2)
            self.PnL = round(self.ending_value - startcash, 2)
    
    if __name__ == '__main__':
        startcash = 10000
        cerebro = bt.Cerebro(stdstats=False, optreturn=False)
    
        # Add Benchmark (datas[0])
        benchmark = get_security_data(BENCHMARK_TICKER, START, END)
        benchdata = bt.feeds.PandasData(dataname=benchmark,
                                        name='SPY',
                                        plot=False)
        cerebro.adddata(benchdata)
    
        # Add Securities (datas[1:])
        tickers = get_sp500_tickers()
        securities = get_securities_data(tickers, START_DATE, END_DATE)
    
        # Add securities as datas1:
        for ticker, data in securities.groupby(level=0):
            if len(data) < MINIMUM_PERIOD:
                print(f"Skipping: ticker {ticker} with length{len(data)} \
                    does not meet the minimum length of {MINIMUM_PERIOD}.")
                continue
    
            print(f"Adding ticker: {ticker}.")
            d = bt.feeds.PandasData(dataname=data.droplevel(level=0),
                                    name=ticker,
                                    plot=False)
            d.plotinfo.plotmaster = benchdata
            d.plotinfo.plotlinelabels = True
    
            cerebro.adddata(d)
    
        print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
    
        # Add Strategy
        if USE_ATR:
            cerebro.optstrategy(Strategy,
                                rrr=(1, 2, 3, 4),
                                atr_factor=(1, 2, 3, 4, 5))
        else:
            cerebro.optstrategy(Strategy,
                                rrr=(1, 2, 3, 4),
                                stop_loss=(0.05, 0.10, 0.15, 0.20, 0.25))
    
        # Add observers & analyzers
        cerebro.addobserver(bt.observers.CashValue)
        cerebro.addobserver(bt.observers.Benchmark,
                            data=benchdata,
                            _doprenext=True,
                            timeframe=bt.TimeFrame.NoTimeFrame)
        cerebro.addanalyzer(bt.analyzers.Returns)
        cerebro.addanalyzer(bt.analyzers.DrawDown)
    
        cerebro.addobserver(bt.observers.Trades)
        cerebro.addobserver(bt.observers.BuySell)
    
        # Analyze the trades
        cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')
    
        # Run optimization
        opt_results = cerebro.run(tradehistory=False)
    
        # Generate results list
        final_results_list = []
    
        for run in opt_results:
            for strategy in run:
                value = strategy.ending_value
                PnL = strategy.PnL
                if USE_ATR:
                    stop_loss = strategy.p.atr_factor
                else:
                    stop_loss = strategy.p.stop_loss
                rrr = strategy.p.rrr
                trades = strategy.analyzers.trades.get_analysis()
                total_trades = trades.total.closed
                total_won = trades.won.total
                perc_win = total_won / total_trades
                drawdown = strategy.analyzers.drawdown.get_analysis()['max']['drawdown']
                final_results_list.append([rrr, perc_win, stop_loss, PnL, drawdown])
    
                print(f"Strategy Total Return: {strategy.analyzers.returns.get_analysis()['rtot']}")
    
        #Sort Results List
        by_PnL = sorted(final_results_list, key=lambda x: x[3], reverse=True)
        by_win = sorted(final_results_list, key=lambda x: x[1], reverse=True)
    
        #Print results
        print('Results: Ordered by Profit:')
        for result in by_PnL:
            print('| RRR | Win% | Stop | PnL | Drawdown|')
            print('| {}  | {}%  | {} | {} | {}'.format(
                result[0],
                round(result[1], 2),
                result[2],
                round(result[3], 2),
                round(result[4], 2)))
    
        print('Results: Ordered by Win%:')
        for result in by_win:
            print('| RRR | Win% | Stop | PnL | Drawdown|')
            print('| {}  | {}%  | {} | {} | {}'.format(
                result[0],
                round(result[1], 2),
                result[2],
                round(result[3], 2),
                round(result[4], 2)))
    

    Deja un comentario