Pythonda öz Domain Xüsusi Dili yazmaq

Charl Folscher'in Unsplash'dəki şəkli

İnvestisiya anlayışlarını tapmaq üçün araşdırma zamanı qazma işlərini avtomatlaşdırmaq və maliyyə strategiyalarının geribildirimini asanlaşdırmaq qərarına gəldim.

Bir neçə prioriteti nəzərə alaraq maliyyə geribildirimləri üçün mətnli bir interfeys yaratmağa başladım. İstifadəmin sadə olmasını, lakin maliyyə strategiyalarının mürəkkəbliyinə uyğunlaşmasını istədim.

Əvvəlcə tamamilə təbii bir dil interfeysinə addım atan bir rəsmi qrammatikanı təyin etmək qərarına gəldim. Nəticədə danışıq strategiyamızın itkin elementlərini, qeyri-müəyyənliyini və bütün yaxşılıqlarını tapmağa imkan verən bir ticarət strategiyasını sözlə təsvir etmək istəyərdim.

Nəhayət, bu yazının məqsədi geri çəkilmənin faydaları və ya çatışmazlıqlarını müzakirə etmək deyil. Ancaq Parsing Expression Qrammatikasından (PEG) istifadə edərək öz Domen Xüsusi Dilinizi necə yaratdığınıza dair bir fikir vermək.

Niyə PEG

Seçim bir neçə səbəbə görə bir PEG qrammatikasına və analiz generatoruna düşdü:

  • dostlar və həmyaşıdlar bunu tövsiyə etdilər
  • populyarlıq artır, məsələn, Guido Van Rossumun PEG parserlərindəki seriyalarına baxın
  • alətlər və onların istifadəsi asanlığı, məsələn, 竜 TatSu
  • və nəhayət, dilimi müəyyənləşdirmək üçün Genişləndirilmiş Backus-Naur Forma (EBNF) meta-qrammatikasından istifadə etmək fikri xoşuma gəldi, baxmayaraq ki bu PEG spesifik deyil.

Ayrıca, dilin tərifinə diqqət yetirmək istədiyim üçün əl yazısından qaçdım. Beləliklə, EBNF qrammatikasından parser yaradan 竜 TatSu istifadə etdim.

Niyə normal ifadələr olmur

Ticarət strategiyalarında son dərəcə müəyyən bir komponent var, buna görə özünüzdən soruşa bilərsiniz ki, niyə mətnli təsvirdə onları təcrid etmək üçün adi ifadələri istifadə etmirsiniz?

Asan bir qazanma kimi səslənsə də, mən buna qarşı qərar verdim və səbəbi iki qatlıdır: birincisi, əvvəlcə hansı strategiyaları dəstəklədiyimi və bu mürəkkəbliyi idarə edərkən bu dəsti necə böyüdüyümü müəyyənləşdirməliydim.

İkincisi, bobince'in "Niyə HTML-ni regex ilə analiz edə bilməzsiniz" sözünün tərcüməsi burada reallaşdırmağa çalışdığım şeylərlə güclü rezonans doğurdu. Təəssüf ki, Stack Overflow-da ən yaxşı cavab və hər hansı bir proqram mühəndisinin ən azı bir dəfə oxuması lazım olan bir əsasıdır!

Başqa sözlə, hiss etdim ki, müntəzəm ifadələr Jenga üslubunda bir yerə yığılmış və fundamental bir binanın xırda dəyişikliyi altında çökməyi gözləyən kənar işlərin qarışıqlığı ilə başa çatacaqdı.

Sadə bir qrammatika

Backstesting üçün rəsmi qrammatikanın nəyə nail ola biləcəyini təsvir etməyin ən yaxşı yolu bir nümunə ilə:

TSLA alsansa SMA (5)> SMA (50)

Bu bəyanat aşağıdakı aşırı strategiyanı əhatə edir:

Qiymətlərindəki 5 dövrlük Sadə Hərəkət ortalaması 50 dövrlük hərəkət ortalamasını keçərsə Tesla-nı satın alın. Qarşı şərt başlandığı təqdirdə mövqeyi bağlayın və ya qısa keçin.

Fikir budur ki, sürətli hərəkət edən ortalama kəskin dəyişiklik çarpaz istiqamətdə bir cərəyanın başlanğıcını göstərir. Beləliklə, aşağıdan yavaş hərəkət edən ortalamanı keçərsə, artan bir tendensiyanı və aşağıya doğru enən kəsişmədə bir azalma göstərməlidir.

Və burada hərəkətin son nəticəsi:

Bir giriş qutusu mətni sintaksis yoxlayan və mənalı rəy verən PEG analizatoruna göndərir. Bəyanat doğruldulduqdan sonra süjetlər və ölçülər ilə arxa bir yer yaradıram. Bunun xaricində şəbəkə gecikməsi real vaxt rejimində baş verir.

İlkin nümunə kimi ifadələri müəyyən edən qrammatika aşağıda verilmişdir (EBNF formasının dəyişməsi):

@@ qrammatika :: BAAS @@ xəbərsiz :: Doğrudur
start = bəyanat $;
bəyanat = buysell aktivi if_expr [kvalifikator];
buysell = 'almaq' | 'satmaq';
varlıq / /\\\\\ - xilas+/;
if_expr = 'if' göstərici binop göstəricisi;
göstərici = / \ w + / '(' nömrə ')';
sayı = / \ d + /;
binop = '<=' | '<' | '> =' | '>';
kvalifikator = 'uzun müddət' | 'qısa bir müddət';

Bunu hərəkətdə görmək üçün yuxarıdakıları base.ebnf olaraq saxla (adı və uzadılması əsla fərq etmir) və TatSu quraşdırılmış (və ya əvvəlcə tatsu quraşdırın) quraşdırılmış piton mühitinə sahib olun və əmr satırından yerinə yetirin:

tatsu - yaradır-analiz edən baza.ebnf - faylı baza_parser.py

Sonra pitonda:

baza_parser idxalından BAASParser
text = 'TESLA BU SMA (5)> SMA (50) almaq' parser = BAASParser () parser.parse (mətn)

Aşağıdakı bir nəticə çıxarır:

['almaq', 'TESLA', ['əgər', ['SMA', '(', '5', ')'], '>', ['SMA', '(', '50', ' ) ']]]

Çıxış qrammatikasını bilmədən artıq təşkil edilmişdir, lakin həqiqətən istifadə edilə bilməz. Bunun necə inkişaf etdirə biləcəyimizi sonrakı hissədə təhlil edilmiş elementlərə necə şərh etmək lazım olduğunu izah edəcəyəm.

Qrammatika ətraflı

Qrammatikanı şərh və ya dəqiqləşdirmədən əvvəl, onun qaydalarına keçək.

İlk iki sətir TatSu direktivləridir:

@@ qrammatika :: BAAS @@ xəbərsiz :: Doğrudur

Qrammatikanın adını - BAAS - müəllifin adını, yəni BAASParser'i və işin həssaslığını təyin edən analizatorun vəziyyətini müəyyənləşdirirlər.

Sonra başlanğıc adlandırılması lazım olan ilk qayda:

start = bəyanat $;

və sadəcə bəzi mətn girişinin girişin sonu $ və ya başqa bir sözlə izlədiyi bir ifadə olduğu təqdirdə etibarlı olduğunu söyləyir. TatSu'nun nümunələrini izləməyi və başlanğıc qaydasını zəruri saxlamağı seçdim.

Bəyanat qaydası, bütün dillərin yüksək səviyyəli quruluşunu təyin etdiyim yerdir:

bəyanat = buysell aktivi if_expr [kvalifikator];

yəni müəyyən bir şərt təsdiqlənsə bir şey alırıq və ya satırıq. Təsnifat uzunmüddətli strategiyaları idarə etmək üçün əlavə bir arqumentdir (kvadrat mötərizədə əlavə olunur).

Vaxtımın çoxunu bu dildə yazıldığında ticarət qaydalarının necə oxunacağına qərar verdim. İstəyirdim ki, intuitiv, əhatəli və genişlənsin.

Buysell qaydası:

buysell = 'almaq' | 'satmaq';

yəni sözün əsl alqı-satqısı ilə başlamaq lazımdır.

Aktiv hər hansı bir dəstək bildirən sifarişdir:

varlıq / /\\\\\ - xilas+/;

müntəzəm bir ifadə uyğun məktublar, nömrə, alt altlar, nöqtələr və ya tire, məsələn BTC-USD.

Mütəmadi ifadələrdən çəkinmək istədiyimi bildirsəm də, hər hansı bir vasitə üçün həmişə yaxşı bir ölçü və kontekst var. Xüsusilə, təhlil kontekstini mövqe lövbərləri ilə və ya irəli / arxayınlıqla baxmaq istəmirəm. Alternativ olaraq qaydanı yaza bilərdim:

aktiv = 'a' | 'b' | 'c' | ...;

bütün əlifba, nömrələr və bəzi durğu işarələrini EBNF ilə eyni şəkildə daxil etmək üçün bütün yollar - Vikipediya nümunələrindəki nümunələr. Ancaq bu qrammatikanı çox səs-küylü edərdi və şübhə edirəm ki, Pythonda bir performans qazancı olar.

Əgər şərt ticarət strategiyasının əsasını təşkil edirsə:

if_expr = 'if' göstərici binop göstəricisi;

və bir başlamalı və iki texniki göstəricinin vahid müqayisə qaydasına sahib olmalıdır. Sol rekursiya tətbiq etməmək üçün məqsədim üçün çox əsas tutdum.

Müqayisə hərfi hərfi ola bilən binop tərəfindən tutulur:

binop = '<=' | '<' | '> =' | '>'

və bərabərsizlik qaydası yaradır.

Şərti ifadədəki göstərici qayda texniki göstəricinin adı və mötərizədə yerləşdirilən ədədi parametr kimi təyin olunur:

göstərici = / \ w + / '(' nömrə ')';

Və sayı:

sayı = / \ d + /;

Həm göstərici adı, həm də onun parametrləri aktiv qaydalarına tətbiq edildiyi eyni ifadələrin tətbiq olunduğu müntəzəm ifadələrdir.

Nəhayət, seçici sadəcə:

kvalifikator = 'uzun müddət' | 'qısa bir müddət';

Başqa sözlə, bəyan qaydası hərfi uzun (qısa müddətdə) yalnız aktivi ala (sata) biləcək bir strategiyanı ifadə etməklə bitə bilər.

Göndərmə səhvləri

TatSu, mənalı təhlil səhvlərinə qadir olan bir analizator hazırlayır. Ancaq səhv mesajı qaydalarınızın təşkilindən asılıdır.

Məsələn, itkin parametr ilə ifadəni təhlil edərkən:

TSLA alsaq SMA (> SMA (50)

Aşağıdakı səhvimi alıram:

tatsu.exceptions.FailParse: (1:17) gözləyir : TSLA alsaq SMA (> SMA (50) ^.)

Ok oxunun itkin parametr altında necə yerləşdirildiyinə diqqət yetirin. Çünki sayı ayrı bir qaydadır. Ancaq nömrəni indikatora aşağıdakı şəkildə daxil etsəydim:

göstərici = / \ w + / '(' / \ d + / ')';

onda səhv yalnız çatışmayan parametr yerinə yerinə yetirilməmiş bir göstərici tanıyacaqdır. Sözügedən göstərici göstəriş qaydasında uğursuz olacaq, ancaq harada olduğunu dəqiq söyləməyəcək:

tatsu.exceptions.FailParse: (1:12) gözləyir : TSLA alsaq SMA (> SMA (50) ^.)

Göstərici adından əvvəl ox boşluğuna necə işarə etdiyinə diqqət yetirin.

Beləliklə, terse olmağa çalışmayın, mənalı səhvlərə ehtiyac duyduğunuz zaman qaydaları pozun.

Bölüşdürülmüş elementləri qeyd etmək

Əvvəldə qeyd etdiyim kimi, giriş mətnini iç siyahıya salmaq əlçatmazlığa malikdir. Şükürlər olsun ki, TatSu bizə qayda elementlərini annotasiya etməyə və siyahı əvəzinə bir lüğət çıxarmağa imkan verir.

Beləliklə, aşağıdakı Abstrakt Sintaksis Ağacını (AST) əldə etmək yerinə:

['almaq', 'TESLA', ['əgər', ['SMA', '(', '5', ')'], '>', ['SMA', '(', '50', ' ) ']]]

Şərh edilmiş AST əldə edə bilərik:

{'aktiv': 'TSLA', 'şərt': {'sol': {'adı': 'SMA', 'parametr': '5'}, 'op': '>', 'sağ': {' adı ':' SMA ',' parametr ':' 50 '}},' direktiv ':' almaq ',' kvalifikator ': Yoxdur}

Bir lüğət yalnız özünü izah etmir, AST ['aktiv'] və AST [1] müqayisə edin, lakin prinsipcə qrammatikadan əvvəlcədən bilik tələb etmir. Əslində, AST-dən keçə bilərik, məsələn, bir gəzintiçi yardım sinfi ilə və istədiyiniz nodu adı ilə götürə bilərik.

Bəzi_name-ni qabaqlayaraq qayda elementlərinə əlavə edə bilərsiniz: beləliklə müvafiq qaydaları yenidən yaza bilərik:

bəyanat = direktiv: buysell aktiv: aktivin vəziyyəti: if_expr [təsnifat: təsnifatçı] $;
if_expr = 'if' sol: göstərici op: binop right: göstərici;
göstərici = adı: / \ w + / '(' parametr: nömrə ')';

Direktivə diqqət yetirin:, aktiv:, şərt:, sol:, sağ:, binop:, təsnifat:, adı: və parametr: əlavə.

Açıq suallar

Bu yazıda PEG analiz generatoru TatSu'dan istifadə edərək öz qrammatikamızı necə yaratmağınıza əməli bir nümunə verməyə çalışıram. Qarşılaşdığım bəzi seçimlər və məsələlər barədə açıq olmağa çalışdım. Ancaq bunların bəziləri açıq qalır və onların ümumiyyətlə PEG qrammatikası / generatorları və ya alternativləri olduqda necə davranıldığını öyrənmək istərdim.

Xüsusi səhv mesajlarını necə müəyyənləşdirirsiniz?

TatSu artıq aydın səhv mesajları təqdim edərkən, səhv mesajlarını son istifadəçiyə uyğunlaşdırmağı üstün tuturam. Beləliklə, demək əvəzinə:

tatsu.exceptions.FailParse: (1:12) gözləyir 

Seçdim:

Parametrini izləyən texniki göstərici adını gözləmək

Bunu etmək üçün TatSu səhvlərini ələ keçirirəm və sabit kodlaşdırılmış bir lüğətdən uyğun bir səhv alıram.

Bir elementə hərfi məcmuədən dəyərlər götürməyə necə icazə verirsiniz?

Məsələn, sonrakı mərhələlərdə genişləndirə biləcəyim bir sıra aktivləri dəstəkləyirəm. Qrammatikaya, yəni TSLA-ya şifrələnmiş alternativlərlə çox uzun bir mükəmməl bir qayda yaza bilərdim. AMZN | AAPL | ….

Lakin, bu yanaşma səs-küylü və abstraksiya itkisi hiss edir. Beləliklə, bir sərt kodlu dəstə qarşı sonrakı bir ötürmə ilə ona yaxınlaşıram.

Nəticə

Pythonda öz Domain Xüsusi Dilinizi necə qurmağınıza dair bir real nümunə ilə ümumi bir məlumat verirəm. Daha quruluşlu bir həll ehtiyacınız varsa, ancaq vaxtınız yoxdursa və ya hamısını içəri daxil etməyiniz lazım deyilsə, yanaşma faydalıdır.

Ehtiyaclarım və işə hazırlaşmağım arasında bir güzəştə getmək üçün qrammatika tərifinə diqqət yetirirəm və TatSu-nun mənim üçün analitik yaratmasına icazə verirəm.

Beləliklə, bir az vaxt ayırmaq üçün bir az vaxt ayırsanız və bunun üçün geniş bir həll üçün təməl qoymaq lazımdırsa, bu sizin üçün də düzgün yanaşma ola bilər!