Backtesting ist bei der Entwicklung quantitativer Handelsstrategien von entscheidender Bedeutung und bietet eine datengesteuerte Möglichkeit, Ideen zu validieren und die Leistung unter historischen Marktbedingungen zu bewerten. Viele unterschätzen jedoch die Komplexität der Nachbildung der tatsächlichen Ausführung und ignorieren häufig Auswirkungen wie Slippage, Geld-Brief-Spanne und Handelskosten. Diese Auslassungen können die Leistungskennzahlen erhöhen und die Robustheit beim Übergang von der Simulation zum Live-Handel verringern.
\ Durch die Einbeziehung von Ausführungsfriktionen und Marktmikrostruktureffekten wird sichergestellt, dass die Backtest-Ergebnisse realistische, praktische Strategien und nicht idealisierte Modelle widerspiegeln. Die wichtigsten Komponenten, die wir oft übersehen und die wir in diesem Artikel analysieren werden, sind:
- Schlupf: Die Differenz zwischen dem erwarteten Handelspreis und dem tatsächlichen Ausführungspreis. Dies ist auf die Marktvolatilität und die Auftragsgröße zurückzuführen und beeinträchtigt häufig die im Backtest ermittelte Rentabilität.
- Geld-Brief-Spanne: Die Transaktionskosten sind in der Differenz zwischen Kauf- und Verkaufspreisen enthalten. Das Ignorieren von Spreads kann zu übermäßig optimistischen Renditen führen, insbesondere bei weniger liquiden Instrumenten.
- Handelskosten: Maklergebühren, Umtauschprovisionen und Finanzierungskosten summieren sich im Laufe der Zeit und wirken sich erheblich auf die Nettoperformance aus. Ihre Vernachlässigung führt zu unrealistisch hohen Renditen und verzerrt die risikobereinigten Kennzahlen.
\ In diesem Artikel werden wir Python für das Backtesting einer relativ einfachen Strategie verwenden, mit den oben genannten Komponenten experimentieren und schließlich die Ergebnisse unseres Backtestings präsentieren und diskutieren.
Extrahieren von Intraday-Daten
Zuerst führen wir unsere Importe durch und stellen die Funktionen zum Abrufen der Preise bereit Die historische Intraday-API von EODHD.
from typing import Dict, List, Any, Callable
import pandas as pd
import itertools
from datetime import datetime, timedelta
import requests
import numpy as np
from itertools import product
import matplotlib.pyplot as plt
from sklearn.linear_model import LinearRegression
token = 'YOUR EODHD API KEY'
def get_prices(symbol: str, from_date: str, to_date: str) -> pd.DataFrame:
url = f'https://eodhd.com/api/intraday/{symbol}'
from_ts = int(pd.to_datetime(from_date, utc=True).timestamp())
to_ts = int(pd.to_datetime(to_date, utc=True).timestamp())
params = {'api_token': token, "fmt": "json", "interval": "5m", "from": from_ts}
resp = requests.get(url, params=params)
df = pd.DataFrame(resp.json())
if df.empty:
return pd.DataFrame(columns=['open', 'low', 'high', 'close', 'volume'])
df['datetime'] = pd.to_datetime(df['datetime'])
df.sort_values(by='datetime', inplace=True)
df.set_index('datetime', inplace=True)
df = df[['open', 'low', 'high', 'close', 'volume']]
return df
def get_prices_day_by_day(symbol: str, start_date: str, end_date: str) -> pd.DataFrame:
start_dt = pd.to_datetime(start_date).normalize()
end_dt = pd.to_datetime(end_date).normalize()
all_prices = []
current_dt = start_dt
while current_dt <= end_dt:
from_str = current_dt.strftime('%Y-%m-%d')
day_df = get_prices(symbol, from_str, end_date)
if day_df.empty:
break
day_df = day_df[day_df.index.normalize() <= end_dt]
day_df = day_df[day_df.index.normalize() >= current_dt]
if not day_df.empty:
all_prices.append(day_df)
max_dt = day_df.index.max().normalize() if not day_df.empty else None
if max_dt is None or max_dt <= current_dt:
break
current_dt = max_dt + timedelta(days=1)
if not all_prices:
return pd.DataFrame(columns=['open', 'low', 'high', 'close', 'volume'])
df = pd.concat(all_prices)
df = df[~df.index.duplicated(keep='first')]
df.sort_index(inplace=True)
return df
df_1min = get_prices_day_by_day("AAPL.US", "2025-01-01", "2026-01-01")
df_1min
Hinweis: Ersetzen YOUR EODHD API KEY mit Ihrem tatsächlichen EODHD-API-Schlüssel. Wenn Sie noch keins haben, können Sie es hier erhalten Eröffnung eines EODHD-Entwicklerkontos.
\ Sie werden feststellen, dass wir nach unseren Importen zwei separate Funktionen erstellen.
\
get_prices: ist die Funktion, die mithilfe von praktisch die 5-Minuten-Kerzen einer Aktie für einen bestimmten Zeitraum ermittelt Intraday-Preisdaten-API.
\
erhaltenPreiseTagvonTag: ist die Funktion, die einige Tage für ein definiertes Start- und Enddatum durchläuft und alle Ergebnisse zu einem einzigen Datenrahmen hinzufügt.
\ Der endgültige Datenrahmen sollte so aussehen:
\
Der Grund, warum wir es so machen, ist, dass typische APIs für historische Daten normalerweise bis zu einer bestimmten Anzahl von Zeilen bereitstellen. In unserem Fall werden wir das gesamte Jahr 2025 backtesten, was bedeutet, dass wir etwa 20.000 Kerzen haben werden, eine Menge, die mit einem einzigen Aufruf nicht abgerufen werden kann. Deshalb werden wir nach und nach anrufen, um die notwendigen Daten einzuholen.
\ Der andere Grund für die Verwendung des 5-Minuten-Zeitrahmens besteht darin, dass wir ihn als Live-Preis-Feed behandeln. Unsere Strategie wird jedoch auf dem 4-Stunden-Zeitrahmen basieren. Aus diesem Grund verwenden wir die Resample-Funktion von Python, um auch den 4H-Zeitrahmen zu berechnen.
resampled = df_1min.resample('4H').agg({
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last',
'volume': 'sum'
}).dropna()
resampled['ma_fast_4h'] = resampled['close'].rolling(window=10, min_periods=1).mean()
resampled['ma_slow_4h'] = resampled['close'].rolling(window=50, min_periods=1).mean()
close_4h = resampled['close']
volume_4h = resampled['volume']
ma_fast_4h = resampled['ma_fast_4h']
ma_slow_4h = resampled['ma_slow_4h']
signal = (ma_fast_4h - ma_slow_4h).clip(-1, 1)
df_1min['close_4h'] = df_1min.index.map(close_4h.reindex(df_1min.index, method='ffill'))
df_1min['volume_4h'] = df_1min.index.map(volume_4h.reindex(df_1min.index, method='ffill'))
df_1min['MA_fast'] = df_1min.index.map(ma_fast_4h.reindex(df_1min.index, method='ffill'))
df_1min['MA_slow'] = df_1min.index.map(ma_slow_4h.reindex(df_1min.index, method='ffill'))
df_1min['signal'] = np.where(df_1min['MA_fast'] > df_1min['MA_slow'], 1,
np.where(df_1min['MA_fast'] < df_1min['MA_slow'], -1, 0))
df_1min
Um alles sauber und leicht verständlich zu halten, werden wir alle Daten, die wir benötigen, im ersten 5-Minuten-Datenrahmen speichern. Bevor wir den Code erklären, sprechen wir über die Strategie.
\ Es wird eine einfache Crossover-Strategie sein, bei der wir zwei gleitende Durchschnitte haben werden, einen schnellen (Periode 10) und einen langsamen (Periode 50). Wenn das Schnelle über dem Langsamen liegt, gehen wir davon aus, dass der Markt einen Aufwärtstrend aufweist, und investieren daher langfristig. Im umgekehrten Szenario investieren wir kurzfristig.
\ Nun erklären wir den Code:
- Zunächst berechnen wir mithilfe der Resample-Funktion die 4-Stunden-Kerzen in einem separaten Datenrahmen.
- Anschließend berechnen wir die beiden gleitenden Durchschnitte und das Signal unserer Strategie, wie oben erläutert
- Abschließend fügen wir diese Daten dem ersten 5-Minuten-Datenrahmen hinzu.
\ Unser Datenrahmen sollte wie folgt aussehen:
Wie Sie sehen, haben wir immer noch die 5-Minuten-Kerzen mit den zusätzlichen 4-Stunden-Säulen. Aus diesem Grund werden Sie feststellen, dass die 4-Stunden-Spalten sich wiederholen und sich nur alle 4 Stunden ändern.
Backtesting-Zeit!
Da wir nun über die Daten zur Umsetzung unserer Strategie verfügen, ist es an der Zeit, die eigentliche Backtesting-Engine zu entwickeln.
def backtest(df, initial_capital=100000, commission_pct=0.001,
spread_pct=0.0005, slippage_base_pct=0.0005):
capital = initial_capital
position = 0.0
entry_price = 0.0
entry_time = None
trades = []
def trade_costs(price, size):
return abs(size * price * commission_pct)
def adjust_price(price, direction, volume_4h, spread_pct, slippage_base_pct):
# Volume slippage
vol_norm = max(volume_4h / 20_000_000, 0.1)
slippage_pct = slippage_base_pct / vol_norm
slippage_pct = min(slippage_pct, 0.005)
half_spread = price * spread_pct / 2
slippage = price * slippage_pct
# Buy high (pay ask), sell low (receive bid)
if direction > 0: # Buy / long entry
return price + half_spread + slippage
else: # Sell / short entry or long exit
return price - half_spread - slippage
sig = 0
for idx, row in df.iterrows():
prev_sig = sig
sig = row['signal']
price_open = row['open']
# Exit on signal reverse OR zero
if position != 0 and sig * position < 0:
direction_out = -1 if position > 0 else 1
adj_exit = adjust_price(row['close'], direction_out, row['volume_4h'], spread_pct, slippage_base_pct)
pnl_gross = position * (adj_exit - entry_price)
costs = trade_costs(adj_exit, abs(position))
pnl_net = pnl_gross - costs
capital += pnl_net
trades.append({
'entry_time': entry_time, 'exit_time': idx,
'entry_price': entry_price, 'exit_price': adj_exit,
'position_size': position, 'pnl_gross': pnl_gross,
'costs': costs, 'pnl_net': pnl_net, 'capital_after': capital
})
position = 0
# Enter new position
if position == 0 and sig != prev_sig and sig != 0:
direction = sig
adj_entry = adjust_price(price_open, direction, row['volume_4h'], spread_pct, slippage_base_pct)
position_size = capital / adj_entry # Fixed sizing
entry_costs = trade_costs(adj_entry, abs(position_size))
if capital > entry_costs:
position = position_size * direction
entry_price = abs(adj_entry)
entry_time = idx
capital -= entry_costs
# Close final position
if position != 0:
last_row = df.iloc[-1]
direction_out = -1 if position > 0 else 1
adj_exit = adjust_price(last_row['close'], direction_out, last_row['volume_4h'], spread_pct, slippage_base_pct)
pnl_gross = position * (adj_exit - entry_price)
costs = trade_costs(adj_exit, abs(position))
pnl_net = pnl_gross - costs
capital += pnl_net
trades.append({
'entry_time': entry_time, 'exit_time': df.index[-1],
'entry_price': entry_price, 'exit_price': adj_exit,
'position_size': position, 'pnl_gross': pnl_gross,
'costs': costs, 'pnl_net': pnl_net, 'capital_after': capital
})
trades_df = pd.DataFrame(trades)
# Metrics (unchanged)
total_return = (capital - initial_capital) / initial_capital * 100
num_trades = len(trades_df)
win_rate = (trades_df['pnl_net'] > 0).mean() * 100 if num_trades > 0 else 0
avg_win = trades_df[trades_df['pnl_net'] > 0]['pnl_net'].mean() if win_rate > 0 else 0
avg_loss = trades_df[trades_df['pnl_net'] < 0]['pnl_net'].mean() if (100 - win_rate) > 0 else 0
profit_factor = abs(avg_win * (win_rate / 100) / (abs(avg_loss) * ((100 - win_rate) / 100))) if avg_loss != 0 else float('inf')
metrics = {
'final_capital': capital,
'total_return_pct': total_return,
'num_trades': num_trades,
'win_rate_pct': win_rate,
'avg_win': avg_win,
'avg_loss': avg_loss,
'profit_factor': profit_factor,
'total_costs': trades_df['costs'].sum()
}
return trades_df, metrics
Wie Sie feststellen werden, verfügen wir über eine Funktion, die unser Anfangskapital und die Prozentsätze der zuvor besprochenen Komponenten berücksichtigt: Provision, Spread und Slippage.
\ Der Code geht praktisch Zeile für Zeile vor und prüft, ob bereits eine offene Position vorhanden ist. Abhängig vom Signal öffnet, schließt er einen Trade oder unternimmt scheinbar nichts, wenn sich nichts geändert hat.
\ Am Ende wird ein detaillierter Datenrahmen mit den Trades und einigen Ergebnismetriken der Strategie zurückgegeben, wie z. B. Rendite, Anzahl der Trades, Gewinnprozentsatz usw.
\ Lassen Sie es uns mit einigen Grundwerten ausführen.
trades_df, metrics = backtest(
df_1min,
initial_capital=50000,
commission_pct=0.0005,
spread_pct=0.0002,
slippage_base_pct=0.0001
)
metrics
\ Die zurückgegebenen Metriken sind die folgenden:
{'final_capital': np.float64(33776.36077208968),
'total_return_pct': np.float64(-32.44727845582063),
'num_trades': 29,
'win_rate_pct': np.float64(37.93103448275862),
'avg_win': np.float64(1132.900075678812),
'avg_loss': np.float64(-1560.2231972177665),
'profit_factor': np.float64(0.44373639954880745),
'total_costs': np.float64(602.5884081034602)}
Wir sehen, dass unsere Strategie 15 Trades erforderte, Geld mit einer Rendite von minus 32 % verlor und die kumulierten Kosten 602 $ betrugen. Wir sollten hier klarstellen, dass die Strategie offensichtlich nicht spannend ist und Geld verliert, aber das Ziel dieses Artikels ist es, die Auswirkungen der Kosten auf eine Strategie aufzuzeigen und nicht, eine erfolgreiche Strategie vorzustellen.
Kostensensitivitätsanalyse: Alle Szenarien ausführen
Jetzt ist es an der Zeit, diese Backtesting-Engine mit zahlreichen möglichen Kostenkombinationen auszuführen.
param_grid = {
'commission_pct': [0.0, 0.0005, 0.001, 0.002],
'spread_pct': [0.0, 0.0002, 0.0005, 0.001],
'slippage_base_pct': [0.0, 0.0002, 0.0005, 0.001]
}
# Generate all combinations
scenarios = list(itertools.product(
param_grid['commission_pct'],
param_grid['spread_pct'],
param_grid['slippage_base_pct']
))
Aus diesem Grund erstellen wir ein Parameterraster mit Kosten beginnend bei Null (um eine Basis für den Vergleich festzulegen). Bitte beachten Sie, dass diese Zahlen, wie beispielsweise maximal 0,2 % für Provision oder 0,1 % für Spread und Slippage, auf dem Markt existieren; Daher haben wir Szenarien, die je nach Broker gültig sind.
\ Jetzt ist es an der Zeit, das Backtesting aller Szenarien durchzuführen:
results_list = []
for i, (comm_pct, spread_pct, slip_pct) in enumerate(scenarios):
trades_df, metrics = backtest(
df_1min,
initial_capital=10000,
commission_pct=comm_pct,
spread_pct=spread_pct,
slippage_base_pct=slip_pct
)
scenario_metrics = {
'commission_pct': comm_pct,
'spread_pct': spread_pct,
'slippage_base_pct': slip_pct,
**metrics
}
results_list.append(scenario_metrics)
print(f"#{i+1:2d}: C{comm_pct:.4f} S{spread_pct:.4f} Slip{slip_pct:.4f} → "
f"{metrics['total_return_pct']:+6.1f}% ({metrics['num_trades']:2d} trades)")
metrics_df = pd.DataFrame(results_list)
metrics_df
Der Code führt alle Szenarien nacheinander aus, speichert die Ergebnisse der Metriken in einem Array und wandelt sie schließlich in einen Datenrahmen um.
\
Die erste Beobachtung ist, dass die Anzahl der Trades über alle Läufe hinweg konstant bleibt. Das ist logisch, denn die einzige Möglichkeit, wie Provisionen die Handelszahlen beeinflussen können, besteht darin, das Signal zu ändern, was hier offenbar nicht der Fall ist.
\ Vergleichen wir zunächst das Nullkosten-Szenario mit einem Szenario, dessen Kosten etwa in der Mitte unseres möglichen Bereichs liegen.
zero_cost = metrics_df[(metrics_df['commission_pct']==0) &
(metrics_df['spread_pct']==0) &
(metrics_df['slippage_base_pct']==0)]
realistic = metrics_df[(metrics_df['commission_pct']==0.001) &
(metrics_df['spread_pct']==0.0005) &
(metrics_df['slippage_base_pct']==0.0005)]
print(f"Gross edge: {zero_cost['total_return_pct'].iloc[0]:+.1f}%")
print(f"Net realistic: {realistic['total_return_pct'].iloc[0]:+.1f}%")
print(f"Cost drag: {zero_cost['total_return_pct'].iloc[0] - realistic['total_return_pct'].iloc[0]:+.1f}%")
\ Dies wird zurückkommen:
Gross edge: -28.5%
Net realistic: -40.5%
Cost drag: +12.0%
Dies deutet darauf hin, dass die Leistung von -28,5 % auf – sinkt, wenn realistische Provisionsniveaus (0,1 %), Spreads (0,05 %) und Slippages (0,05 %) berücksichtigt werden.40,5 %Hinzufügen von a Kosten von +12,0 Prozentpunkten. Dies zeigt, dass Ausführungsfriktionen selbst bei einer Verluststrategie die Ergebnisse verschlechtern, Verluste erhöhen und dazu führen, dass das Kapital schneller erodiert, als eine einfache PnL-Berechnung zeigen würde.
\ Lassen Sie uns nun drei Visualisierungen aus den Backtest-Ergebnissen erstellen: ein Streudiagramm, das veranschaulicht, wie höhere Gesamtkosten mit niedrigeren Renditen verbunden sind, ein Liniendiagramm, das den linearen Rückgang bei steigenden Provisionssätzen darstellt, und ein Balkendiagramm, das durchschnittliche Renditen bei verschiedenen Reibungsniveaus (ideal, Einzelhandel, schlechtest) vergleicht.
df = metrics_df.copy()
df = pd.read_csv('full_cost_impact.csv') # your metrics_df
df['total_friction'] = df['commission_pct'] + df['spread_pct'] + df['slippage_base_pct']
fig, axes = plt.subplots(1,3, figsize=(15,4))
# 1. Cost Drag Explosion
axes[0].scatter(df['total_costs'], df['total_return_pct'])
axes[0].set_xlabel('Total Costs ($)'); axes[0].set_ylabel('Net Return (%)')
axes[0].set_title('Higher Costs = Lower Returns')
# 2. Commission Sensitivity
comm_means = df.groupby('commission_pct')['total_return_pct'].mean()
axes[1].plot(comm_means.index * 100, comm_means.values, marker="o")
axes[1].set_xlabel('Commission (%)'); axes[1].set_title('Commission Impact')
# 3. Friction Buckets
friction_bins = ['Ideal (0%)', 'Retail (0.2%)', 'Worst (0.3%)']
friction_means = df.groupby(pd.cut(df['total_friction'],
bins=[0, 0.001, 0.002, 1], labels=friction_bins))['total_return_pct'].mean()
axes[2].bar(friction_bins, friction_means.values)
axes[2].set_ylabel('Net Return (%)'); axes[2].set_title('Friction Impact')
plt.tight_layout()
plt.savefig('cost_impact_charts.png')
plt.show()
\
Die Diagramme zeigen deutlich einen umgekehrten Zusammenhang zwischen Ausführungskosten und Strategieleistung. Selbst geringe Kosten erhöhen die Verluste bei dieser marginalen Strategie erheblich, da die Provisionen eine starke lineare Sensitivität aufweisen. Gesamtfriktionswerte deuten darauf hin, dass die „Einzelhandels“-Werte (0,2 %) die Erträge im Vergleich zu optimalen Bedingungen um mehr als fünf Prozentpunkte reduzieren können.
\ Wir werden nun durch lineare Regression über alle Szenarien hinweg bestimmen, wie empfindlich die Renditen auf die einzelnen Kostenkomponenten reagieren. Der Prozess umfasst die Umrechnung von Prozentsätzen in tatsächliche Werte, die Erstellung eines Modells, das die Gesamtrendite basierend auf Provision, Spread und Slippage vorhersagt, und die anschließende Darstellung der marginalen Auswirkung der einzelnen Kosten pro Prozentpunkt.
metrics_df['comm_pct'] = metrics_df['commission_pct'] * 100
metrics_df['spread_pct'] = metrics_df['spread_pct'] * 100
metrics_df['slip_pct'] = metrics_df['slippage_base_pct'] * 100
from sklearn.linear_model import LinearRegression
X = metrics_df[['comm_pct', 'spread_pct', 'slip_pct']]
y = metrics_df['total_return_pct']
model = LinearRegression().fit(X, y)
coefs = pd.DataFrame({
'Cost': ['Commission', 'Spread', 'Slippage'],
'Impact_per_%': model.coef_
})
print(coefs.sort_values('Impact_per_%'))
\ Und die Ergebnisse sind:
Cost Impact_per_%
2 Slippage -97.184237
0 Commission -36.458293
1 Spread -18.401344
\ Die Regression zeigt, dass Slippage den größten schädlichen Effekt hat und die Erträge um reduziert 97,4 % pro 1 % Schlupfgefolgt von Provisionen (-36,4 %) und Spreads (-18,4 %). Diese überraschende Reihenfolge verdeutlicht, dass Slippage in Hochfrequenz- oder Niedrigliquiditätssituationen vorherrscht, und verdeutlicht, warum die Ausführungsqualität bei systematischen Strategien oft wichtiger ist als die Hauptmaklergebühren.
\ Der nächste Analysepunkt umfasst einen Code, der den tatsächlichen Widerstand jeder Kostenkomponente identifiziert, indem er die durchschnittlichen Renditen, wenn nur diese Kosten aktiv sind (andere werden auf Null gesetzt), mit der Nullkosten-Basislinie vergleicht und ihre praktischen Auswirkungen über die getesteten Bereiche hinweg einordnet.
# REAL Delta Return per 0.01% cost increase
base_return = metrics_df[metrics_df['commission_pct']==0.0]['total_return_pct'].iloc[0] # -9.97%
# Average drag per cost type (isolated)
comm_drag = metrics_df[metrics_df['spread_pct']==0.0]['total_return_pct'].mean() - base_return
spread_drag = metrics_df[metrics_df['commission_pct']==0.0]['total_return_pct'].mean() - base_return
slip_drag = metrics_df[metrics_df['commission_pct']==0.0]['total_return_pct'].mean() - base_return
print(f"""
REAL COST IMPACT RANKING:
Spread {metrics_df['spread_pct'].max()*100:.1f}% → {spread_drag:.1f}% drag
Commission {metrics_df['commission_pct'].max()*100:.1f}% → {comm_drag:.1f}% drag
Slippage {metrics_df['slippage_base_pct'].max()*100:.1f}% → {slip_drag:.1f}% drag
""")
\ Mit folgenden Ergebnissen:
REAL COST IMPACT RANKING:
Spread 10.0% → -6.1% drag
Commission 0.2% → -8.5% drag
Slippage 0.1% → -6.1% drag
\ Provisionen sind der Hauptfaktor, der die Leistung mindert -8,5 % Trotz eines bescheidenen Höchstsatzes von 0,2 %, während Spread und Slippage beide einen Beitrag leisten -6,1 % Auswirkungen von viel höheren getesteten Werten von 1,0 %. Dies zeigt, dass Provisionen einen überproportional großen Effekt bei Fix-pro-Trade-Setups haben, was die Bedeutung der Brokerauswahl als wichtigen Hebelpunkt für systematische Strategien unterstreicht.
Wichtige Erkenntnisse
Die wichtigsten Erkenntnisse aus der obigen Analyse lassen sich wie folgt zusammenfassen:
- Ausführungskosten verstärken Verluste: Realistische Reibungen verwandeln eine marginale Strategie in einen völligen Verlierer, was zu einem erheblichen Kostendruck über den Bruttovorteil hinaus führt.
- Schlupf pro Einheit am zerstörerischsten: Bei der Regressionsanalyse ist Slippage hinsichtlich der Auswirkung pro Prozentpunkt am höchsten, vor Provisionen und Spreads.
- Die Provisionen übersteigen das Gewicht: Trotz bescheidener Zinssätze erzeugen sie im Vergleich zu höher getesteten Spreads und Slippage den größten realen Widerstand.
- Klare inverse Kosten-Rendite-Beziehung: Diagramme zeigen, dass höhere Kosten die Rendite direkt verringern; Reibungsniveaus im Einzelhandel beeinträchtigen die Leistung im Vergleich zum Ideal erheblich.
- Modellieren Sie Reibungen von Anfang an: Die feste Anzahl der Trades beweist, dass die Kosten den reinen Vorteil schmälern (wesentlich für eine realistische Realisierbarkeit des Live-Handels).
Nächste Schritte und abschließende Gedanken
In diesem Artikel haben wir von Grund auf eine vollständige Backtesting-Pipeline aufgebaut. Wir haben Intraday-Preisdaten für AAPL mit extrahiert Die historische Intraday-API von EODHDresampelte es auf einen 4-Stunden-Zeitrahmen und implementierte eine einfache Crossover-Strategie mit gleitendem Durchschnitt.
\ Anschließend haben wir diese Strategie anhand von 64 Kostenszenarien einem Stresstest unterzogen und dabei die Provision, die Geld-Brief-Spanne und die Slippage variiert, um deren individuelle und kombinierte Leistungseinbußen zu quantifizieren. Die Ergebnisse waren eindeutig: Realistische Spannungen machten eine ohnehin marginale Strategie zu einer deutlich schlechteren, wobei allein die Kostenbelastung über 12 Prozentpunkte des zusätzlichen Verlusts ausmachte.
\ Es gibt viel Raum, dieses Framework weiter auszubauen. Ein natürlicher nächster Schritt besteht darin, Positionsgrößenbeschränkungen und Marktauswirkungsmodelle zu integrieren, die den Slippage dynamisch mit der Ordergröße im Verhältnis zum durchschnittlichen Tagesvolumen skalieren. Sie könnten auch eine Walk-Forward-Validierung einführen, um eine übermäßige Anpassung der Kostenannahmen an einen einzelnen Zeitraum zu vermeiden, oder das Anlageuniversum erweitern, um Ausführungsfriktionen zwischen Aktien, ETFs und Futures zu vergleichen. Durch die Hinzurechnung der Finanzierungskosten für Overnight-Positionen und der Kreditkosten für Short-Trades würde das Modell die tatsächlichen Handelsbedingungen noch repräsentativer machen.
\ Letztlich ist die Lektion hier nicht spezifisch für eine bestimmte Strategie. Jeder systematische Trader steht vor der gleichen grundlegenden Herausforderung: der Kluft zwischen Bruttovorteil und Nettorealität. Ausführungskosten sind keine Randbemerkung; Sie sind eine bestimmende Variable dafür, ob eine Strategie die Produktion überlebt. Seien Sie ein frühes Vorbild für sie, seien Sie ein ehrliches Vorbild, und lassen Sie diese Disziplin durch jede Phase Ihres Entwicklungsprozesses ziehen.
Ihr Unternehmen – auf AutoPilot mit DDImedia KI-Assistent \N (Treten Sie unserer Warteliste bei)
Besuchen Sie uns unter DataDrivenInvestor.com
Treten Sie unserem Creator-Ökosystem bei Hier.
Offizieller DDI-Telegrammkanal: https://t.me/+tafUp6ecEys4YjQ1
Folgen Sie uns weiter LinkedIn, Twitter, YouTubeUnd Facebook.
\