Här är en rad ur en daglig aktiefil. NVDA, fredag 7 juni 2024, den sista handelssessionen innan splitten 10-för-1 trädde i kraft.
| Kolumn | Värde |
|---|---|
| date | 2024-06-07 |
| close | 1208.88 |
| volume | 42,800,000 (avrundat) |
| split_factor | 1.0 |
| dividend | 0.00 |
| adj_close | 120.8781 |
Jag har medvetet utelämnat OHLC-kolumnerna här. De kommer tillbaka mot slutet, och det är de som i tysthet ställer till det.
Allt intressant med den här raden finns i glappet mellan kolumn två och kolumn sex. Avsluten den 7 juni skedde till 1208.88. Ingen kommer någonsin att handla till det priset igen, eftersom aktien som priset avsåg inte längre finns som en enhet. 120.8781 är en rekonstruktion, och det är inte bara 1208.88 delat med tio. Vi går igenom kolumnerna en i taget.
date: 2024-06-07 och de två andra datumen som spelar större roll
Splitten tillkännagavs den 22 maj 2024 i samband med rapporten.
Avstämningsdagen var 6 juni. Handeln på splitjusterad basis började måndag 10
juni. Tre datum, och din dataleverantörs split_factorkolumn
motsvarar exakt ett av dem, oftast den första splitjusterade sessionen. Fel
med en dag är den vanligaste buggen i bolagshändelser som jag ser, och den
syns inte i aggregaten: din aktiekurva får ett gap ned på 90% eller ett gap
upp på 900% under en enda dag. Det märks knappt i en Sharpe beräknad över
2,000 dagar, men påverkar maxnedgången eller en stop-loss-regel rejält.
Utdelningen har ett eget datumproblem, och reglerna ändrades för alla under 2024. NVDA:s kvartalsutdelning på $0.01 efter splitten hade avstämningsdag 11 juni. USA:s avvecklingstid flyttades till T+1 den 28 maj 2024, vilket innebär att ex-dagen och avstämningsdagen nu är samma dag. Alla dataladdare som härleder ex-dagen som "avstämningsdag minus en bankdag" – vilket hade varit rätt i årtionden – började i tysthet skapa utdelningsjusteringar en session för tidigt i juni 2024. Inget felmeddelande visas. Faktorn hamnar bara på fel rad.
Fel i kod för bolagshändelser ger aldrig ett undantag. Det blir ett tal som är en dag fel och ser helt rimligt ut.
close: 1208.88 – priset som inte finns i filen
Det här är det råa avslutet, och i de flesta researchflöden finns det bara som
en kolumn ingen läser. Signaler beräknas på adj_close. Och alla
regler som hänvisar till en prisnivå i dollar per aktie hänvisar nu till ett
tal som ingen marknadsaktör någonsin såg.
NVDA:s uppgång mot $1,000 våren 2024 var verklig, med verkliga orderflöden bakom sig: runda tal som magneter, optionslösenpriser samlade kring 950 och 1000, och rubrikrisk varje gång kursen närmade sig. I justerade data sker hela den perioden vid $100. Om du testar beteende kring psykologiska nivåer, logik för att fylla gap nära runda tal eller en funktion för närhet till lösenpris, testar du mot en prisserie som skalats om av en händelse som ännu inte hade inträffat.
Samma sak gäller urvalsfilter. "Uteslut aktier under $5" är en rimlig proxy för en verklig samling problem med marknadsmikrostruktur: stora relativa spreadar, regler för belåningsbarhet och tickstorlekar. Kör filtret på justerad historik så utesluter du bolag under perioder då de handlades till $30 och senare genomgick en omvänd split. Kör "pris över $500" så gallrar du i tysthet bort NVDA från alla år före juni 2024. Prisfilter hör hemma på ojusterade avslut. Punkt.
split_factor: 1.0 – och 0.1 på nästa rad
Faktorn är en kumulativ produkt som räknas bakåt från i dag. Varje rad före 10 juni 2024 har en faktor på 0.1 från den här splitten, multiplicerad med 0.25 från splitten 4-för-1 år 2021, multiplicerad med vad som än kom dessförinnan. NVDA:s historik före 2021 skalas med 0.025, så en stapel som handlades till $200 år 2019 visas som $5.00.
Två följder är värda att lägga på minnet. För det första förändras den justerade historiken varje gång en ny bolagshändelse inträffar. Ett backtest du körde i maj 2024 och ett du kör i dag ger olika resultat med identisk kod och identiska datum. Om du cachelagrade funktioner med symbol och datum som nyckel utan en dataversionsstämpel har du nu en cache med två olika skalor. Vi lägger in justeringsepoken i funktionsnyckeln av precis den anledningen; det krävdes en mycket förvirrande eftermiddag för att vi skulle lära oss det.
För det andra: vid en skalning på 0.025 rör sig en aktie som handlades i steg om $0.01 nu i steg om $0.00025. Logiken för avrundning till tickstorlek, jämförelser av typen "är det här en ny högsta kurs" och kontroller av flyttalens likhet körs nu på nivåer långt under marknadens faktiska upplösning.
dividend: 0.01 – talet som tar bort centstegen från alla tidigare priser
En cent. Det ser ut som brus, och det är därför adj_closevisar
120.8781 i stället för jämna 120.888.
Stängningskursen den 10 juni var 121.79. Ex-utdelningsfaktorn är (121.79 − 0.01) / 121.79 = 0.99991789. Den faktorn multiplicerar alla priser före 11 juni, så det splitjusterade 120.888 blir 120.878074. Liten, permanent och tillämpad igen vid vart och ett av de drygt sextio ex-datumen bakåt genom NVDA:s historik. Det är den sammansatta effekten som spelar roll: när du tittar på 2015 har priset multiplicerats med dussintals mer eller mindre irrationella faktorer. Inget justerat historiskt pris ligger på centstegen.
Det innebär att varje ==jämförelse med en rund nivå blir fel,
varje kontroll av typen "nådde vi limitpriset" behöver en tolerans, och dina
rekonstruerade OHLC-värden uppfyller inte längre invarianten low ≤ open ≤ high
efter en naiv avrundning till 4 decimaler. Det innebär också att
adj_closeär en totalavkastningsserie, medan closeär
en prisavkastningsserie. Beräknar du avkastning från den ena och nedgångar
från den andra blandar du två olika definitioner av vad som hände.
volume: 42,800,000 – divisorn som tillämpas inkonsekvent
Aktievolymen behöver multipliceras med 10 för att kunna jämföras med sessioner efter splitten. Vissa dataleverantörer gör det. Andra gör det inte. En del justerar OHLC men lämnar volymen rå, vilket är det sämsta av de tre alternativen eftersom filen ser konsekvent ut tills du multiplicerar pris med volym.
Det rimliga är att sluta använda aktievolym som en funktion och i stället använda dollarvolym, som per konstruktion är oförändrad vid splitar. Ett likviditetsfilter baserat på genomsnittlig dollarvolym över 20 dagar kräver ingen justeringslogik alls och kan inte slås sönder av en bolagshändelse. Funktioner som är invarianta är bättre än korrekt justeringskod, eftersom korrekt justeringskod är något du hela tiden måste se till att få rätt.
Tre format på leverantörsfiler, tre feltyper.
| Vad som levereras | Vad som går fel |
|---|---|
| Endast justerad stängningskurs, råa OHLC | Gap- och intradagslogik beräknas över två prisskalor |
| Justerade OHLC, rå volym | Dollarvolymen blir fel med splitförhållandet; ADV-filter ger fel utslag |
| Rå OHLCV plus faktorkolumner | Inget, om du tillämpar dem. Allt, om du tillämpar dem på fel rad |
Kolumnerna som saknas: adj_open, adj_high, adj_low
Lägg nu tillbaka OHLC. Om ett dataflöde bara justerar stängningskursen och din
strategi beräknar ett gap över natten som
open / prev_adj_close − 1, beräknas 120.37 / 120.8781 − 1 den 10
juni 2024, vilket är ett rimligt tal, ungefär −0.4%. Kör samma kod bakåt till
7 juni 2024 så hämtas öppningskursen ur den råa filen (cirka 1207) medan
stängningskursen justerats med 0.025 från äldre splitar. Då blir gapet flera
tusen procent.
Allt som blandar en rå kolumn med en justerad kolumn är aritmetik över två koordinatsystem. Jag behandlar numera blandad läsning av justerade värden som en kontroll på dataladdarnivå, i stället för något en analytiker måste komma ihåg, för det kommer de inte att göra – inte heller jag.
Vad ingen kolumn berättar: hur många aktier du faktiskt skulle ha köpt
Det här är det som kostar pengar i backtestens kostnadsmodell, och det är anledningen till att jag över huvud taget skriver om en aktiesplit.
Provisioner för amerikanska aktier tas ofta ut per aktie. Räkna med $0.005, med en lägsta orderavgift på $1.00, vilket är ett vanligt fast prissteg hos nätmäklare för privatpersoner. Backtesta nu en NVDA-position på $400,000 ett år före splitten, då den råa kursen låg nära $400 och den justerade kursen visas som $40.
| Aktier | Provision | |
|---|---|---|
| Det som faktiskt hände (rå kurs $400) | 1,000 | $5.00 |
| Det backtestet ser (justerad kurs $40) | 10,000 | $50.00 |
Samma exponering, samma handel, 10 gånger den modellerade kostnaden. Det är $45 hittepå per transaktion, $90 per tur och retur, och en strategi som omsätter dagligen drar på sig cirka $22,000 per år i kostnader som aldrig funnits. På en portfölj på $400k är det 5.6 procentenheter av årsavkastningen, helt skapade av en divisor. Här får du resultatet att se sämre ut än verkligheten, vilket går att leva med.
Omvända splitar fungerar åt andra hållet, och det är den farliga riktningen. En omvänd split på 1-för-10 innebär att justerad historik visar priser som är 10 gånger högre och aktieantal som är 10 gånger lägre än det du skulle ha handlat. Kostnadsmodellen delar i tysthet provisionen per aktie med tio. I marknadssegment där omvända splitar är vanliga – småbolag under $5 med stora spreadar – är avgifter per aktie den största enskilda posten i kostnadskalkylen. Backtestet smickrar just de aktier som det borde straffa.
Lösningen är enkel och strukturell: bestäm positionsstorlek i dollar, räkna om till aktier med den ojusterade kursen för det datumet och beräkna avgifter per aktie utifrån det antalet. Behåll kolumnen med rå kurs i dataladdaren för alltid, inte som ett granskningsunderlag utan som en aktiv indata till orderfyllnadsmotorn.
- Signaler och avkastning: justerade serier.
- Aktieantal, avgifter per aktie, avrundning till tickstorlek, prisnivåfilter: ojusterade serier.
- Likviditet och kapacitet: dollarvolym, som inte behöver någotdera.
Sex kolumner, en rad, en tämligen vanlig bolagshändelse som tillkännagavs tre veckor i förväg och inte överraskade någon. Marknaden hanterade den på en förmiddag. Det är betydligt svårare att återskapa den korrekt i ett researchflöde, och tecknet på att flödet misslyckats är nästan aldrig ett felmeddelande.
← Alla inlägg


