Handeln ist nicht gratis
Ein Backtest, der gratis handelt, überschätzt jede Strategie, und je mehr sie handelt, desto mehr lügt er. Reale Kosten sind die Broker-Provision, Börsen- und Aufsichtsgebühren und vor allem der Spread, den man jedes Mal überquert. Sie werden meist in Basispunkten des Handelswerts angegeben: 1 bp sind 0,01 %. Ein Round-Trip mit je 5 bp kostet 10 bp, was eine Strategie mit 3 bp Tagesgewinn nicht überlebt, wenn sie täglich dreht.
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 millionUmschlag: wie viel Sie handeln
Die Kosten hängen davon ab, wie stark sich die Position ändert, nicht von ihrer Größe. Ist die Position ein Anteil des Kontos, ist der Umschlag eines Tages die absolute Änderung gegenüber der vorherigen Position: abs(position[t] - position[t - 1]), an Tag 0 ab flach. Long zu bleiben kostet nach dem ersten Kauf nichts; von Long auf Short zu wechseln handelt das Konto zweimal. Über ein Jahr summiert ist der Umschlag die erste Zahl, die man bei jeder Strategie ansieht.
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 daysNettorenditen und Break-even-Kosten
Die Kosten eines Tages als Anteil des Kontos sind turnover * bps / 10000, und die Nettorendite ist die Bruttorendite minus diese Kosten. Eine nützliche Einzelzahl ist der Break-even-Kostensatz: der Kostensatz je gehandelter Einheit, bei dem der Gesamtgewinn genau null ist, sum(gross) / sum(turnover) * 10000. Vergleichen Sie ihn mit dem, was Sie wirklich zahlen. Eine Strategie mit 2 bp Break-even ist praktisch tot, eine mit 40 bp hat Spielraum.
# 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- Kosten werden in Basispunkten des Handelswerts angegeben:
abs(notional) * bps / 10000, für Kauf und Verkauf gleich. - Der Umschlag ist
abs(Positionsänderung)(ein Wechsel handelt 2); Nettorendite = brutto - Umschlag * bp / 10000. - Der Break-even-Kostensatz
sum(gross) / sum(turnover) * 10000zeigt, wie viel Kosten eine Strategie verträgt; vergleichen Sie ihn mit dem echten.
Als Nächstes: Slippage, die Lücke zwischen erwartetem und erhaltenem Preis.
Lab: Was bewertet wird
- Mit
trade_costeinen Handel beziffern25 Pkt. - Mit
turnoverdas Gehandelte zählen25 Pkt. - Mit
net_returnsdie Rechnung abziehen25 Pkt. - Mit
breakeven_bpsdie Kosten finden, die sie töten25 Pkt.