Фреймворк салығы: бэктест кітапханаңыз қарапайым pandas циклінен баяу болғанда
"Иллюзиясыз бэктестер" сериясының бөлігі.
Көптеген алготрейдинг жобаларының түбінде жайлы бір болжам жатыр: жетілген, көп жұлдызды бэктест кітапханасы жылдам деген. Онда жылдар бойғы үлестер, нағыз оқиға циклі, брокер моделі, комиссия сұлбасы бар. Ол өзіңіз жазатын шатпақ pandas циклін сөзсіз жеңеді ғой. Сондықтан оған жүгінесіз, стратегияңызды жалғайсыз да, параметр сканерлеуін бастайсыз — бірнеше мың конфигурация, түні бойғы тапсырма. Таңертең оралсаңыз, ол әлі де жұмыс істеп тұр.
Біз сегіз бэктест қозғалтқышын бір бірдей параметр сканерлеуінде өлшеп, іздеу құралын қалай таңдайтыныңызды өзгертуге тиіс нәрсе таптық. Ең танымал ашық бастапқы кодты оқиғаға негізделген екі фреймворк — backtrader және bt — сканерлеуді біз лақтырып тастайтын базалық көрсеткіш ретінде жазған қарапайым pandas циклінен де баяу орындады. Сәл ғана баяу емес. backtrader pandas базалық көрсеткішінің уақытынан шамамен 2.5× алды; bt шамамен 4.7×. Сол уақытта сол жұмыстағы векторланған/компиляцияланған қозғалтқыш bt-дан шамамен 13,000× жылдам істеді.
Бұл фреймворк салығы. Танымал бэктестерлер бір адал жүгіру үшін жасалған — бір стратегия, бір деректер жиынтығы, мұқият толтырулар, брокерше жүретін брокер. Дәл осы сізге қорытынды растау немесе тірі трейдингпен паритетті тексеру үшін керек. Бұл алго-зерттеу шын мәнінде уақытын жұмсайтын нәрсе үшін дәл дұрыс емес құрал: сол стратегияны сәл өзгеше тұтқалармен он мың рет жүгірту. Бұл мақала салықты өлшейді, механизмді түсіндіреді және "нағыз" бэктест фреймворкі қашан қате таңдау болатынына шешім ережесін береді.
Мұндағы әрбір сан бір қайта жаңғыртылатын сынақ жүйесінен (benchmarks/bench_oss_engines.py, коммит 250dbb5) бірдей, паритеті бекітілген жұмыс жүктемесінде алынған. Егер біз қозғалтқышты өзіміз жүгіртпеген болсақ, оны айтамыз да, санды ойлап табудың орнына жеке адалдық бөліміне саламыз.
Бір жүгіру он мыңға қарсы

Параметр іздеудің анықтаушы фактісі — қозғалтқыш мыңдаған рет жүгіреді, бірақ талдау бір рет болады. Бэктестті орнату үшін төлейтін кез келген тұрақты шығын — оқиға циклін салу, брокерді инстанциялау, бар сайын бір нысанды бөлу — оны әрбір комбинацияда төлейсіз. Бір жүгіруде көрінбейтін шығын ("6 секундты кім елейді?") сканерлеу бойынша бүкіл шотқа айналады ("6 секунд × 10,000 = 16.6 сағат").
Бэктест қозғалтқыштары үш парадигмаға бөлінеді, ал парадигма сканерлеу өнімділігінің тағдыры:
- Оқиғаға негізделген — қозғалтқыш бар сайын жүреді, оқиғаларды шығарады, сіздің
next()/onBars()кері шақыруыңызды шақырады, тапсырыстарды брокер нысаны арқылы бағыттайды. Бұл backtrader, backtesting.py, PyAlgoTrade, zipline және nautilus_trader сәулеті. Ол тірі трейдингтің шын мәнінде қалай жұмыс істейтінін көрсетеді, дәл сол себепті оған нақтылық үшін сенеді — және дәл сол себепті ол баяу: бар сайынғы Python үстеме шығыны әр комбинацияда 150,000 рет төленеді. - Салмақ/қайта теңгеру —
btосында отыр. Оған мақсатты салмақ матрицасын бересіз де, ол сіз көрсеткен күндерде қайта теңгереді. Бар сайын кері шақыру жоқ, бірақ әлі де Python-да бағаланатын оқиға сайынғы нысан графы (алго ағашы, транзакциялар кітабы) бар. - Векторланған / компиляцияланған — бүкіл стратегия массив операциялары (vectorbt), JIT-компиляцияланған ядро (numba) немесе жергілікті код (Rust, MLX GPU ядросы) ретінде өрнектеледі. Бар сайынғы Python мүлдем жоқ. Цикл, егер болса, машина жылдамдығымен жүреді.
Бұл мақаланың қалғаны сол таксономияның эмпирикалық салдары. Біз бір жұмыс жүктемесін салдық, әр қозғалтқышты дәлелденетіндей сол жұмысты орындауға мәжбүр еттік және уақытын өлшедік.
Жұмыс жүктемесі: бір стратегия, сексен тұтқа, бәріне бірдей
Бенчмарк әр қозғалтқыш сол жұмысты істегенде ғана адал. Біздікі әдейі қарапайым — қозғалтқыштар арасында жалғыз өзгеше нәрсе қозғалтқыштың өзі болатын жұмыс жүктемесі.
- Деректер. Геометриялық броундық қозғалыстан жасалған бір синтетикалық жабылу сериясы:
150,000бар,seed=42, бар сайынғы құбылмалылықsigma=0.0008,x0=30000. Детерминистік, сондықтан кез келген адам оны бит-бит қайта жаңғырта алады. Құрылысы бойынша тек жабылу — OHLC әр аяқта жабылуға орнатылады, өйткені стратегия жабылу қиылысы. - Стратегия. Hull Moving Average қиылысы:
Lұзындықтағы HMA-ны жылдамырақ HMA-үштен-бір нұсқасына қарсы (HMAменHMA3). Әрдайым нарықта, әр қиылыста лонг/шортқа аударылады. Бұл нақты, тривиалды емес индикатор — екі ұялас салмақталған жылжымалы орташа плюс квадрат-түбір-терезе тегістегіші — ойыншық SMA емес, сондықтан бар сайынғы жұмыс өкілдік етеді. - Сканерлеу.
6..200аралығын қамтитын80HMA ұзындығы. Бұл тікелей өлшенетіндей кішірейтілген "он мың жүгіру": 80 тәуелсіз комбинация, әрқайсысы 150k бар бойынша толық бэктест. - Шығындар.
0.09%айналым жиынтық ақысы, жақ комиссияларын модельдейтін қозғалтқыштар үшін жаққа бөлінген.close[i]кезіндегі сол бардағы толтыру —iбардағы сигнал сол бардың жабылуында орындалады, бұл біздің өндірістік қозғалтқышымыз қолданатын конвенция.
Комбинация сайынғы таймер дәл екі нәрсені орайды: numpy HMA алдын ала есептеуі мен қозғалтқыш жүгіруі. Шын мәнінде бір реттік орнату (деректерді жүктеу, бар нысандарын салу) таймердің сыртында отыр. Бір жылыну жүгіруі бар, содан кейін N ішіндегі ең жақсысы қайталанады, және — оқиғаға негізделген қозғалтқыштар 80 толық комбинация бірнеше минуттан бір сағаттан астам уақыт алатындай баяу болғандықтан — біз тордың біркелкі үлгісінің уақытын өлшеп, сызықтық түрде экстраполяциялаймыз. Комбинациялар тәуелсіз, сондықтан сызықтық экстраполяция күтуде дәл; сол конвенция pandas базалық көрсеткішіне қолданылады, сондықтан ешбір қозғалтқыш одан артықшылық алмайды.
Паритет: бәрі сол жұмысты істейтінін дәлелдеу
Міне, қозғалтқыштарды аңғал салыстыру түсетін тұзақ: "жылдам" қозғалтқыш жай ғана азырақ жасап тұрған болуы мүмкін. Егер backtrader 2,700 мәміле есептеп, ал сіздің векторланған қозғалтқышыңыз 40 есептесе, векторланған қозғалтқыш жылдамырақ емес — ол қате, ал салыстырудың мәні жоқ.
Сондықтан біз салыстыруды мәмілелер саны паритетін тексерумен бекітеміз. L=104 кезінде numpy эталоны дәл 2,707 жабылған мәміле береді. Әр қозғалтқыш оны ±1 төзімділік аясында қайта жаңғыртуы керек, әйтпесе жүгіру work-parity FAILED тұжырымымен үзіледі. Төзімділік тек қозғалтқыштар есеп жүргізу конвенцияларында келіспейтіндіктен бар — соңғы ашық позиция мәжбүрлеп жабылып, есептеле ме, бастапқы кіру "мәміле" ме — мәмілелердің өзінде емес:
| Қозғалтқыш | L=104 кезінде хабарланған мәмілелер | Конвенция |
|---|---|---|
| numpy эталоны | 2707 | жабылған айналымдар |
| backtesting.py | 2708 | +1: соңғы позиция соңында мәжбүрлеп жабылған |
| backtrader | 2707 | соңғы ашық позиция есептелмеген |
| bt | 2708 | +1: бастапқы кіру транзакция ретінде есептелген |
| PyAlgoTrade | 2708 | +1: бастапқы кіру толтыру ретінде есептелген |
Әр қозғалтқыш 2707 ± 1-ге түседі. Ақыр соңында анықталған жылдамдық айырмашылықтары қандай болса да, олар қозғалтқыштың үнсіз жұмыс өткізіп жіберуінің артефактісі емес. Дәл осы тәртіп бізге оқиғаға негізделген фреймворк пен GPU ядросын бір кестеге қоюға және оны шынымен де айтуға мүмкіндік береді.
Нәтижелер
Міне, бүкіл кесте, жылдамнан баяуға қарай сұрыпталған. combos/s — өткізу қабілеті; соңғы баған — толық 80-комбинациялы сканерлеу қанша уақыт алатыны. Базалық жол — M0, аңғал pandas қозғалтқышы, скалярлық есеп жүргізумен бар бойынша for циклі, түстен кейін жазып, лақтырып тастайтын нәрсе. Сол базалық көрсеткіштен баяу нәрсенің бәрі қалың қаріппен.
| Қозғалтқыш | combos/s | Парадигма | Толық 80-комбинациялы сканерлеу |
|---|---|---|---|
| MLX GPU ядросы | 779 | векторланған (Apple GPU) | 0.10 s |
| жергілікті Rust | ~350 | компиляцияланған | 0.23 s |
| mp + numba | 246 | компиляцияланған JIT + мультипроцесс | 0.33 s |
| vectorbt | 56.9 | векторланған (numpy/numba) | 1.4 s |
| numba (бір ядро) | 39.7 | компиляцияланған JIT | 2.0 s |
| backtesting.py | 1.42 | оқиғаға негізделген | 56 s |
| PyAlgoTrade | 0.51 | оқиғаға негізделген | 2.6 min |
| M0 — аңғал pandas + цикл | 0.28 | скалярлық базалық көрсеткіш | 4.8 min |
| backtrader | 0.11 | оқиғаға негізделген | 12.7 min |
| bt | 0.06 | салмақ / қайта теңгеру | 22.5 min |
Кестені жоғарыдан төменге оқыңыз да, парадигма өзін-өзі сұрыптайды: жоғарғы бесеуі — бәрі векторланған немесе компиляцияланған, төменгі бесеуі — бәрі оқиғаға негізделген немесе нысан-графына негізделген, ал аңғал pandas циклі екі жетілген, танымал фреймворктің үстінде отыр. Жоғарыдан төменге қарай айырма — төрт реттік дәреже. Дәл сол 2,707-мәмілелі жұмыс жүктемесінде MLX ядросы сканерлеуді секундтың оннан бір бөлігінде аяқтайды; bt-ға жиырма екі жарым минут керек. Бұл шамамен 13,000× коэффициент.
Кестенің ортасындағы жанжал

Көзге түсетін сандар шеткі шектерде, бірақ тәлімді нәтиже ортада: backtrader (0.11 combos/s) және bt (0.06 combos/s) екеуі де аңғал pandas базалық көрсеткішінен (0.28 combos/s) баяу.
Мұны сіңіру керек. M0 — айлакер қозғалтқыш емес. Бұл DataFrame ішіне индекстейтін, позиция мен қолма-қол ақшаны қарапайым скалярларда бақылайтын және мәмілелерді тізімге қосатын Python for циклі — біз жеңетін анық жаман нәрсе болуы үшін қосқан әдейі оңтайландырылмаған "бақылау". pandas-тың жол сайынғы қатынауы атышулы баяу, біз соған сүйендік. Соған қарамастан экожүйедегі ең көп ұсынылатын екі бэктест кітапханасы оған жеңіледі: backtrader 2.5×, bt 4.7×.
Мұны адал ұстайтын нюанс: әр оқиғаға негізделген қозғалтқыш pandas-тан баяу емес. backtesting.py (1.42 combos/s) базалық көрсеткішті 5× жеңеді, өйткені ол бар сайынғы нысан жасауды ең азда ұстайтын жеңіл, numpy-мен қолдау көрсетілген оқиға циклі. PyAlgoTrade (0.51) да базалық көрсеткіштен сәл озады. Сонымен "оқиғаға негізделген" автоматты түрде өлім жазасы емес — бірақ бар сайынғы механизм неғұрлым ауыр болса, соғұрлым нашар, ал backtrader мен bt мұнда ең ауыр механизмді алып жүреді. Парадигма төбені белгілейді; іске асыру сол төбенің астында қайда түсетініңізді шешеді.
Мәселе бұлардың жаман кітапханалар екенінде емес. backtrader-дің брокер моделі мен bt-дың алго-ағашы дизайны сізге дұрыстық пен мәнерлілік сатып алу үшін бар — нақты тапсырыстарды өңдеу, портфельді қайта теңгеру, анализаторлар. Бұл мүмкіндіктердің орындалу шығыны бар, ал бір рет жүгіргенде ол шығын көрінбейді. Сканерлеу бойынша ол бүкіл әңгіме.
Оқиғаға негізделген қозғалтқыштар салықты неге төлейді

Механизм жұмбақ емес. Оқиғаға негізделген бэктест бар сайын шамамен мынаны жасайды:
- Сағатты алға жылжытады, деректер арнасынан келесі барды кеседі және оны нысан ретінде материалдандырады (
Bar,Line, dict). - Пайдаланушы кодына кері шақыруды жібереді (
next(),onBars()), бұл өз кадры бар Python функция шақыруы. - Кері шақыру ішінде брокер/позиция күйін сұрайды, тағы да әдіс шақырулары мен атрибут іздеулері арқылы.
- Егер тапсырыс жасалса, оны брокер арқылы бағыттайды: оны тексереді, маржа/қолма-қол ақшаны тексереді, толтыруды жоспарлайды, портфель нысанын өзгертеді, транзакциялар кітабына қосады.
- Анализаторларды, бақылаушыларды және фреймворк ұстайтын кез келген есеп жүргізуді жаңартады.
Енді 150,000 барға, содан кейін 80 комбинацияға көбейтіңіз: сканерлеу сайын он екі миллион бар-итерация, әрқайсысы Python деңгейіндегі бір уыс нысан бөлулері мен динамикалық диспетчерлер. Python-ның операция сайынғы үстеме шығыны — атрибут іздеу немесе шағын бөлу үшін ондаған-жүздеген наносекунд — бір рет тривиалды, ал он екі миллион рет апатты. bt жағдайы — сол аурудың бір нұсқасы: ол әр бардың орнына тек мәміле күндерінде қайта теңгерсе де, әр қайта теңгеру алго нысандарының ағашын бағалайды және pandas-пен қолдау көрсетілген портфель кітабын түртеді, ал олардың комбинация сайын 2,707-і бар, 80-ге көбейтілген.
Аңғал pandas циклі backtrader мен bt-ды дөрекі себеппен жеңеді: ол бар сайын азырақ жасайды. Ол брокерді, оқиға нысандарын, анализатор стегін, тапсырыс-бағыттау күй машинасын аттап кетеді. Ол pandas-тың жол сайынғы ұсқынсыз салығын төлейді, бірақ сол жалғыз ұсқынсыз салық фреймворктің ұқыпты, мүмкіндіктері толық, оқиға сайын бір нысан салығынан әлі де арзан. Бэктестті "позиция × келесі кіріс − ақылар"-ға дейін жалаңаштағанда, фреймворктің бар сайын жасайтынының көбі — іздеу кезінде пайдаланбайтын үстеме шығын.
Міне тұзақ: үстеме шығын — сіз фреймворкті таңдаған себеп. Сіз нақты брокерді қаладыңыз. Сіз анализаторларды қаладыңыз. Қорытынды растау кезінде оның бәрін қалайсыз. Екінші ұштан тек бір скалярлық мақсатты оқитын 10,000-комбинациялы іздеу кезінде сіз айналымдар жасату үшін лимузинге төлеп жатырсыз.
Кестенің екінші ұшы: векторланған және компиляцияланған
Кестенің жоғарғы жағы — бар сайынғы Python-ды толығымен жойғанда болатын нәрсе.
- vectorbt (56.9 combos/s) бүкіл стратегияны numpy/numba массив операциялары ретінде өрнектейді. Python-да бар циклі жоқ — сигнал, позиция, PnL бәрі массив деңгейінде. Ол сканерлеуді 1.4 секундта орындайды,
bt-дың 22.5 минутына қарсы: бірдей жұмыста шамамен 950× жылдам. (vectorbt дизайнын vectorbt шолуында және кеңірек pandas-vs-polars салыстыруында тереңірек қарастырамыз.) - numba (39.7) — пішіні өзгермеген, жергілікті кодқа JIT-компиляцияланған pandas циклі. M0-мен бірдей алгоритм, бірақ
@njit0.28 combos/s-ты 39.7-ге айналдырады — декоратордан ~140× жеделдету, өйткені скалярлық циклде үстемдік еткен интерпретатор үстеме шығыны жай ғана буланып кетеді. - mp + numba (246) компиляцияланған ядроны CPU ядролары бойынша жүгіртеді. Комбинациялар ұятты түрде параллель — әрқайсысы тәуелсіз — сондықтан мультипроцессинг JIT үстінде дерлік сызықтық түрде масштабталады.
- жергілікті Rust (~350) Python желімінің соңғы қалдығын алып тастайды: бүкіл сканерлеу жергілікті.
- MLX GPU (779) сканерлеуді Apple-silicon GPU ядросына бейнелейді. 80 комбинация арифметиканың 80 параллель жолағына айналады; сканерлеу сіз енгізу пернесін жібергенге дейін аяқталады.
Екі нәрсені дәл атап өткен жөн. Біріншіден, numba парадигма тілден маңыздырақ екенін дәлелдейді. M0 мен numba сол алгоритмді жүгіртеді — айырмашылық тек біреуі бар сайын интерпретацияланатын Python, ал екіншісі компиляцияланған. Бұл — бақыланатын A/B-дегі бүкіл фреймворк салығы: ішкі циклден интерпретаторды жою үшін ~140×. Екіншіден, numba (39.7)-дан mp+numba (246)-ға, MLX (779)-ға секіру енді қозғалтқыш туралы мүлдем емес — ол оркестрация мен аппараттық құрал туралы. Бар сайынғы салық жойылған соң, жылдамдық қанша комбинацияны параллель және қандай кремнийде жүгіртетіндіктің мәселесіне айналады. Біз бұл толық прогрессияны бэктест қозғалтқышы жылдамдық сатысында жүріп өтеміз, ал соңғы шақырымның процесс/сериялау шығынымен неге үстемдік етілетінін IPC салығы мақаласында.
Біз нені өлшемедік (және неге сізге айтып отырмыз)
Бенчмарктің сенімділігі оның қолдан жасаудан бас тартатын нәрсесінде тұрады. Біз сегіз қозғалтқышты паритет аясында басынан аяғына дейін жүгірттік. Бірнеше белгілі фреймворкке біз сан қоймадық, және өлшемеген санды экстраполяциялаудан гөрі оларды адал тізіп бергенді жөн санаймыз:
- zipline / zipline-reloaded — оқиғаға негізделген, Quantopian тегі. Ауыр орнату (толық сауда күнтізбесі мен деректер бумасы), бұл алма-алма комбинация сайынғы уақыт өлшеуді қиындатады. Сәулет бойынша ол backtrader-мен бірге оқиғаға негізделген лагерьде отыр; біз оны кестенің сол ұшына жақын күтер едік, бірақ мұны дәлелдемедік.
- nautilus_trader — Rust/Cython ядросы бар оқиғаға негізделген, тірі паритет үшін нақты жасалған. Оның ядросы компиляцияланған, сондықтан ол толық Python салығын төлемеуі ықтимал оқиғаға негізделген қозғалтқыш — біз әлі жүргізбеген шынымен қызық өлшем.
- QuantConnect Lean — C#-негізделген, мүлдем басқа орындалу ортасы; Python сынақ жүйесінде тікелей салыстырылмайды.
- Jesse — оқиғаға негізделген, крипто-бағдарлы; біз оның дизайнын жеке жазбада қарастырдық, бірақ мұнда бенчмарктемедік.
- QSTrader — оқиғаға негізделген, портфель-бағдарлы; сол парадигма ескертпелері.
- fastquant — біз көрдік; орнату/API біздің ортамызда бұзылған еді, сондықтан сан жоқ. Біз оны болжамаймыз.
Біз хабарлайтын сандар туралы екі адал ескертпе. vectorbt, numba, mp+numba, жергілікті Rust және MLX сандары төрт оқиғаға негізделген жолды шығарған OSS сынақ жүйесінен емес, бірдей жұмыс жүктемесіндегі өз қозғалтқыш сатымыздан алынған — бұл сол жұмыс жүктемесі, бірақ басқа өлшеу қондырғысы, ал жергілікті Rust саны — тығыз өлшем емес, шамамен ~350. Ал абсолютті combos/s аппараттық құралға тәуелді; көшетіні — реттілік пен арақатынастар, олар ешбір ақылға қонымды аппараттық айырмашылық оларды аудармайтындай жеткілікті үлкен (жоғарыдан төменге 13,000×, pandas-фреймворк аударылуы үшін 2.5–4.7×).
Оқиғаға негізделген қозғалтқыштарды қорғауда

Мұны "оқиғаға негізделген бэктестерлер жаман" деп оқу оңай болар еді. Бұл — қате сабақ, әрі әділетсіз.
Оқиғаға негізделген қозғалтқыштар басқа жұмысқа арналған, әрі олар онда шебер. Бар сайынғы брокер, тапсырыс өмірлік циклі, толтыру логикасы, анализаторлар — бұл механизм бэктестті тірі трейдингке мүмкіндігінше жақын ұқсас ету үшін бар. Мақсатыңыз жақында орналастыратын стратегияңыздың жалғыз, сенімді дайындық репетициясы болғанда, сіз қозғалтқыштың әр толтыруға тер төгуін, ішінара орындауларды модельдеуін, маржаны құрметтеуін және алу мүмкін болмаған бағаларда сауда жасауға рұқсат бермеуін қалайсыз. Ол дәлдік — өнім. Оның орындалу шығыны — нақтылықтың бағасы, ал бір жүгіру үшін баға елеусіз.
Сәтсіздік қозғалтқышта емес; оны қате фаза үшін пайдалануда. Зерттеудің қарама-қарсы талаптары бар екі бөлек фазасы бар:
- Іздеу өткізу қабілетін қалайды. Сіз көбі қоқыс болатын ландшафтты зерттеп жатырсыз, әрі екінші көзқарасқа тұрарлық аз санды табу үшін мыңдаған нүктені бағалауыңыз керек. Нүкте сайынғы дәлдік әрең маңызды — сіз орналастырмайсыз, рейтингтейсіз. Мұнда фреймворк салығы — таза ысырап.
- Растау дәлдікті қалайды. Сізде бір уыс үміткер бар, әрі олардың нақты орындалудан, ақылардан, сырғанаудан және қағаз кірістерін үрлейтін алдын ала қарау тұзақтарынан аман қалатынын мүмкіндігінше дәл білуіңіз керек. Мұнда оқиғаға негізделген қозғалтқыш өз шығынын ақтайды.
Фреймворк салығы жазалайтын қате — іздеуіңізді растау қозғалтқышыңызда жүгірту — 99%-ын лақтыратын ландшафтты зерттеу үшін лимузин тарифтерін төлеу.
Шешім ережесі
Практикалық түйін бір шешімге сығылады:
Векторланған/компиляцияланған қозғалтқышта іздеңіз. Аман қалғандарды оқиғаға негізделгенде растаңыз.
Нақты:
- Сканерлеу үшін жылдам ядроны салыңыз немесе алыңыз. Дайын күйінде қаласаңыз — vectorbt; егер стратегияңыз таза векторланбаса — numba-компиляцияланған цикл (
@njitдекораторы жалғыз мұнда ~140× сатып алды). Бүкіл параметр кеңістігін одан өткізіңіз. - backtrader,
bt, zipline немесе кез келген ауыр оқиғаға негізделген фреймворкте ешқашан үлкен сканерлеу жүгіртпеңіз. Егер солардың біріндегі сканерлеу сіздің тар жолыңыз болса, шешім үлкенірек машина емес — қате қозғалтқыш. Тіпті аңғал pandas циклі олардың екеуін жеңер еді. - Қысқа тізімді дәлдік тексеруі үшін оқиғаға негізделген қозғалтқышқа көтеріңіз. Бір уыс аман қалғанды алып, оларды нақты қозғалтқышта қайта жүгіртіңіз, онда брокер моделі мен толтыру логикасы жылдам ядро абстракциялап тастаған мәселелерді әшкерелей алады.
- Екеуінің арасында паритетті мәжбүрлеңіз. Жылдам қозғалтқыш пен дәлдік қозғалтқышы белгіленген конфигурацияда мәмілелер саны мен PnL бойынша келісуі керек (бізде L=104 кезіндегі ±1-мәміле тексеруі), әйтпесе іздеу мен растау әртүрлі стратегияларды өлшеп жатыр, ал бүкіл құбыр өтірік.
Бұл — іздеу мен растаудың қарама-қарсы шығын профильдері болғанда әрдайым пайда болатын дәл сол екі жылдамдықты сәулет, әрі дәл сол себепті біздің стектің өзі параметр іздеу үшін жылдам векторланған/компиляцияланған жолды сақтайды және ауыр механизмді қорытынды мақсат функциясын бағалау мен үстірт тексерулеріне сақтайды.
Түйіндер
- Танымал ≠ сканерлеу үшін жылдам. Бір бірдей, паритеті бекітілген жұмыс жүктемесінде (150k бар, 80 HMA қиылысу комбинациясы, 2,707 мәміле) backtrader (0.11 combos/s) және
bt(0.06) екеуі де аңғал pandas циклінен (0.28) баяу жүгірді. Жетілген, көп жұлдызды фреймворк автоматты түрде жылдам таңдау емес. - Фреймворк салығы бар сайын, ал сканерлеу оны көбейтеді. Сканерлеу сайын он екі миллион бар-итерация, әрқайсысы оқиға нысанын, кері шақыруды және брокер айналымын алып жүреді. Бір жүгіруде көрінбейтін шығын — он мыңда бүкіл шот.
- Парадигма төбені белгілейді. Векторланған/компиляцияланған қозғалтқыштар (vectorbt 56.9, numba 39.7, mp+numba 246, жергілікті Rust ~350, MLX 779) оқиғаға негізделген қозғалтқыштарды екіден төртке дейінгі реттік дәрежеге жеңеді — жоғарыдан төменге ~13,000×-ке дейін. Сол алгоритм, тек JIT-компиляцияланған, 140× жылдам жүрді.
- Барлық оқиғаға негізделген қозғалтқыш тең емес. backtesting.py (1.42) және PyAlgoTrade (0.51) әлі де аңғал базалық көрсеткішті жеңеді; салық бар сайынғы механизм қаншалықты ауыр болуымен масштабталады. Іске асыру төбенің астында қайда түсетініңізді шешеді.
- Екі қозғалтқыш, екі фаза. Векторланған/компиляцияланған ядрода іздеңіз; аман қалғандарды нақты оқиғаға негізделген қозғалтқышта растаңыз. Екеуі де сол стратегияны өлшеуі үшін олардың арасында мәмілелер саны/PnL паритетін мәжбүрлеңіз.
- Нені өлшегеніңіз туралы адал болыңыз. Біз сегіз қозғалтқышты паритет аясында бенчмарктеп, өлшемегендерін (zipline, nautilus_trader, Lean, Jesse, QSTrader және жүгіруден бас тартқан fastquant орнатуы) олар үшін сандар ойлап табудың орнына тізіп бердік.
Жайсыз түйін: егер параметр сканерлеуі сіздің тар жолыңыз болса, мәселе шамасы сіздің машинаңызда да, стратегияңызда да емес. Мәселе — сіз бір адал жүгіру үшін жасалған қозғалтқышта іздеу жүгіртіп жатырсыз — әрі сіз ұстауға тым ұялған pandas циклімен жылдамырақ болар едіңіз.
Толық сынақ жүйесі, паритет тұжырымдары және қозғалтқыш сайынғы шикі JSON нәтижелері benchmarks/bench_oss_engines.py мен benchmarks/results_oss/ ішінде 250dbb5 коммитінде тұрады. Сатының компиляцияланған/GPU ұшы үшін бэктест қозғалтқышы жылдамдық сатысын және IPC салығы талдауын қараңыз.
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.