22. oktober 2026 · data

Vi genopbyggede en backtest, efter at en dataleverandør ændrede sin historik

Vi genopbyggede en backtest, efter at en dataleverandør ændrede sin historik

Seks uger efter, at vi kørte en backtest, kørte vi den igen med samme kode, konfiguration og datoperiode. Slutværdien af egenkapitalen var anderledes. Ikke voldsomt anderledes, men nok til at flytte Sharpe-ratioen fra 1.31 til 1.08 og ændre, hvilket parametersæt der så bedst ud.

Strategien havde ikke ændret sig. Det havde de historiske data. En leverandør havde revideret flere dages handler og bars, og vi havde ikke bevaret det præcise input-snapshot fra den første kørsel. De næste par dage brugte vi på at finde ud af, om det nye resultat skyldtes en datakorrektion, en fejl i datapipelinen eller en forskningsproces, vi ikke kunne reproducere.

Mandag: genkørslen stemte ikke

Det første tegn var et lille gab i et likvidt spot-par. Vores oprindelige bar viste en højeste pris på 62,418. Genkørslen viste 62,431. Forskellen virkede harmløs, indtil vi fulgte strategiens entry: dens tærskel lå mellem de to priser. Nogle få ændrede bars gav andre signaler, og dermed andre positioner flere dage senere.

Vi sammenlignede antallet af handler pr. dag og fandt ud af, at uoverensstemmelsen begyndte på én bestemt dato. Derefter sammenlignede vi de rå rækker, der lå til grund for den dato. Leverandørens aktuelle svar indeholdt en håndfuld korrigerede handler og en revideret aggregeret bar. Der var ingen ændringslog knyttet til det svar, vi havde brugt.

Det er almindelig dataadfærd. Børser reparerer poster; leverandører udfylder huller, korrigerer fejlbehæftede handler og justerer aggregeringslogik. En forespørgsel efter “januardata” kan give et andet svar i marts.

Tirsdag: vi fandt den manglende kvittering

Vi havde gemt leverandørnavn, symbol og datoperiode i kørselsregistreringen. Vi havde ikke gemt selve svaret eller en indholdshash for hver inputpartition. Det betød, at vi kunne bevise, hvad vi havde bedt om, men ikke hvilke bytes leverandøren havde returneret.

Vi fandt de ældste cachede filer, vi kunne opspore, og sammenlignede dem med det aktuelle svar. Forskellen skyldtes hverken en splitjustering eller en tidszonekonvertering. Det var et lille sæt korrigerede handler, som blev ført gennem vores barbygger. Det udelukkede én teori, men cachen var ufuldstændig, så vi kunne stadig ikke rekonstruere hver påvirket bar præcist.

Det, vi registreredeDet, det gjorde os i stand til at besvareDet, der manglede
Leverandør, symbol, datoperiodeHvilken forespørgsel kørslen foretogSvarindholdet
PipelineversionHvilken kode der byggede barreneDe præcise inputrækker
KørselstidspunktHvornår vi kørte jobbetHvilken leverandørrevision der var tilgængelig på det tidspunkt

Vi havde sidestillet “samme forespørgsel” med “samme data”. Den antagelse fik eksperimentet til at se mere reproducerbart ud, end det var.

Onsdag: korrektionen ændrede fortællingen

Vi afspillede de berørte datoer igen med begge versioner af de tilgængelige data. De reviderede bars fjernede to entries og flyttede én exit med én bar. De tre handler stod for det meste af ændringen i Sharpe. Strategien var følsom over for nogle få observationer tæt på sin trigger, selvom den samlede prisserie så næsten identisk ud på et diagram.

Det betød ikke, at det oprindelige resultat var svigagtigt, eller at det reviderede resultat automatisk var bedre. Det betød, at påstanden skulle afgrænses: Den gamle kørsel beskrev det datasnapshot, vi havde på det tidspunkt, mens genkørslen beskrev leverandørens aktuelle historik. Vi kunne ikke ærligt kalde det ene en reproduktion af det andet.

Torsdag: vi ændrede forskningsregistreringen

Vi tilføjede uforanderlige inputsnapshots til nye forskningskørsler. Hver partition får en indholdshash, og kørselsmanifestet registrerer kilde, hentetidspunkt, skemaversion, justeringspolitik og versionen af transformationskoden. For store datasæt gemmer vi de faktiske filer i indholdsadresseret lager, så identiske partitioner kan deles uden at overskrive tidligere versioner.

Når lagrings- eller licensregler forhindrer os i at beholde rådata, bevarer vi det stærkeste dokumentationsspor, vilkårene tillader: hasher, forespørgselsparametre, kildeversions-id'er og et lille udsnit af fortolkede rækker til fejlsøgning. En hash kan ikke genskabe dataene, men den kan fortælle os, når to kørsler ikke brugte identiske bytes.

En reproducerbar backtest kræver en identitet for inputdataene, ikke kun en data-URL. Registrer, hvad der ankom, hvornår det ankom, og hvordan det blev til de rækker, strategien brugte.

Fredag: hvad vi ville springe over næste gang

Vi ville springe over kun at sammenligne hovedtallene. En ændret Sharpe fortæller dig, at noget har flyttet sig; den lokaliserer ikke årsagen. Start med rækketællinger og partitionshasher, og find derefter den første inputdato, der afviger. Sammenlign derfra transformerede rækker, signaler, ordrer og fills i den rækkefølge.

Vi ville også undlade lydløst at erstatte gamle data med korrigeret historik. Korrektioner er nyttige, men gamle kørsler bør fortsat være knyttet til deres oprindelige inputs. Et nyt datasæt bør oprette en ny kørsel med en synlig relation til den ældre.

Vores praktiske regel er nu enkel: Hvis en genkørsel skal reproducere et eksperiment, skal datasnapshot'et fastlåses. Hvis den skal drage fordel af korrigeret historik, skal den mærkes som et nyt eksperiment. Det tog os et par dage at lære forskellen, og den står nu på én linje i kørselsmanifestet.

markedsdatabacktestingdatateknikreproducerbarhed
← Alle indlæg