Waarom je nooit 'loops' moet gebruiken in Pandas voor backtesting
Je zit achter je scherm, je hebt een mooie strategie in je hoofd en je wilt snel testen of die werkt.
Je opent Pandas, laadt wat data en je typt een for-loop. Het voelt vertrouwd, maar het is een val.
In de wereld van algoritmische trading bots, backtesting en risicomanagement is snelheid alles. Een simpele loop in Python kan je script van seconden naar uren duwen, en je resultaten vertekenen.
Waarom loops langzaam zijn en wat je eraan kunt doen
Een loop in Pandas is gewoon een herhaling. Voor elke rij in je dataframe voer je handmatig een berekening uit.
Dat klinkt logisch, maar het is traag. Python moet voor elke iteratie het datatype controleren, de index raadplegen en het geheugen bijwerken. Dat kost tijd, vooral bij grote datasets zoals tickdata of minute-bars van brokers zoals Interactive Brokers of LMAX.
Vectorisatie is het antwoord. In plaats van één voor één, verwerk je de hele kolom in één keer.
Pandas en NumPy gebruiken onder de motorkap C-code, die vele malen sneller is. Je script loopt niet meer uren, maar seconden. Dat betekent dat je meer iteraties kunt draaien, je parameters sneller kunt tunen en je risico’s beter kunt beheren.
Een voorbeeld: een simple moving average berekenen met een loop duurt bij 1 miljoen rijen makkelijk 10 seconden. Met vectorisatie is het binnen een seconde klaar. Die tijdswinst telt op, vooral als je meerdere brokers of API’s test.
De kern: vectorisatie in de praktijk
Laten we een concreet voorbeeld bekijken. Je hebt een dataframe met prijzen van een aandeel, bijvoorbeeld €150 tot €160 per dag.
Je wilt een 20-daagse moving average berekenen. Met een loop schrijf je zoiets:
for i in range(len(df)):
if i >= 20:
df.loc[i, 'MA20'] = df['Close'].iloc[i-20:i].mean() Dit werkt, maar het is sloom. Vectorisatie doet hetzelfde in één regel:
df['MA20'] = df['Close'].rolling(window=20).mean() Het resultaat is identiek, maar voor complexere gewogen voortschrijdende gemiddelden berekenen is de snelheid enorm verbeterd.
Je kunt deze aanpak toepassen op alle technische indicatoren: RSI, MACD, Bollinger Bands. Geen loops, gewoon de ingebouwde functies van Pandas gebruiken. Voor backtesting betekent dit dat je een hele periode kunt testen zonder dat je computer vastloopt. Denk ook aan je data-inrichting en Pandas configuratie voor je financiële tijdsreeksen. Denk ook aan je datakwaliteit.
Als je data van een broker haalt via API, zorg dan dat de kolommen het juiste type hebben.
Gebruik float voor prijzen en int voor volumes. Vectorisatie faalt als de datatypes niet kloppen, dus check altijd df.dtypes. Dit voorkomt fouten en houdt je risicomanagement scherp.
Varianten: van simpele backtest tot complexe bot
Niet elke strategie is even makkelijk te vectoriseren. Een eenvoudig momentum-model met een entry- en exit-regel is wel te doen.
Stel je koopt als de prijs boven de MA20 sluit en verkoopt als die daaronder valt. Met vectorisatie bereken je de signalen in één keer:
df['Signal'] = np.where(df['Close'] > df['MA20'], 1, -1) Je kunt dan de returns berekenen met shift, zodat je niet in het verleden kijkt: df['Return'] = df['Close'].pct_change() * df['Signal'].shift(1) Voor complexere bots, zoals die met meerdere condities of risicolimieten, gebruik je functies zoals apply of groupby.
Zelfs dan probeer je de logica zo te schrijven dat je zo min mogelijk per rij berekent.
Bijvoorbeeld door eerst alle condities te berekenen en dan pas te filteren. Prijsindicaties voor data: een betaald abonnement op Polygon.io voor Amerikaanse aandelen kost ongeveer €200 per jaar. Voor Europese data kun je bijvoorbeeld €50-€100 per maand betalen via een broker zoals DEGIRO of via een data-provider. Die kosten wegen op tegen de tijdswinst van vectorisatie, vooral als je dagelijks backtests draait.
Modellen met prijsindicaties: risico’s en kosten meenemen
Backtesting zonder transactiekosten is een illusie. Een vectorized model kan ook kosten meenemen.
Stel je broker rekent €2 per trade en 0,1% over het volume. Je kunt dit direct in de return-berekening stoppen: df['Return'] = (df['Close'].pct_change() * df['Signal'].shift(1)) - (0.001 * abs(df['Signal'].diff())) - (2 / (df['Close'] * df['Volume']))
Let op: de laatste term is een schatting, afhankelijk van je broker.
Bij Interactive Brokers liggen de kosten lager dan bij een retail broker, dus pas het aan. Vectorisatie maakt het makkelijk om verschillende kostenscenario’s te testen zonder je code te herschrijven. Risicomanagement is ook een kwestie van vectorisatie. Je kunt drawdowns berekenen met een cumulatieve return en de maximumdrawdown in één keer bepalen:
df['CumReturn'] = (1 + df['Return']).cumprod()
df['Peak'] = df['CumReturn'].cummax()
df['Drawdown'] = (df['CumReturn'] - df['Peak']) / df['Peak']
max_drawdown = df['Drawdown'].min() Dit geeft je direct inzicht in het risico, zonder dat je een loop nodig hebt.
Je kunt ook een stop-loss of position-sizing meenemen, bijvoorbeeld door de positiegrootte te berekenen op basis van de drawdown. Vectorisatie houdt het overzichtelijk en snel.
Praktische tips voor soepele backtests
- Gebruik altijd .shift() voor signalen: voorkomt look-ahead bias.
- Check je datatypes: df.dtypes en zorg voor float voor prijzen.
- Test met subsets: begin met 1 jaar data, dan 5 jaar, om snelheid te meten.
- Gebruik NumPy voor extra snelheid: importeer numpy als np en werk met arrays.
- Vermijd apply voor simpele berekeningen: rollen en where zijn sneller.
Een laatste tip: integreer je backtest met je broker API. Bijvoorbeeld via de IB-insync library voor Interactive Brokers.
Je kunt de vectorized signalen direct gebruiken om orders te plaatsen, zonder dat je per trade een loop nodig hebt.
Dit houdt je bot efficiënt en je risico beheersbaar. Onthoud: vectorisatie is geen truc, het is de basis van professionele algoritmische trading. Het bespaart tijd, vermindert fouten en geeft je de ruimte om te focussen op wat telt: een robuuste strategie met goed risicomanagement. Dus de volgende keer dat je een backtest opzet, optimaliseer je positiegrootte door de Kelly Criterion formule te programmeren, open Pandas en denk in kolommen, niet in rijen.
