Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

Survivorship bias katika data za hisa: Ufafanuzi

Survivorship bias huficha tickers zilizofutwa kwenye data za hisa. Jifunze pengo linalotokea kwenye backtest na jinsi ya kupima ulimwengu mzima wa hisa kwa usahihi.

Upendeleo wa walionusurika (survivorship bias) katika data za hisa ni pengo linalojitokeza wakati faili linahifadhi tu tickers ambazo bado zimeorodheshwa leo. Kila kampuni iliyoondolewa kwenye orodha, iliyonunuliwa, iliyobadilishwa jina, au iliyofungwa haipo kimyakimya, na jaribio la nyuma (backtest) linalofanywa katika ulimwengu huo hupima sampuli iliyochujwa ya historia badala ya soko kama lilivyokuwa. Hakuna kitu kinachoonekana kuharibika kwenye faili. Kila mstari una bei halisi, na tatizo ni mistari ambayo hakuna aliyeiandika.

Upendeleo wa walionusurika katika data za hisa ni nini?

Fikiria jinsi seti ya data ya "hisa zote za Marekani" inavyokusanywa. Mtu humwomba mtoa huduma orodha ya alama (symbols), kisha huvuta historia ya bei kwa kila jina kwenye orodha hiyo. Orodha hiyo ni ya sasa kulingana na ujenzi wake. Inajibu swali la "nini kinauzwa sasa", na kila alama iliyoacha kuuzwa kabla ya ombi kufanywa haipo kwenye jibu, pamoja na historia yake yote.

Neno hili linatokana na kazi ya Abraham Wald ya mwaka 1943 kuhusu ndege za kivita zilizorejea: silaha ziliwekwa mahali ambapo ndege zilizosalia hazikuonyesha matundu, kwa sababu ndege zilizopigwa katika maeneo hayo hazikurejea ili kupimwa. Hifadhidata ya hisa iliyojengwa kutoka kwa orodha za leo ina umbo sawa. Sampuli unayoweza kuiona ni sampuli iliyofika nyumbani, na uanachama ndani yake huamuliwa na matokeo yaleyale ambayo backtest inajaribu kupima.

Ni tickers ngapi hupotea kila mwaka?

Anza na ukubwa wa pengo. Paneli iliyo hapa chini huweka pamoja kila alama kwenye kanda ya kila siku ya hisa kulingana na mwaka wa kalenda wa mwambaa wake wa mwisho, ikihifadhi tu alama ambazo zilikuwa zimekaa kimya kabla ya Januari 2026.

UlizaAlama zilizochapisha bar ya mwisho ya kila siku, kwa mwaka
SQL halisi nyuma ya kila namba
SELECT
    toString(toYear(last_bar))  AS year,
    count()                     AS gone_dark_count,
    round(avg(bars_on_tape))    AS avg_bars_on_tape
FROM
(
    SELECT
        ticker,
        max(date) AS last_bar,
        count()   AS bars_on_tape
    FROM global_markets.stocks_daily_aggs
    WHERE date >= '2015-01-01'
      AND ticker NOT IN ('SPCX')
    GROUP BY ticker
    HAVING max(date) >= toDate('2016-01-01')
       AND max(date) <  toDate('2026-01-01')
)
GROUP BY year
ORDER BY year
Run this yourself

Katika 2025, 1283 alama zilichapisha mwambaa wa mwisho wa kila siku na hazikurejea tena. Hizi si makombora ya wiki moja: kundi hilo lilikuwa na wastani wa 999 mamba ya kila siku kuanzia 2015 na kuendelea. Katika miaka ya 10 kwenye paneli, idadi hiyo hubadilika, na hakuna mwaka unaokaa karibu na sifuri. Alama huondoka kwa sababu za kawaida: ununuzi hukamilika, mfuko hufungwa, muungano huunganisha orodha moja na nyingine, au kiwango cha uorodheshaji hakitimiziwi tena.

Tazama upendeleo ukijitokeza katika hati ya mistari kumi

Hesabu ni rahisi kuona kuliko kuelezea. Hati iliyo hapa chini inaandika faili mbili ndogo za CSV: moja ya ulimwengu wa walionusurika pekee, na ulimwengu uleule ukiwa na majina yaliyoacha kuuzwa yameongezwa tena. Inapata wastani wa kila faili na kuchapisha tofauti. Inatumia maktaba ya kawaida ya Python pekee, kwa hivyo hakuna cha kusakinisha.

python3 <<'PY'
import csv, statistics

survivors = [("AAA", 0.42), ("BBB", 0.18), ("CCC", 0.63), ("DDD", 0.07)]
delisted  = [("EEE", -1.00), ("FFF", -0.55), ("GGG", 0.31)]

def write_csv(path, rows):
    with open(path, "w", newline="") as f:
        w = csv.writer(f)
        w.writerow(["ticker", "total_return"])
        w.writerows(rows)

def mean_return(path):
    with open(path, newline="") as f:
        return statistics.mean(float(r["total_return"]) for r in csv.DictReader(f))

write_csv("survivors_only.csv", survivors)
write_csv("full_universe.csv", survivors + delisted)

a = mean_return("survivors_only.csv")
b = mean_return("full_universe.csv")
print(f"survivors only: {a:+.1%} across {len(survivors)} names")
print(f"full universe: {b:+.1%} across {len(survivors) + len(delisted)} names")
print(f"survivorship bias: {a - b:+.1%}")
PY

Takwimu saba za mapato katika hati hiyo ni za kubuni, si data za soko. Zipo ili kufanya utaratibu uonekane. Walionusurika pekee wana wastani wa +32.5%, ulimwengu kamili una wastani wa +0.9%, na tofauti ya pointi 31.6 ndiyo upendeleo. Hakuna kitu katika faili ya walionusurika pekee kilicho cha uongo. Kila moja ya majina hayo manne kwa kweli ilitoa mapato kama inavyosema. Faili haitaji kamwe yale matatu ambayo yangevuta wastani chini.

Upimaji uleule kwenye vikundi halisi

Sasa fanya hesabu ileile kwenye kanda. Paneli iliyo hapa chini inajenga ulimwengu wa kuanzia mnamo Januari ya kila mwaka kuanzia 2016 hadi 2022, ikihifadhi alama zilizofungua mwezi huo juu ya $5 na wastani wa kiasi cha kila siku cha zaidi ya hisa 250,000, kisha inafuata kila moja yao hadi kufungwa kwake kwa hivi karibuni. Majina yaliyoacha kuuzwa yanashikiliwa kwa bei yao ya mwisho iliyochapishwa badala ya kuondolewa. Safu ya walionusurika ina wastani wa alama pekee zinazochapisha mamba sasa. Safu ya ulimwengu kamili ina wastani wa kila mtu aliyekuwepo mwanzoni.

UlizaWastani wa waliosalia dhidi ya wastani wa kundi zima, kwa mwaka wa kuanzia
SQL halisi nyuma ya kila namba
WITH
    entry AS
    (
        SELECT
            ticker,
            toYear(date)                   AS cohort_start,
            argMin(toFloat64(close), date) AS entry_close
        FROM global_markets.stocks_daily_aggs
        WHERE toMonth(date) = 1
          AND date BETWEEN '2016-01-01' AND '2022-01-31'
          AND ticker NOT IN ('SPCX')
        GROUP BY ticker, cohort_start
        HAVING argMin(toFloat64(close), date) >= 5
           AND avg(volume) >= 250000
    ),
    outcome AS
    (
        SELECT
            ticker,
            argMax(toFloat64(close), date) AS final_close,
            max(date)                      AS last_bar
        FROM global_markets.stocks_daily_aggs
        WHERE date >= '2016-01-01'
        GROUP BY ticker
    )
SELECT
    toString(e.cohort_start)                                                             AS cohort_year,
    count()                                                                              AS cohort_size,
    countIf(o.last_bar < today() - 45)                                                   AS gone_count,
    round(100 * avgIf(o.final_close / e.entry_close - 1, o.last_bar >= today() - 45), 1) AS survivors_only_pct,
    round(100 * avg(o.final_close / e.entry_close - 1), 1)                               AS full_universe_pct,
    round(100 * (avgIf(o.final_close / e.entry_close - 1, o.last_bar >= today() - 45)
                 - avg(o.final_close / e.entry_close - 1)), 1)                           AS gap_pct
FROM entry AS e
INNER JOIN outcome AS o ON o.ticker = e.ticker
GROUP BY e.cohort_start
HAVING countIf(o.last_bar >= today() - 45) > 0
ORDER BY e.cohort_start
Run this yourself

Kwa kundi la 2016, 968 ya majina 2728 yalikuwa yameondoka kwenye kanda wakati ukurasa huu ulipoundwa. Kupata wastani wa walionusurika pekee kunatoa 212%. Kupata wastani wa kundi zima kunatoa 149.3%. Safu ya pengo ina umbali kati ya usomaji huo miwili: pointi 62.7 kwa kundi hilo, na pointi 9.1 kwa kundi la 2022, ambalo limekuwa na muda mdogo sana wa kupoteza wanachama. Haya ni mapato ya jumla katika madirisha ya urefu tofauti, kwa hivyo soma chini ya safu ya pengo badala ya kulinganisha viwango katika safu. Swali lilelile la ulimwengu liko chini ya jinsi mapato ya kila mwezi yanavyopimwa na chini ya kila wastani wa uzito sawa.

Mahali majina yaliyokosekana yalipoishia

Kuondolewa kwenye orodha si kufutwa moja kwa moja. Paneli iliyo hapa chini inachukua ulimwengu mmoja wa kuanzia, Januari 2019, na kuupanga kulingana na kile kilichotokea baadaye.

UlizaUlimwengu wa Januari 2019, uliopangwa kulingana na matokeo ya kila jina
SQL halisi nyuma ya kila namba
WITH
    entry AS
    (
        SELECT
            ticker,
            argMin(toFloat64(close), date) AS entry_close
        FROM global_markets.stocks_daily_aggs
        WHERE date BETWEEN '2019-01-01' AND '2019-01-31'
          AND ticker NOT IN ('SPCX')
        GROUP BY ticker
        HAVING argMin(toFloat64(close), date) >= 5
           AND avg(volume) >= 250000
    ),
    outcome AS
    (
        SELECT
            ticker,
            argMax(toFloat64(close), date) AS final_close,
            max(date)                      AS last_bar
        FROM global_markets.stocks_daily_aggs
        WHERE date >= '2019-01-01'
        GROUP BY ticker
    )
SELECT
    if(o.last_bar >= today() - 45,
       'Still trading',
       concat('Last bar in ', toString(toYear(o.last_bar))))            AS outcome_label,
    count()                                                             AS cohort_size,
    round(100 * avg(o.final_close / e.entry_close - 1), 1)              AS avg_return_pct,
    round(100 * countIf(o.final_close > e.entry_close) / count(), 1)    AS share_above_entry_pct
FROM entry AS e
INNER JOIN outcome AS o ON o.ticker = e.ticker
GROUP BY outcome_label
ORDER BY outcome_label
Run this yourself

Kundi la Still trading lina majina 1954, likiwa na wastani wa 139.8% kutoka bei ya kuingia ya Januari 2019, huku 72.1% yao wakiwa juu ya mahali walipoanzia. Soma safu za 9 na matokeo yanaonekana kuchanganyika. Kampuni iliyonunuliwa kwa premium inaweza kuondoka kwenye kanda kwa chapa ya mwisho yenye nguvu, wakati ile inayofifia kuelekea notisi ya kuondolewa huondoka kwa chapa dhaifu. Yote hayapo kwenye faili ya walionusurika pekee. Yarudishe na wastani utabadilika.

Upendeleo wa walionusurika dhidi ya upendeleo wa kutazama mbele

Haya mawili huwekwa pamoja, na huvunja backtest katika maeneo tofauti. Upendeleo wa kutazama mbele ni tatizo la muda: jaribio hutumia ukweli katika wakati ambao hakuna mtu aliyekuwa nao bado, kama vile takwimu za mapato zilizorekebishwa au marekebisho yaliyotumika miezi baadaye. Upendeleo wa walionusurika ni tatizo la uanachama: orodha ambayo jaribio linauza ni orodha iliyopo sasa, si orodha iliyokuwepo wakati huo. Backtest inaweza kuwa safi kwa wakati na bado ikauza ulimwengu uliokusanywa kwa kutumia busara ya nyuma. Marekebisho ya bei yana mtego unaohusiana, kwa kuwa historia ya bei iliyorekebishwa kwa mgawanyiko huandika upya kiwango cha kila mwambaa kabla ya tarehe ya mgawanyiko.

Jinsi ya kurekebisha upendeleo wa walionusurika katika backtest

  1. Jenga upya ulimwengu kulingana na kila tarehe. Uanachama wa wakati fulani unamaanisha orodha inajengwa upya kwa ajili ya siku ambayo jaribio linasimama, kutoka kwa rekodi zilizokuwepo siku hiyo. Jaribio kisha linashikilia majina yaliyokuwa hai wakati huo, ikiwa ni pamoja na yale yaliyotoweka baadaye.
  2. Weka bwana wa usalama (security master). Bwana wa usalama hupanga vitambulisho kwa muda: ni alama ipi iliyoelekeza kwa kampuni ipi kati ya tarehe zipi, na nini kilichochukua nafasi yake baada ya muungano au mabadiliko ya jina. Faili za bei hutegemea alama. Kampuni hazitegemei.
  3. Funga majina yaliyoondolewa kwenye orodha kwa bei yao ya mwisho halisi. Wakati jina linapoondoka kwenye kanda, nafasi inatoka kwa chapa yake ya mwisho badala ya kutoweka kutoka kwa wastani. Kufuta ndiko kunakofungua pengo katika paneli zilizo hapo juu. Backtest inayoweza kuzalishwa tena inabandika sheria hiyo kwenye msimbo karibu na ulimwengu.
  4. Soma nyaraka za mtoa huduma kuhusu alama zilizokufa. Wauzaji wanaochukulia hili kwa uzito huiandika. Massive hufichua alama zilizondolewa kwenye orodha kupitia endpoint ya tickers zao na alama ya amilifu, na hubeba mabadiliko ya alama, kuondolewa kwenye orodha, miungano na ununuzi kama rekodi katika endpoint tofauti ya matukio ya ticker.
"Hatubadilishi data au kuunganisha mabadiliko ya ticker katika mfululizo mmoja wa jumla."
Msingi wa maarifa wa Massive, "Massive inashughulikiaje mabadiliko ya ticker na ununuzi?", iliyosomwa Agosti 2026.

Ikiwa nyaraka za mtoa huduma hazisemi chochote kuhusu alama zilizondolewa kwenye orodha, chukulia upakuaji kama wa walionusurika pekee hadi utakapokagua. Ufikiaji hutofautiana sana kati ya vyanzo: tazama maelezo yetu kuhusu API za data za soko la hisa bila malipo.

Mtego wa ticker iliyosindikwa tena

Alama hurejea katika mzunguko baada ya uorodheshaji kuisha. Paneli iliyo hapa chini huhesabu alama zilizochapisha angalau mamba 200 ya kila siku, zikakaa kimya kwa angalau siku 250 za kalenda, kisha zikaonekana tena kama uorodheshaji mpya kabisa kuanzia 2022 na kuendelea.

UlizaAlama zilizoorodheshwa upya chini ya mtoaji mpya baada ya muda mrefu wa ukimya
SQL halisi nyuma ya kila namba
SELECT
    toString(toYear(relisted_on)) AS year,
    count()                       AS relisting_count,
    round(avg(dark_stretch))      AS avg_dark_stretch,
    max(dark_stretch)             AS longest_dark_stretch
FROM
(
    SELECT
        ticker,
        any(relist_date)                             AS relisted_on,
        dateDiff('day', max(date), any(relist_date)) AS dark_stretch
    FROM
    (
        SELECT
            d.ticker      AS ticker,
            d.date        AS date,
            i.relist_date AS relist_date
        FROM global_markets.stocks_daily_aggs AS d
        INNER JOIN
        (
            SELECT
                ticker,
                min(toDate(listing_date)) AS relist_date
            FROM global_markets.stocks_ipos
            WHERE toDate(listing_date) BETWEEN '2022-01-01' AND today()
              AND ticker NOT IN ('SPCX')
            GROUP BY ticker
        ) AS i ON i.ticker = d.ticker
        WHERE d.date < i.relist_date
    )
    GROUP BY ticker
    HAVING dateDiff('day', max(date), any(relist_date)) >= 250
       AND count() >= 200
)
GROUP BY year
ORDER BY year
Run this yourself

Katika 2022, 16 alama zilirudi kwa njia hii, baada ya ukimya wa wastani wa siku 3247 za kalenda. Kipindi kirefu zaidi cha ukimya katika safu hiyo kilikuwa siku 5734. Faili ya bei inayotegemea alama pekee huunganisha historia ya kampuni moja kwenye nyingine, na muungano unaonekana safi wakati wote. Hii ndiyo kesi ambayo bwana wa usalama huikamata na safu ya ticker tupu haiwezi.

Maelezo ya data na ufafanuzi

Alama huhesabika kama bado inauzwa wakati mwambaa wake wa hivi karibuni wa kila siku unapoangukia ndani ya siku 45 za kalenda tangu siku ukurasa huu ulipozalishwa. Buffer hiyo inashughulikia ucheleweshaji wa siku moja hadi mbili mbele ya kanda na majina madogo yanayoruka vipindi.

Mapato ni mapato ya jumla ya bei kutoka kwa kufungwa kwa kuingia kwa Januari hadi kufungwa kwa mwisho kunakopatikana kwa alama hiyo, bila gawio na bila kuweka kila mwaka. Vikundi vya awali vimekuwa na muda mrefu zaidi wa kujilimbikiza, kwa hivyo viwango havilinganishwi katika safu. Pengo kati ya wastani huo miwili linaweza.

Ulimwengu wa kuingia huhifadhi alama zilizopimwa kwa $5 au zaidi na wastani wa kiasi cha kila siku cha hisa 250,000 au zaidi wakati wa mwezi wa kuingia. Kanda ya kila siku hubeba fedha, vitengo na hati za dhamana karibu na kampuni zinazofanya kazi, na hakuna kitu hapa kinachozitenganisha, kwa hivyo kufungwa kwa mfuko huhesabika pamoja na kuondolewa kwa kampuni kwenye orodha. Alama zenye mgogoro wa utambulisho unaojulikana katika milisho ya wauzaji hazijumuishwi katika SQL kutoka kila paneli badala ya kuchujwa baadaye.

FAQ

Upendeleo wa walionusurika katika backtest ya hisa ni nini?

Ni kosa linaloachwa nyuma wakati ulimwengu unaoweza kuuzwa unajengwa kutoka kwa alama ambazo bado zipo leo. Kampuni zilizondolewa kwenye orodha au kununuliwa hazingii kamwe kwenye jaribio, kwa hivyo mkakati hupewa tu majina yaliyofika hadi sasa.

Nitajuaje ikiwa seti yangu ya data ina upendeleo wa walionusurika?

Chukua ulimwengu ambao faili yako inadai kuufunika kwa tarehe ya miaka kadhaa iliyopita na uangalie mwambaa wa mwisho wa kila alama. Ikiwa kila jina katika orodha ya 2016 bado linachapisha bei leo, orodha hiyo ilijengwa kutoka kwa orodha za sasa na majina yaliyokufa hayakuwemo kamwe.

Je, upendeleo wa walionusurika hufanya backtest ionekane bora kila wakati?

Hapana. Mwelekeo hutegemea jinsi majina yalivyoondoka kwenye kanda. Ununuzi unaweza kufungwa kwa premium, na kuacha majina hayo hupunguza kundi, wakati kufifia kwa muda mrefu kuelekea notisi ya kuondolewa hufanya kinyume. Paneli zilizo hapo juu hupima wavu wa yote mawili kwenye vikundi halisi.

Ulimwengu wa wakati fulani ni nini?

Ni orodha ya uanachama iliyojengwa upya kulingana na kila tarehe katika jaribio, ikitumia rekodi pekee zilizokuwepo tarehe hiyo. Inajumuisha majina yaliyokuwa yakiuzwa wakati huo na baadaye kutoweka, na haijumuishi majina ambayo hayakuwa yameorodheshwa bado.

Je, upendeleo wa walionusurika ni sawa na upendeleo wa kutazama mbele?

Hapana. Upendeleo wa kutazama mbele unahusu muda: jaribio hutumia habari kabla ya mtu yeyote kuwa nayo. Upendeleo wa walionusurika unahusu uanachama: jaribio linauza orodha iliyokusanywa kwa busara ya nyuma. Backtest inaweza kuwa safi kwa moja na kuvunjika kwa nyingine.


Kila paneli hapa inabeba SQL iliyoizalisha. Badilisha mwezi wa kuingia au legeza kichujio cha ukwasi, kisha uendeshe kwenye terminal ya Strasmore ili kuona ni kiasi gani cha pengo kinanusurika na sheria zako za ulimwengu.