Negociar não é de graça
Um backtest que negocia de graça superestima toda estratégia, e quanto mais negocia, mais mente. Os custos reais são a corretagem, taxas de bolsa e regulatórias e, acima de tudo, o spread entre compra e venda cruzado a cada vez. Costumam ser cotados em pontos-base do valor negociado: 1 pb é 0,01%. Uma ida e volta a 5 pb por lado custa 10 pb, o que uma estratégia que ganha 3 pb por dia não aguenta se trocar de lado todo dia.
def trade_cost(notional, bps):
return abs(notional) * bps / 10000
print(trade_cost(25000, 5)) # 12.5 buying 25,000 at 5 bps
print(trade_cost(-25000, 5)) # 12.5 selling costs the same
print(trade_cost(1_000_000, 1)) # 100.0 one basis point on a millionGiro: quanto você negocia
O custo depende de quanto a posição muda, não do seu tamanho. Com a posição como fração da conta, o giro de um dia é a mudança absoluta em relação à posição anterior: abs(position[t] - position[t - 1]), partindo de zero no dia 0. Continuar comprado não custa nada após a primeira compra; passar de comprado a vendido negocia a conta duas vezes. Somado em um ano, o giro é o primeiro número a olhar em qualquer estratégia.
import numpy as np
positions = np.array([1.0, 1.0, -1.0, -1.0, 0.0]) # long, long, flip to short, short, flat
turnover = np.abs(np.diff(positions, prepend=0.0)) # day 0 starts from a flat position
print(turnover) # [1. 0. 2. 0. 1.]
print(turnover.sum()) # 4.0 accounts traded over the five daysRetornos líquidos e custo de equilíbrio
O custo do dia, como fração da conta, é turnover * bps / 10000, e o retorno líquido é o bruto menos isso. Um número útil é o custo de equilíbrio: o custo por unidade negociada em que o lucro total é exatamente zero, sum(gross) / sum(turnover) * 10000. Compare-o com o que você paga de verdade. Uma estratégia com equilíbrio de 2 pb está morta na prática; uma de 40 pb tem folga.
# Gross and net returns of a rule that flips often, at 5 bps per unit traded
import numpy as np
rng = np.random.default_rng(11)
positions = np.where(rng.random(250) > 0.5, 1.0, -1.0) # a coin-flip position, flipping often
gross = rng.normal(0.0015, 0.005, size=250) # a strategy with a real daily edge before costs
turnover = np.abs(np.diff(positions, prepend=0.0))
net = gross - turnover * 5 / 10000
print(round(float(gross.sum()), 4), round(float(net.sum()), 4))
print(round(float(turnover.sum()), 1), "accounts traded;", round(float(gross.sum() / turnover.sum() * 10000), 2), "bps break-even")# The break-even cost of a small example
import numpy as np
gross = np.array([0.004, 0.002, 0.003, 0.001, 0.002])
positions = np.array([1.0, 1.0, -1.0, -1.0, 0.0])
turnover = np.abs(np.diff(positions, prepend=0.0))
breakeven = gross.sum() / turnover.sum() * 10000
print(round(float(breakeven), 2)) # 30.0 bps per unit traded
print(abs(round(float((gross - turnover * breakeven / 10000).sum()), 10))) # 0.0: the whole profit is gone- Os custos são cotados em pontos-base do valor negociado:
abs(notional) * bps / 10000, igual para compra e venda. - O giro é
abs(mudança de posição)(uma virada negocia 2); retorno líquido = bruto - giro * pb / 10000. - O custo de equilíbrio
sum(gross) / sum(turnover) * 10000diz quanto custo uma estratégia aguenta; compare com o real.
A seguir: o deslizamento, a diferença entre o preço esperado e o preço obtido.
Lab: o que é avaliado
- Precificar uma negociação com
trade_cost25 pts - Contar o que é negociado com
turnover25 pts - Descontar a conta com
net_returns25 pts - Achar o custo que a mata com
breakeven_bps25 pts