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»:
Colocaríamos lo contrario al “ir corto”:
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)))