We draaiden een opgeslagen backtest opnieuw en kregen een andere vermogenscurve. Dezelfde strategiecode, dezelfde periode, hetzelfde symbool. Het eindsaldo week 1.8% af en drie transacties waren één candle verschoven.
Dat is genoeg om een onderzoeksresultaat moeilijk te vertrouwen te maken. Als een collega, een toekomstige versie van jezelf of een papertradingdienst de run niet kan reproduceren, kun je niet vaststellen of een wijziging de strategie verbeterde of alleen het experiment veranderde. Dit is de volgorde die we aanhielden om de afwijkingen op te sporen.
Dag 1: We legden vast wat “dezelfde run” betekende
Onze eerste fout was om een strategie bestand te behandelen alsof dat het experiment was. Dat was het niet. De run hing ook af van de invoerdata, de engineversie, de kalender, instrumentmetadata en uitvoeringsinstellingen. De code beschreef slechts één deel van de berekening.
Voordat we iets veranderden, maakten we een runmanifest. Daarin legden we de commit van de strategie vast, identificaties van datasnapshots, de periode, handelsplaats, kostenstructuur, financieringsbron, fillmodel en softwareversies. We bewaarden ook de resulterende orders en fills, want aan een vermogenscurve alleen zie je niet waar twee runs voor het eerst uiteenlopen.
| Artefact | Wat je vastlegt | Waarom het van belang is |
|---|---|---|
| Marktdata | Snapshot-ID, schemaversie, aanpassingen | Leveranciers herstellen historische data en herzien bedrijfsacties |
| Uitvoering | Commissieniveau, financieringsreeks, fill- en impactinstellingen | Standaardinstellingen en aannames over de rekening veranderen de resultaten |
| Runtime | Codecommit, engine- en afhankelijkheidsversies | Bibliotheken kunnen de volgorde, afronding of indicatoren veranderen |
| Uitvoer | Orders, fills, posities en statistieken | Toont waar de runs van elkaar beginnen af te wijken |
Dag 2: We vergeleken de transacties, niet de Sharpe
De samenvattende statistieken leidden ons af. Beide runs hadden vrijwel dezelfde Sharpe, maar uit de fill-logs bleek dat de eerste afwijking optrad bij een financieringsafrekening. De ene run rekende het tarief aan voor de positie die op het afrekeningstijdstip openstond; de andere gebruikte de positie na de herbalancering op dat tijdstip.
De strategiecode was niet veranderd. De volgorde van gebeurtenissen in de engine wel. Door een kleine versie-update werd de volgorde expliciet, terwijl die voorheen afhing van de toevallige sortering van twee gebeurtenissen.
We legden de volgorde vast in het runcontract: pas de financiering toe op de positie die de afrekening ingaat en verwerk daarna de strategiebeslissingen voor dat tijdstip. De precieze afspraak kan per handelsplaats en engine verschillen. De volgorde impliciet laten is de fout.
Dag 3: Een “zelfde” databestand bleek toch anders
Nadat we de volgorde van gebeurtenissen hadden vastgelegd, zaten de resterende afwijkingen vooral in een paar aandelentransacties. De leverancier had een historische aanpassing voor een aandelensplitsing gecorrigeerd. Ons bestand had dezelfde naam en hetzelfde aantal rijen als voorheen, waardoor het onveranderd leek.
Nu maken we een vingerafdruk van elke onveranderlijke datasnapshot en bewaren we het aanpassingsbeleid erbij. Een hash vertelt ons of de bytes zijn veranderd, maar niet waarom. Daarom bevat het manifest ook de bron, het ophaaltijdstip en de transformatieversie. Bij data die wordt herzien, maken die details deel uit van het resultaat.
Een reproduceerbare backtest moet antwoord geven op de vraag: “Welke versie van het verleden kreeg hij te zien?”
Dag 4: We vonden één stille standaardinstelling
Het laatste verschil kwam door een maker fee die op nul stond omdat het configuratiebestand van de strategie dat veld niet bevatte. Een nieuwere engine paste de standaardcommissie van de rekening toe. Die ene standaardinstelling veranderde de marginale transacties voldoende om het grootste deel van het verschil in eindsaldo te verklaren.
We maakten economisch betekenisvolle instellingen expliciet en lieten de engine de opgeloste configuratie in het runverslag opnemen. Standaardinstellingen zijn handig tijdens het verkennen. Ze zijn zwak bewijs wanneer je resultaten uit verschillende perioden vergelijkt.
Wat we de volgende keer zouden overslaan
We besteedden een halve dag aan het vergelijken van samenvattende statistieken voordat we naar de eerste afwijkende fill keken. Begin daar niet. Sorteer beide gebeurtenissenlogs op tijdstip en vergelijk de eerste afwijking; latere verschillen zijn vaak een gevolg van die ene oorzaak.
We zouden ook het idee laten varen dat een containerimage op zichzelf een run reproduceerbaar maakt. Die legt een groot deel van de softwareomgeving vast, maar niet een extern databestand, een kostenstructuur die tijdens runtime wordt opgehaald of de herziene historie van een leverancier.
Als een backtest verandert, bewaar dan beide runmanifesten en logs en pak vervolgens één oorzaak van de afwijking tegelijk aan. Het nuttige resultaat is niet alleen een curve die je opnieuw kunt genereren. Het is een verslag dat uitlegt welke data en aannames het hebben opgeleverd, en waarom de volgende run kan afwijken.
← Alle artikelen


