Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

Kan een LLM alpha-factoren vinden?

Een LLM schrijft in een uur honderd alpha-factoren. Bekijk hoe 240 coin flip-factoren scoren op tien jaar echte koersen en hoe u de beste test.

Een LLM kan de hele dag alpha-factoren voorstellen. Geef een capabel model een data dictionary en een scoring harness, en het schrijft vóór de lunch honderd plausibele factorexpressies. De moeilijkere vraag ligt daaronder: hoe weet iemand ooit of een ervan echt is, als de zoekopdracht die de factor heeft opgeleverd een machine is die winnaars uit ruis produceert?

Wat is een alpha-factor?

Een factor is een regel die marktdata omzet in één getal voor elk aandeel op elke datum. De koersverandering over twaalf maanden is een factor. Dat geldt ook voor de verhouding tussen schuld en eigen vermogen. Een factor wordt een strategie wanneer u een universum erop rangschikt, het bovenste deel koopt, het onderste deel verkoopt en volgens een vast schema herbalanceert. Alpha is het rendement dat overblijft nadat u het rendement hebt afgetrokken dat een gewone market exposure u toch zou hebben opgeleverd.

Kandidaten worden beoordeeld met de Sharpe-ratio: het gemiddelde rendement gedeeld door de standaarddeviatie van dat rendement, omgerekend naar jaarbasis. Het is rendement per eenheid volatiliteit. Een Sharpe van ongeveer 1 over een lange periode op een live strategie is respectabel. Houd dat in gedachten wanneer een backtest de volgende keer 3 vermeldt.

Hoe LLM-factoronderzoek in de praktijk werkt

Elk project in dit domein doorloopt een variant van dezelfde cyclus.

  1. Het model schrijft factorexpressies in een kleine taal die de harness kan evalueren.
  2. Een backtester beoordeelt elke expressie over een vaste historische reeks van prijzen en fundamentele gegevens.
  3. Expressies boven een scoringsdrempel worden behouden. De rest valt af.
  4. De behouden expressies keren als uitgewerkte voorbeelden terug naar de context van het model, waarna de cyclus opnieuw begint.

Multi-agenttrading-systemen verdelen deze taken over afzonderlijke rollen: één rol stelt voor en een andere test. De infrastructuur is echt nuttig, en de marktdata-vaardigheden die een AI-agent nodig heeft zijn dezelfde als die een persoon nodig heeft.

Niets in deze cyclus is oneerlijk. Onderzoek gebeurt nu eenmaal via zoekopdrachten. Het probleem is rekenkundig en ontstaat zodra stap 2 meer dan enkele keren wordt uitgevoerd.

Waarom een LLM-zoekopdracht naar alpha-factoren winnaars produceert

Eén prijshistorie. Duizenden goedkope hypotheses. Elke hypothese wordt beoordeeld tegen dezelfde beperkte steekproef, en die steekproef bevat veel toeval. Test voldoende regels en sommige zullen nauw aansluiten bij dat toeval. De score kan niet aangeven welk type aansluiting u hebt gevonden, omdat een regel die ruis volgt en een regel die de markt volgt hetzelfde getal opleveren.

Hieronder ziet u de nulhypothese, 240 keer getrokken. Elke ‘factor’ is een muntworp: een hash van de ticker, de maand en een proefnummer verdeelt 40 grote Amerikaanse namen elke maand in twee helften. De strategie gaat long in de ene helft en short in de andere. Er zit per constructie geen informatie in. Beoordeeld op echte rendementen van maandultimo tot maandultimo van januari 2016 tot en met juni 2021, verdelen de 240 proeven zich als volgt.

Query240 coin-flipfactoren gescoord op reële prijzen: geannualiseerde Sharpe, jan. 2016 t/m jun. 2021
De exacte SQL achter elk getal
WITH month_end AS (
    SELECT ticker,
           toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker IN ('AAPL','ADBE','AMZN','BA','CAT','COST','CRM','CSCO','CVX','DE',
                     'DUK','GE','GOOGL','HD','HON','IBM','INTC','JNJ','JPM','KO',
                     'LMT','MCD','MMM','MRK','MSFT','NKE','NVDA','ORCL','PEP','PFE',
                     'PG','QCOM','SO','T','TGT','TXN','UNP','VZ','WMT','XOM')
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2015-12-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2021-06-30')
      AND toDayOfMonth(toTimeZone(window_start, 'America/New_York')) >= 22
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY ticker, month_start
),
lagged AS (
    SELECT ticker,
           month_start,
           close_px,
           lagInFrame(close_px) OVER (PARTITION BY ticker ORDER BY month_start
                                      ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
    FROM month_end
),
monthly_return AS (
    SELECT ticker, month_start, close_px / prev_px - 1 AS ret
    FROM lagged
    WHERE prev_px > 0
      AND month_start >= toDate('2016-01-01')
),
trial AS (
    SELECT arrayJoin(range(1, 241)) AS n
),
factor_month AS (
    SELECT t.n AS trial_id,
           m.month_start AS month_start,
           avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1)
         - avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) AS long_short_ret
    FROM monthly_return AS m
    CROSS JOIN trial AS t
    GROUP BY trial_id, month_start
    HAVING countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1) > 0
       AND countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) > 0
),
scored AS (
    SELECT trial_id,
           avg(long_short_ret) / stddevSamp(long_short_ret) * sqrt(12) AS sharpe
    FROM factor_month
    GROUP BY trial_id
    HAVING stddevSamp(long_short_ret) > 0
)
SELECT multiIf(sharpe < -1.2, 'below -1.2',
               sharpe < -0.8, '-1.2 to -0.8',
               sharpe < -0.4, '-0.8 to -0.4',
               sharpe <  0.0, '-0.4 to 0.0',
               sharpe <  0.4, '0.0 to 0.4',
               sharpe <  0.8, '0.4 to 0.8',
               sharpe <  1.2, '0.8 to 1.2',
               '1.2 and above') AS sharpe_bucket,
       count() AS factor_count,
       round(100 * count() / 240, 1) AS share_pct
FROM scored
GROUP BY sharpe_bucket
ORDER BY min(sharpe)
Run this yourself

De spreiding is precies het punt. Niets in deze grafiek voorspelt iets, en toch eindigden 1 proeven in de bovenste band (1.2 and above), 0.4% van de zoekopdracht, en 1 in de onderste band (below -1.2). Een onderzoeker die één gelukkige proef uitvoert en daarna stopt, houdt een grafiek en een Sharpe-ratio over, zonder te kunnen vaststellen of het om een ontdekking gaat. De rendementen lopen hier van maandultimo tot maandultimo; zo worden maandrendementen gemeten behandelt die berekening.

Het relevante getal is hoeveel u hebt getest

Een backtest die op zichzelf wordt gerapporteerd, mist zijn noemer. Lees dezelfde 240 proeven als een zoekopdracht die steeds verder wordt uitgebreid: bij elke stap ziet u de beste score naast het gemiddelde van alles wat tot dan toe is getest.

QueryDe beste score stijgt met de omvang van de zoekopdracht: beste en gemiddelde Sharpe per aantal uitgevoerde trials
De exacte SQL achter elk getal
WITH month_end AS (
    SELECT ticker,
           toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker IN ('AAPL','ADBE','AMZN','BA','CAT','COST','CRM','CSCO','CVX','DE',
                     'DUK','GE','GOOGL','HD','HON','IBM','INTC','JNJ','JPM','KO',
                     'LMT','MCD','MMM','MRK','MSFT','NKE','NVDA','ORCL','PEP','PFE',
                     'PG','QCOM','SO','T','TGT','TXN','UNP','VZ','WMT','XOM')
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2015-12-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2021-06-30')
      AND toDayOfMonth(toTimeZone(window_start, 'America/New_York')) >= 22
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY ticker, month_start
),
lagged AS (
    SELECT ticker,
           month_start,
           close_px,
           lagInFrame(close_px) OVER (PARTITION BY ticker ORDER BY month_start
                                      ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
    FROM month_end
),
monthly_return AS (
    SELECT ticker, month_start, close_px / prev_px - 1 AS ret
    FROM lagged
    WHERE prev_px > 0
      AND month_start >= toDate('2016-01-01')
),
trial AS (
    SELECT arrayJoin(range(1, 241)) AS n
),
factor_month AS (
    SELECT t.n AS trial_id,
           m.month_start AS month_start,
           avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1)
         - avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) AS long_short_ret
    FROM monthly_return AS m
    CROSS JOIN trial AS t
    GROUP BY trial_id, month_start
    HAVING countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1) > 0
       AND countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) > 0
),
scored AS (
    SELECT trial_id,
           avg(long_short_ret) / stddevSamp(long_short_ret) * sqrt(12) AS sharpe
    FROM factor_month
    GROUP BY trial_id
    HAVING stddevSamp(long_short_ret) > 0
),
ladder AS (
    SELECT arrayJoin([1, 2, 5, 10, 25, 50, 100, 160, 240]) AS n
)
SELECT l.n AS factors_tried,
       round(max(s.sharpe), 2) AS best_sharpe,
       round(avg(s.sharpe), 2) AS average_sharpe
FROM ladder AS l
CROSS JOIN scored AS s
WHERE s.trial_id <= l.n
GROUP BY factors_tried
ORDER BY factors_tried
Run this yourself

Een voortschrijdend maximum kan alleen stijgen. Precies daarin schuilt de valkuil. De eerste geteste regel scoorde 0.44. Na 240 proeven staat de beste score op 1.59, terwijl het gemiddelde over alle proeven 0.01 bedraagt. De kopregel verbeterde zonder dat ook maar één regel beter werd. Een harness die tienduizend expressies beoordeelt, loopt deze curve veel verder naar rechts door dan hier is weergegeven. Het gerapporteerde getal is het hoogste punt van die curve.

Wat een holdoutperiode met de winnaars doet

De standaardverdediging is een holdout: u beoordeelt de strategie over één periode en beoordeelt de overlevende kandidaten vervolgens opnieuw over een latere periode die de zoekopdracht nooit heeft gezien. Neem de twaalf beste muntworpen uit de trainingsperiode en voer exact dezelfde regels uit over de vijf daaropvolgende jaren, van juli 2021 tot en met juni 2026.

QueryDe twaalf beste trials in de training, opnieuw gescoord op vijf onaangeroerde jaren (jul. 2021 t/m jun. 2026)
De exacte SQL achter elk getal
WITH month_end AS (
    SELECT ticker,
           toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker IN ('AAPL','ADBE','AMZN','BA','CAT','COST','CRM','CSCO','CVX','DE',
                     'DUK','GE','GOOGL','HD','HON','IBM','INTC','JNJ','JPM','KO',
                     'LMT','MCD','MMM','MRK','MSFT','NKE','NVDA','ORCL','PEP','PFE',
                     'PG','QCOM','SO','T','TGT','TXN','UNP','VZ','WMT','XOM')
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2015-12-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-06-30')
      AND toDayOfMonth(toTimeZone(window_start, 'America/New_York')) >= 22
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY ticker, month_start
),
lagged AS (
    SELECT ticker,
           month_start,
           close_px,
           lagInFrame(close_px) OVER (PARTITION BY ticker ORDER BY month_start
                                      ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
    FROM month_end
),
monthly_return AS (
    SELECT ticker, month_start, close_px / prev_px - 1 AS ret
    FROM lagged
    WHERE prev_px > 0
      AND month_start >= toDate('2016-01-01')
),
trial AS (
    SELECT arrayJoin(range(1, 241)) AS n
),
factor_month AS (
    SELECT t.n AS trial_id,
           m.month_start AS month_start,
           avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1)
         - avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) AS long_short_ret
    FROM monthly_return AS m
    CROSS JOIN trial AS t
    GROUP BY trial_id, month_start
    HAVING countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1) > 0
       AND countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) > 0
),
scored AS (
    SELECT trial_id,
           avgIf(long_short_ret, month_start <  toDate('2021-07-01'))
             / stddevSampIf(long_short_ret, month_start <  toDate('2021-07-01')) * sqrt(12) AS in_sample_sharpe,
           avgIf(long_short_ret, month_start >= toDate('2021-07-01'))
             / stddevSampIf(long_short_ret, month_start >= toDate('2021-07-01')) * sqrt(12) AS out_of_sample_sharpe
    FROM factor_month
    GROUP BY trial_id
    HAVING countIf(month_start <  toDate('2021-07-01')) >= 24
       AND countIf(month_start >= toDate('2021-07-01')) >= 24
)
SELECT concat('trial ', toString(trial_id)) AS factor_label,
       round(in_sample_sharpe, 2) AS in_sample_sharpe,
       round(out_of_sample_sharpe, 2) AS out_of_sample_sharpe
FROM scored
ORDER BY in_sample_sharpe DESC
LIMIT 12
Run this yourself

Elk paar staven staat voor één regel. De linker staaf is de score waarmee de regel in het rapport terechtkwam. De rechter staaf is de score van dezelfde regel over de volgende vijf jaar. De best gerangschikte proef scoorde 1.59 in de training en -0.51 daarna; de twaalfde scoorde 0.74 en vervolgens 0.49.

Twaalf regels vormen op zichzelf een kleine steekproef. Rangschik alle 240 regels op basis van de trainingsscore in vijf groepen en bereken vervolgens de gemiddelde holdoutscore per groep. Dat geeft een duidelijker beeld.

QueryTrainingsrang versus holdout-resultaat: 240 trials verdeeld in vijf gelijke groepen
De exacte SQL achter elk getal
WITH month_end AS (
    SELECT ticker,
           toStartOfMonth(toDate(toTimeZone(window_start, 'America/New_York'))) AS month_start,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker IN ('AAPL','ADBE','AMZN','BA','CAT','COST','CRM','CSCO','CVX','DE',
                     'DUK','GE','GOOGL','HD','HON','IBM','INTC','JNJ','JPM','KO',
                     'LMT','MCD','MMM','MRK','MSFT','NKE','NVDA','ORCL','PEP','PFE',
                     'PG','QCOM','SO','T','TGT','TXN','UNP','VZ','WMT','XOM')
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2015-12-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-06-30')
      AND toDayOfMonth(toTimeZone(window_start, 'America/New_York')) >= 22
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY ticker, month_start
),
lagged AS (
    SELECT ticker,
           month_start,
           close_px,
           lagInFrame(close_px) OVER (PARTITION BY ticker ORDER BY month_start
                                      ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS prev_px
    FROM month_end
),
monthly_return AS (
    SELECT ticker, month_start, close_px / prev_px - 1 AS ret
    FROM lagged
    WHERE prev_px > 0
      AND month_start >= toDate('2016-01-01')
),
trial AS (
    SELECT arrayJoin(range(1, 241)) AS n
),
factor_month AS (
    SELECT t.n AS trial_id,
           m.month_start AS month_start,
           avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1)
         - avgIf(m.ret, bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) AS long_short_ret
    FROM monthly_return AS m
    CROSS JOIN trial AS t
    GROUP BY trial_id, month_start
    HAVING countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 1) > 0
       AND countIf(bitAnd(cityHash64(m.ticker, toString(m.month_start), t.n), 1) = 0) > 0
),
scored AS (
    SELECT trial_id,
           avgIf(long_short_ret, month_start <  toDate('2021-07-01'))
             / stddevSampIf(long_short_ret, month_start <  toDate('2021-07-01')) * sqrt(12) AS in_sample_sharpe,
           avgIf(long_short_ret, month_start >= toDate('2021-07-01'))
             / stddevSampIf(long_short_ret, month_start >= toDate('2021-07-01')) * sqrt(12) AS out_of_sample_sharpe
    FROM factor_month
    GROUP BY trial_id
    HAVING countIf(month_start <  toDate('2021-07-01')) >= 24
       AND countIf(month_start >= toDate('2021-07-01')) >= 24
),
ranked AS (
    SELECT trial_id,
           in_sample_sharpe,
           out_of_sample_sharpe,
           row_number() OVER (ORDER BY in_sample_sharpe DESC) AS in_sample_rank
    FROM scored
)
SELECT multiIf(in_sample_rank <=  48, 'best fifth in training',
               in_sample_rank <=  96, 'second fifth',
               in_sample_rank <= 144, 'middle fifth',
               in_sample_rank <= 192, 'fourth fifth',
               'worst fifth in training') AS training_group,
       round(avg(in_sample_sharpe), 2) AS avg_in_sample_sharpe,
       round(avg(out_of_sample_sharpe), 2) AS avg_out_of_sample_sharpe
FROM ranked
GROUP BY training_group
ORDER BY min(in_sample_rank)
Run this yourself

Tijdens de training lopen de groepen van 0.66 bovenaan tot -0.63 onderaan. Dat is een brede en volledig ordelijke ladder, wat gegarandeerd is omdat de groepen juist op basis van die score zijn samengesteld. Tijdens de holdout-periode bedragen de gemiddelden aan dezelfde twee uiteinden 0.01 en 0.13. De ladder vlakt af. Een holdout is het enige onderdeel van de pipeline waarop nog niet is geoptimaliseerd. Daarom is het de moeite waard deze zorgvuldig te gebruiken.

Verdedigingsmaatregelen die daadwerkelijk werken

Een holdout die u maar één keer gebruikt. Elke blik op de holdout maakt er trainingsdata van. Walk-forward testing, waarbij het venster doorschuift en elke score afkomstig is uit data na de fit, is de variant die herhaald gebruik kan doorstaan.

Een correctie voor multiple testing. De deflated Sharpe-ratio, in 2014 geïntroduceerd door Bailey en López de Prado, corrigeert een geobserveerde Sharpe-ratio voor het aantal uitgevoerde proeven, de lengte van de steekproef, de scheefheid van de rendementen en de dikte van hun staarten. Voert u een eerlijk aantal proeven in, dan wordt een headline-Sharpe uit een zoekopdracht met tienduizend expressies vaak tot nul gereduceerd.

Een audit trail voor elke geteste expressie, ook voor de expressies die zijn verworpen. Dit is de dragende pijler. Daarom is ‘auditable’ het interessante woord in de beschrijving van een factoronderzoeksproject. De deflatie vereist een aantal proeven. Een pipeline die alleen winnaars registreert, heeft de input voor zijn eigen correctie vernietigd. Afgewezen concepten, afgebroken parameter-sweeps, herstarts van de onderzoeker en elke eerdere versie van de scoringcode tellen allemaal mee.

Controles op kosten en look-ahead bias voordat u de score gelooft. Een factor die wordt gerangschikt op basis van een fundamenteel veld met de datum waarop een leverancier het heeft geladen, in plaats van de datum waarop de markt het kon zien, backtest uitstekend maar presteert slecht in de handel.

Het woord ‘auditable’ lezen

Nieuwe repositories in dit domein verschijnen bijna wekelijks. Een project met enkele tientallen sterren is eerder een prototype dan een trackrecord. Het aantal sterren verandert bovendien sneller dan de code. Daarom beoordeelt deze pagina het patroon en niet één specifiek project. Dit zijn de eerste punten die u moet controleren bij elk project dat u tegenkomt.

  • Registreert het elke kandidaat met de expressie en de score, voorzien van een timestamp, of alleen de behouden kandidaten?
  • Wordt de holdout door de harness afgedwongen, of door de zelfdiscipline van de onderzoeker?
  • Staat bij elke gerapporteerde score ook een aantal proeven?
  • Voor welke markt is het gebouwd? Een library die is afgestemd op Chinese A-shares houdt rekening met dagelijkse prijslimieten en met de beperking om aandelen die in dezelfde sessie zijn gekocht te verkopen. Het gedrag van een factor onder die regels kan niet zonder meer worden doorgetrokken naar Amerikaanse aandelen.
  • Kunt u het opnieuw uitvoeren en de cijfers reproduceren? Leg de exacte commit die u hebt gelezen vast, omdat een project in deze fase zijn scoringcode tussen twee weekends kan herschrijven.

Dit alles maakt een LLM niet nutteloos voor factoronderzoek. Het genereren van hypotheses is een echte bottleneck en modellen zijn daar goed in. Wat verandert, is waar de verantwoordelijkheid komt te liggen: bij het bijhouden van hoeveel hypotheses zijn verbruikt. Voordat een van deze factoren een live orderboek bereikt, maakt paper trading het verschil tussen een backtest en een uitgevoerde transactie zichtbaar.

Veelgestelde vragen over LLM-alpha-factoren

Kan een LLM alpha-factoren vinden?

Het kan er duizenden voorstellen, maar een voorstel is geen vondst. De claim wordt gemaakt bij de scoringstap. Een score uit een brede zoekopdracht bevat een selectieprobleem dat de score zelf niet kan zien. Beoordeel daarom eerst de discipline rond de holdout en het vastgelegde aantal proeven, en pas daarna de expressie.

Wat is de deflated Sharpe-ratio?

Een correctie die een geobserveerde Sharpe-ratio omzet in de waarschijnlijkheid dat een zoekopdracht van die omvang dezelfde uitkomst zou hebben opgeleverd zonder echte edge. Bailey en López de Prado publiceerden de methode in 2014. De centrale input is het aantal proeven. Precies dat aantal kan een niet-auditeerbare researchcyclus niet leveren.

Hoeveel backtests zijn te veel?

Er bestaat geen vaste drempel. Er is alleen een correctie die moet worden toegepast. Eén backtest met een score van 1,0 en tienduizend backtests waarvan de beste score 1,0 is, vormen verschillende beweringen over de werkelijkheid. De muntworpen hierboven bereikten 1.59 over 240 proeven, zonder dat er enige informatie in de data zat.

Waarom verzwakken gepubliceerde factoren na publicatie?

Academisch onderzoek heeft de afname van gepubliceerde anomalieën gevolgd in de jaren nadat zij verschenen. Crowding is een mogelijke verklaring. Een oorspronkelijk resultaat dat te sterk op zijn eigen steekproef is gefit, is een andere. Beide verklaringen geven dezelfde vorm in een grafiek. De efficiënte-markthypothese beschrijft de eerste verklaring. De proeven hierboven tonen de tweede.


Elk paneel hier is een opgeslagen query over echte maandultimo-prijzen, met de SQL eronder zichtbaar. Kopieer er één, verhoog het aantal proeven en kijk hoe het beste getal op de Strasmore-terminal stijgt.

#llm#factor research#overfitting#multiple testing#quant