From 7dd938c368bedbe17c728ce12d310ad0fba4a403 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Thu, 25 Jul 2024 17:42:09 -0700 Subject: [PATCH 1/8] Add new data structures and event handling classes for trading platform. Include base classes, value and bar structs, event arguments, emitters, listeners. Update ruleset for SonarLint. --- ...tform.BusinessLayer-Miha’s MacBook Pro.dll | Bin 0 -> 1224096 bytes .refactoring/base.cs | 92 ++ .refactoring/test.dib | 92 ++ .sonarlint/mihakralj_quantalibcsharp.ruleset | 778 ++++++------ Calculations/Basics/CORR_Series.cs | 104 +- Calculations/Basics/COVAR_Series.cs | 92 +- Calculations/Calculations.csproj | 158 +-- .../ClassStructures/Pair_TSeries_Abstract.cs | 264 ++--- Calculations/Logic/CROSS_Series.cs | 88 +- Calculations/Logic/EQUITY_Series.cs | 180 +-- Calculations/Logic/TOrders.cs | 66 +- Calculations/_Updated/ADL_Series.cs | 136 +-- Calculations/_Updated/ADOSC_Series.cs | 178 +-- Calculations/_Updated/ALMA_Series.cs | 226 ++-- Calculations/_Updated/ATRP_Series.cs | 172 +-- Calculations/_Updated/ATR_Series.cs | 174 +-- Calculations/_Updated/BBANDS_Series.cs | 222 ++-- Calculations/_Updated/BIAS_Series.cs | 142 +-- Calculations/_Updated/CCI_Series.cs | 170 +-- Calculations/_Updated/CMO_Series.cs | 178 +-- Calculations/_Updated/CUSUM_Series.cs | 140 +-- Calculations/_Updated/DECAY_Series.cs | 164 +-- Calculations/_Updated/DEMA_Series.cs | 252 ++-- Calculations/_Updated/DWMA_Series.cs | 242 ++-- Calculations/_Updated/EMA_Series.cs | 238 ++-- Calculations/_Updated/ENTROPY_Series.cs | 172 +-- Calculations/_Updated/FWMA_Series.cs | 188 +-- Calculations/_Updated/HEMA_Series.cs | 230 ++-- Calculations/_Updated/HMA_Series.cs | 174 +-- Calculations/_Updated/HWMA_Series.cs | 262 ++--- Calculations/_Updated/JMA_Series.cs | 350 +++--- Calculations/_Updated/KAMA_Series.cs | 212 ++-- Calculations/_Updated/KURTOSIS_Series.cs | 190 +-- Calculations/_Updated/MACD_Series.cs | 154 +-- Calculations/_Updated/MAD_Series.cs | 156 +-- Calculations/_Updated/MAE_Series.cs | 152 +-- Calculations/_Updated/MAMA_Series.cs | 376 +++--- Calculations/_Updated/MAPE_Series.cs | 168 +-- Calculations/_Updated/MAX_Series.cs | 134 +-- Calculations/_Updated/MEDIAN_Series.cs | 170 +-- Calculations/_Updated/MIDPOINT_Series.cs | 142 +-- Calculations/_Updated/MIDPRICE_Series.cs | 128 +- Calculations/_Updated/MIN_Series.cs | 134 +-- Calculations/_Updated/MSE_Series.cs | 150 +-- Calculations/_Updated/OBV_Series.cs | 190 +-- Calculations/_Updated/RMA_Series.cs | 230 ++-- Calculations/_Updated/RSI_Series.cs | 238 ++-- Calculations/_Updated/SDEV_Series.cs | 162 +-- Calculations/_Updated/SLOPE_Series.cs | 242 ++-- Calculations/_Updated/SMAPE_Series.cs | 148 +-- Calculations/_Updated/SMA_Series.cs | 190 +-- Calculations/_Updated/SMMA_Series.cs | 184 +-- Calculations/_Updated/SSDEV_Series.cs | 162 +-- Calculations/_Updated/SVAR_Series.cs | 160 +-- Calculations/_Updated/T3_Series.cs | 320 ++--- Calculations/_Updated/TBars.cs | 276 ++--- Calculations/_Updated/TEMA_Series.cs | 238 ++-- Calculations/_Updated/TRIMA_Series.cs | 166 +-- Calculations/_Updated/TRIX_Series.cs | 234 ++-- Calculations/_Updated/TR_Series.cs | 156 +-- Calculations/_Updated/TSeries.cs | 220 ++-- Calculations/_Updated/VAR_Series.cs | 160 +-- Calculations/_Updated/WMAPE_Series.cs | 162 +-- Calculations/_Updated/WMA_Series.cs | 206 ++-- Calculations/_Updated/ZLEMA_Series.cs | 188 +-- Calculations/_Updated/ZL_Series.cs | 178 +-- Calculations/_Updated/ZSCORE_Series.cs | 174 +-- Indicators/Charts/2MACross_chart.cs | 562 ++++----- Indicators/Charts/2MASlope_chart.cs | 605 +++++----- Indicators/Charts/JMA_chart.cs | 193 +-- Indicators/Charts/TrailingStop.cs | 190 +-- Indicators/Indicators.csproj | 110 +- LICENSE | 402 +++---- Strategies/Strategies.csproj | 104 +- Tests/Basic tests/Indicators.cs | 308 ++--- Tests/Basic tests/Oscillators.cs | 318 ++--- Tests/Basic tests/TBars_input.cs | 190 +-- Tests/Pairs/ADD_Test.cs | 128 +- Tests/Pairs/DIV_Test.cs | 128 +- Tests/Pairs/MUL_Test.cs | 128 +- Tests/Pairs/SUB_Test.cs | 128 +- Tests/Pairs/TBars_Test.cs | 224 ++-- Tests/Validations/Trends/Skender.cs | 972 +++++++-------- Tests/Validations/Trends/Tulip.cs | 1048 ++++++++--------- ...o illuminate and standardize the vast d.md | 4 +- docs/Trading_example.ipynb | 4 +- 86 files changed, 9367 insertions(+), 9153 deletions(-) create mode 100644 .github/TradingPlatform.BusinessLayer-Miha’s MacBook Pro.dll create mode 100644 .refactoring/base.cs create mode 100644 .refactoring/test.dib diff --git a/.github/TradingPlatform.BusinessLayer-Miha’s MacBook Pro.dll b/.github/TradingPlatform.BusinessLayer-Miha’s MacBook Pro.dll new file mode 100644 index 0000000000000000000000000000000000000000..11c3b4e32238d9a2ddd1a3227c3444ca2e264b34 GIT binary patch literal 1224096 zcmeFa33wdEng8GFo_j{JMYnM5WJ9vq|MS~Jk3C)WR@GZo zZ@u-_TW`HxecXl@dV%M88U8=?kmudSQ-25B?~Z>qliWA_zCQ2W#m}z1tLNCyt~~k4 z7q&(&yF7Z*<>y>7^2Bp4y)?RF}Px4V*A&5#?d%;2;P$vzNg>2SAGv^)Jb;(2GUxx97x69HP=Xrp!X#XM7g2Qyr2Efci*ei6pB|pKg`1v_1T_MZX#a~8Fwo4yrJC#@A=jT z@{lPDyp(IfJ*9F!qDUz`V%W=tj7h1Msb(g!^;&k@8LcAm(Wu04BP#Rjxkx>NkV#~< zBg2CSg}nfyzD`O#TBJy&kdI)qQg0EQ`7uwZy=sRNwi#?$QR>?w{57_=Y_w_LyceaQ|%iV9Eg5Wlf2mGD#(B*xg2%p@=d;tw3<&Cr_hEu%*AS+A*)vl@c?D2;EtFG8sJ=OX9da( zW2xR@97qY{z$aq_G?qKy8dA#)Z8;g*-fD?xsT0i*&HN2x{T1j8&wAhBe|qdbLma|- zKbr%;hxnuT|Cc%N_YgmY{}o0hjYqQJ_xvA9!HIgzP#fze@MNs}hRaO!X+%+Ge3c)} z#DVabKDbmV@TyC(0088a16Tk6v)uvWKsbafJczo$RrT(rHGa6O$Xzez`g=koPRZMZ zOv+4_T2~_}HY>bM5+xTlm3Y6U+Vn8lSI@^QzuHF#5N60NEF;e-)V;=9N5N9(Px z7rb047rto~GwySQz1*frvP56B@XCkozHt0g6b{!2_+_=2 z&(iDBz4p9q1}T{tukGIBA&)&eU-hR2D&-_Yw-3*FSCS5Gs;yg>Tl3zm=G{OTS#RMR zEo_1`+G^qYmaUL2y4fPGp3%w7*g$jFO^Dyj=wxGT6H_%an|mg-XzfYMM>i`a4Ag#R zZZo>Y3XiX8T`M9hHzVvwHcoqs-Z8X`zEoSKjM^1MU!kW_o(PNA7G_9KRJ2HA%PVLN z%VYFeas<&A?DtFj*6XeB1YYj8C~FzTFvxqy4i*4_h;aZ506@??fCT^`>K(uW0N_ps zumAugzXMpHZ@8qISOv87(wD%4!8E5B6BY!ke!(9=Na3*V<-$ZOW!Ai8uYRpK&5tV9r9PIzLKA5BnjVwrnR z4+mbxCG~sJ{xqW5Q)iMD{9O3TxYjF#<5h9wN=0r}B#53tcwjcD6edn*^4p8x!A8bT+`Qh6^oAB1|w7KHO&Q!rnz1&qTyktu$H?r#~r@VGU zco^m4ZXHf={hS2XMVDaBIML0pAleE`!@VSpY1$2I+G(w@0DzGMU;zLt6MzNA+cRby zl&eT{gC0c4VrU1DH3FdI8x?(6y#~kv-p=_k%SpeaXP?H?{+JWjFnHlVE0xH#io7R| zJWG+&QMROvH^cYF;AaD#Q6tFzT!mFP;pZ!?DkU2Oj}?Md2O0(&S!Dc@rUME+6DU)! zXyo1vzFeeVH*&UI?deaCf+Bhhv~qZsfd@v`{O$L`FOXd*(EW0)Bgu4C!N$z@P@=U9 ztPan%{*6qfoIgWKhTP+u+Us2FXo}3J2EV1HZ)PlRukR6}60M1vr9=Xj+veh2erv@q zgfvx_XNfHAHAsgatS5 z4M|RD)H2&@Uf051M-+Hf31v5rd%Zmma_hG0JDd9GC$!jZWzn~2eOx2trqUs=m8Z%| z+#1VJh>oL0EUH0NAPsFgtv!spx=Y^CCwJ<;oVo|LQ#Z2gvEZvTcP~VbQ&;1z7o&G9 zqBR-oe=S?hPV`mT(EKyN=x0XdLUcU&CqKEZT$sowTY@>Qffg)UK0EUR*1PujwEfHr zTzF64BvEt%cqD_q3|`1*d#*crJa38G+&YO=sohE~Pn`=qADs-W;uWHw1Dh-tqTld@ zWX&GNMSI7o{mE8{vJeQ~b>47GeUKe1O%yRyMXwa_{h1hry+u^2KlOJ095e2liS>s=%|o#)upTMUOP)DCXr z(U4_=z-tw#l^UuHWqslxuWAbVLipn*Y`+q(d6czkXfV~PIi-$FHEl-?hQb=#buXEH z=pkdTWQ*qrHn;`t35&t-$SX%1!~4H7+Cb)7AKc$Jeu#~?&3o?(2H{!!XAQT%i{i!oc4Km4j7_vcJjpe=iU{G&64T^@JZpH^=s5IgFd+n0=A+U~J6ezq?3PM^`j zsJ4zTYn?+wuF^WSA9x4bbRM4FYtWz8NZXk2RfdYj0;OiHo(O#-w3Gaj2H&8S!n{tH zwd7g=0Q#H*SO5Tg;{X-_fHvj;765=A<^UD|a8LrU0DyxNfCT^?k^n3K02RgIumAvb z5eKjU0F(>|umFI=6MzNI^1~m~r-@*oS@uIogDc9+BdwLb$4cximArGA>azj%Gr!p{ zl?g8On*}rp6TB`zyyYzueGO-0O}XEjUBcuU$~6}|PMWCImG<^dnuvFu&7E}NJ31Le z(5}Mo=%k6hU1{&^q>0?~5$`+_cfrltuEMu#y;k@9P$P2P(c-#vtKQYYF7nUE{_ai& z4M|twf9|Adi05O!vy(x?J}*OOOnfi$?Z}5cLuU*k6i)_48W`HnM6b`*6o})Ppfetp zubfvt86|uDyvt|o9E9z$v3!++^7U2w$X5t&(jLb+JqX)FWchlF%D1Sxh9!HlI~( zz8qU}_(}oB9H3lvBqq&MRerVR=5!FYdC9d8HCF9gQeC31Mb%Z- zdbO^sG0knF2VtAPT-IXMsNSeHlr<*1iGd(&vzyCWs+NRxusW!$CD#BGZ$a2*JeL*S zSXq}=mnv)7Wi=V(_L-ZY-Ad#^zc)O#EW831W4rktG4Dpgkk|=>@Jbg8@9Ii2$(ba* zw<}50LI*lcaD?ycN-_zVBt0F(QdN#j9F+hpfGGpTs^od&w=c`U?B1 zurC&gizE^kn@9u)liq0+765Qe0wnlX@R3B+|Pq0M^GP01I3hgfkSHTom{r#&#I-p?)NLHar!fHGXHD zI^2~S?mvQoFkGVkF=(YEH*J($-={r}jlp;rgRmHrUqw6PX(*CGGx8e_-+MGq`-8IA zv(*a+)E^=*RHPn9UaZJq9C@iC%i_olitMC_<1JQT`)j>|1dL&3V0!^!S;o|g=tXga zlcZeqk~p$dk(b4h6^guE5$Uxm$kjY@OCWz9ua%Vx-BW)=qG z_+%g~0N{iKU;(4$uHaq`Zh!hj5=SS8VLm&I`xS=T*b?>%f}^DwWyp;`){i(FiA{vr z>F=^t5=*lXIxeuln6EWjvTA4HPR<~%K|W=|YiXip-ZOcIYrqBBL(UX<)?>TwV_PR- zGVC%is**dCN-Q9o*_D{xm?vw(Cyy~6Hw~C!Ky@SNbS5a9QuqvI3O3#l#I!?C)*5pg zln(b&y~6#T1h)kM+?4<<0N}j|zybi?mjEmP;Qa}}0_Z8u=1yCn?*~Xs+1#JNe8c80 zC^mN{`+S@G+;aOtuqz&>+&|FoU0lDTJJi8->R=GPTj72XOwO@^M?H+LOUDLI@stgm zqidTJ?^I#VH7#gT@;_!k_*i-HV<}EG*MQlQoVeq0!_+ z-84a=(>a8o(4?{uP3D#}nryG!i46W5kj)%JJdZZas5MwkgYb=Wf$LmTgG}^%GFm%x z-i6ST`BC*6r8qgdhyM2WSSI#B*r-<{Foia-BsXDNPl6z$<>((pg?h=$2ho!qiPW>f zEpRMW5&yQ)MlRnv6-K`;oJA(K^>y}63Tuz|RvNnTZ8qr}&Ws*wxU%`5M?U?vK0vDy z1N?Sv{?2xsb&~0CF#S*8E$T_oTL5;yCjnRhz=son1ps^`0ayUQza#()0QhJEumFIM zB>)Qm_;>=a0Dw;<01E*4WCE}NfKMd=3jp}n1YiNtZhz?36payn?hobpJdGPB_Ik6= z4OIKJ(}(K$v{iaEy3pZ*C@Aa`KThN#F39H za#|euq#~!sk$+X>j5zWcMIIkVKBveN;>f*SeK22xi#@K}KKI}3M zo_pAJf|YlIzefk05t1}~qM$IMckZkEX)2qH^?M!dfR)Ci<5+B1%UeizBBh@`*UIUXf45kqwG`I*y#F$Yp}L2#QbW$d-9{9aEV#YqowabJwiF;ee9H zP0R)1fovSfmznvDIF0i12U^|Z>5POu>J}CEryPlZ~o7f|cyW~In^ zsu{hAxIBuA_;+-Cc~;cQX2|lV_)O# z_w~e0;|A}PAA2jR(0fhmGMcbitr_1bi0-3}TCwj{{7@#|j6#06kxcOzW;~Wj`}4z7 zS3|Umm44zgcTEBsEqv3%gH1WsAB;kc<7tv2Gyd?U$p3y#( zqBe~))7L|PW}IKFac;eYUqr>_)M=w_E5+I>xxAq*y*^$7_daJ4gnTLadY5it`NVG^ zJdiPbjfETGVeRl>cv2GP6f;I@Lzd+bsg|qP@>82YT+LPUx2trmfWtzqSg)0u8*uh1 zy_6xTW>HA#86INg)@LDKBYHri&Tc#Ui2`h=Cxd!3U+oS17+u>oBLVQ>x`8&1<#F(N zxdN)F)L4)2khzELd5ee3*?Mtfl|!?Gp7s#u0KJ?66*$`-rhs?9{JR#Vd16tUC-CoL zVcsBO=fg!}s-uj;>}eD^Mv)l?$L5pckI1_*-vj^Y?;P*WM$eG=V_!B8B6jN%elRZI zNX>382i|?;++Abi%a5QGZRMxS67&ge*DeK`%Ft#0xJa6%%V3pUCNxjw2j7!d@F?f< zQFR6U;b7r%2+cMW-Czv9>l79|x<-X>qGzcp{BS2%gp)jqJf%}a@CA$H+GmK8sn@_+ zd#H~=*lw)th@DMEhjMM3N=p;r-Z!8Mn?H}yqZ~vx)2_VrQjQNL6F-@t((DZ!)X+JT ziVn6`QCtkw`Yir4k}HKw)DI0XBox9=lYq?X^xXjg=U+TFOLQ#T(TMK5EEV)mjNxSglmsrk_I?n{8wWdOR~``!!<% zlJ!9Lovxazu4XJW)kR6o)oOoIb2S`xHA@!c%m;B#$8YWVkA*%vg=pX}HG#|d)>oN( zEPJA45Is#IQQ|rdK1k3eYxEVed809SR@uH3LpCb%Z;CXcuj|ou3g>NGT1id4LVD&U z;?m8l)!wPkNRHp?-cz6JE!Dh|Y-2m;?WO*tn=U`7EMh5$)S5?Jrc8{RMrRzvr4;&h{7ED@VJ&3@%fOE#Pz|_6)L7Q2$p*P1VoF zO!W&&{rwBoKewFKzrAwQk9>3WTj0?4Ry&vx`NpsE{8i$+5L-C&q#iy$eqO8R_&Baq zVLLb&2RC^BQWsR=%?E`qu*_F#FYWMH#}W^fR~hAh+pan@b3ax4*_j9Q2xbuT44D1S z=3We{)~hKIJ=0p!nE964c>`^;5QZizZTTZ4TQ0)AJU8gO_r8(h;FHr@}QGfcIl#OmxE1QjUj5AYJ-t?O~QwY4%DGa@TFM1upv1CnRjJ+wf z-=9{uHm+o@nWz?`cm^#%uxF_Pcn#mttjALCtrdCcG?9*$2>;Q7}#Jya8B!);F(- zT3lE|P8T+*sj2Emqvruhm0KS6WRttU4ElY+ADt2im7GheUFcD5}~)o=gMYa$SOm{Q-R4>qMOKz~K=AMso# zdS^Bsf|gYZOfums6s?D-y{Oo4gdEYYkcVCh_$C2=BOvMp6{fWR0kr>@fz>t#l_cF% zJ1C{0QnO>7z99Xszu@%q@6$lj&%Z}tHuq}q#`?KZy7luMx>!dj57mlkDi+&RL>DXM zW4%04$z%C^fp^r~6PRadsHOIS`?qYYeo6!}_j2QOP#%lH8@Mt1<%axzn+?lQ{amG* z1MTG2*V@o@0pXfN&y`ZenY%ox&@?B`rNWl>?((aO4iYY&8f;ap!^k!f4x@v)R+W~H zl7(HIEEXMdB%LiQA$lkMM#e8@%v_x~B)Gtk^FGu0Wv6nK)41}!cI7&eY*+pX5G9rO zJ;KUa{^H6p`Oa^sMkSuN?2vYMt!wPH^TTDG%|JXk&`D9g82Z(tUl`qDLUQOC_odqI z3%YZsFMpz4NnfguurHs0xsnz>d}nrZStlQPUuxCb#08q}-F+FT4#a&KX!k`Yq3vq!*$IDcgM(@&`%QCv%kUS74+-|Ebr3IXlx(xEqEm8Th>axi@^Hnq?tLPqT z4S&N=ZP2d{hVLTe6{>@=WGuS2MGq;%P<3eCnQZ-WHC2PdLBu%im9eJ~EMV-BY>%Ma z!IC|4T(S+)+2U6bMxuJ-ek`)({Nx@4*Ac7~)*;w1WNhRZ!tX7vW2IAUEinnJb$=p0 z#Q_>dU39TEaAZEZp5K_ofB#VQO~ifl0T`5C^b49#PkUeRGGMsJ#*>CKjD7ZxHcVCpuW2_KuE?c32<(KfvzIyqQv1_CV~I%ZWhUHSMo{)LEamg}Qgh4M zUS@~od~bwtJXCz)hn?jJ{?NMs&|y~^Z!72+YnyuqO~0olrWO5;z-;b2;hIRck_~95c6o)%&e>Qq6Vb(<7?1ylX>hGi%EIm0B$LTG6JNd&8^J8a+ zKc@oIZ7hJY^I!t70Dxa401ME^+U|LKk5P58_4y6yJbtT3cuLA*>3rCKCAitOW`3z) zCVao@_FH$+!SI6?u7`KqGYA_pNSLzBOEzBsXgiVUIt=@s98(}?!Ow=Dvh4owPD{6- zllh~K!COWLm)+9(DhGlyzowe;4L%2A?;~3#64!(uQ3>aNwKsh*It8ZEf-={7FO9hh z;#mETVcZ_kkKRWTB>jO}X1d+glxMRhn-%+DyniN8fd|6fl)-BrP2M#Gol|4KAD^oo z1dF;XF1fW0^Am=Vujwr=o?o?RJ-T1d=wNcwFJs*8w8NM0Y1|JdhjOj=$GtG}jK3HJ zqDAyIs6wm$)rgkRI(&+v<2Igw>1>a7ykq17H=8gGQR1 z8;;&KINGl(&=c zMI}^~@P_V$FDYS(65iaM@D(NWD&e)=3HK>ssS@6%1UDC{=|lADic9E0bhrA~UiE@x ztwXC@cLa-uAATAX&a^1&U;%F}vmiEl9WpCW--8Xkh`I?upQh&bRR29jO{GL1CWwN< zOV2lP>K`7BlS%XuU>kQwUL`(UQvH@yGp!ekeTvr|4GTmsGM;&*Tm_2K2G_2Fy}j1I zP*#dyPfhQcsjN|3)I5`_s*845*I&Bp`VZB`$tQIwMKAg`t*&qxp>T*FIviI16qD=clxT9uYp!x&`L8v$u>bKb zx64xPcf!h<_~YT5?QZ-J)%eeYaCEa$!sTl63jHkNCq58}i$2s{^hYXsuN75FxRXi^ z>u0fwc8(9?;!N{276Ws@ch45cM_|rs2J0!$@WVy8ERSoov+7yXjhb zH!YcP2tpFY-He;hV@|(>|1we~e`2s#1G|;~43j3j*<8O!_;8EXOH==(+^!35KgBf& z^XB*MntN?{XL`S}3*MRCZ|Q<}Cim;Q;GMbscEMqM-iLEdiDxlt+1WOSji0|D#=z*@ zo1#jWi2{vYAwUFTE*yLg>iqwknZA1+2eu)tvSCVQ?H8kroT zpU#Q@cuxE+ak+QevmV`H&qnmVIR5VVd1w5*OHWRNOxpBxGtEyrOrsQInq-T*1aWhT z&I!LK1Z-g+xS7GX*OJPsArgN-f2fj#uIc3!q=sGvRXr);675W{Yq@s5T0RjMumx}J zPjRf^^_RjgDOP6Czf55GJZ3XeYV8fsB(GDFp{3Pc_tH^ce_wdyD2-dM;py*V_u94h z+cR%3RpVoKirk1#Yv&S|#hDxR=s}v5B!;hF)rC(vBaK!&!=*aJiYs(4H-X8rjgzf_ z&@+$QG_aCHcr!a!bkb>p>jhjf0o#$Ai8yb46IlVT)Ft_p#2#CNqFpCR*(B-vBvtbu z)^1N^u=WVbqN$!*T;vbGBI2_9o5(Q3ZU+WmF+ zsz$Zm+6YHQUnSj($~tGu6~lig7X92xR=FlXf{T6mP_t)g^oN)Ox2>o(QggA|XiKPp zT9KPgisNk=H40Z@+xOlC&mwx;f09urGCxn^KXN&iQF-q);*W{vtT@;y(uMF2T9wR* z>;rp~DWIcG4+ZCR_`gM0MOaXYDH>b2t3EvncR#lPSkV3onf4bev#B3ECjnhDpk z+f}2<^}7+}oL*k|NKb$UyX3^c2>1m$o|TBg`ms%;DUtQiKcMOs4BE+`>Zb z-&W^swFcm=Rq=~bs(o8;tM;*&vS_z6!&9*H)4VCboFyC7O`_ zG!51 zd`2<_OKu95^EiIE_2X8qc_C;r^Fn%bD|0T1U!r(5(*Z;V$aVl18z9#KJRZPn#?;@$ z!K9nJnBD6lme72m2;wZ}Gsj227I$IL(dtvp>+G=9x{a|zj!r*7Bs!0Xr`R^d&P`(9 zQ0#&v_D#i}tXQoe7X}+5_NjUAOURR>wohj*CE2NWkSW#Nj+omqot0Y4#dOcd&qDkx z#?Kg8tsKYWVXHB2B3G@iUK^M~8A)1z$j0i$0Egaio@0PiCFdI;Rm+nMQ0m|?j^W-RHPLW5mZ@|Aj%9i~0LL_GWc|w=y^~qvq(#RzM8;(A=u(P4pPN7?3aR(2ey{0 zNgHk~C!2)v+@dtkEpl9Tnd7TP)5ceqCo$tk-sKcOF4EC&DZ0|d7Egamv8N}oZ!30{ zi&b6P)+A$?@7XBUXr+%cYY|gU;hSJX&`% z_M_PO#R9d8e4pHkVzkH6T>cT;TNfPdkNB^ny-ocT?cL1(PtyK|zmE1cad$xbqaPvd zMeTTq;vs0=57Fn)JnwCToX2gS<~qO3PJQ5!(cIRA1=ix?6z&#n{!cXGl%^}m73;^b zjBm#i8Q%sZGQJH+WPBUYmaPfS^bEmQxO;|(p|ewHZV+KX-B7Fd!O09st==YznZzcz z+OuS@M!a~pA-18@p`#S_jb48qJt|!$nL1kIU7}=dTs%n>sI4d{Cpf<89!er6k<7!C zh+k=>d5y3ynO&=5D-eeMh8gOaZ#{NT=VLYYLF*kzz*5t>tI0}_EjRFFoPC_qW6`zA z@3tuXltQiVQt#+yrGz80UEwoPDm72dMc*f&J${p1&eXUfyo%kQ3s<8(G0M1bHe5+b znNf8i43}bNw(ZVsE5Ak3X>%kWPy^yP>F#5>ZCg3zuZJ;0M@aZ#W1rERTj##inW}VF zw+i*2)#nR=KDW&&F3<)-y;7W#5{(Q5kf1DbnPy)wm_L(<=ZNoteJy{<&-`b@;S?BEkC0Kly6x_MGe|*js*j)tvEW5Hli2v@Ys0nOWUPkMvG*weT(WO znu%UZsH^G*Mvo18VZIc-PPts~qT2~iE}xgP&C0v)mde=Jix>S;-4V`q&h|;W%++PC z0F7RwdYUM}4HxVxyOgqamZI_2j9jWC!4z_3h(0D1U0^Ml^zV0!s7_y_7eNTTKx{v) z)7WBj%quk3SJ=4;Sz^CuCaxPqyBY{@qyV|hoPbW@-tjm=Jl`H%9r3Qc8m91qNv^fP z)kn8t(fyiyCn;a+hh!TK&%s9Kgh;*hfhy$q5nylBUS@Z@*={(O zXp&#%p?d4?#YukTkUQSOgK^>{`g|GL;u`iCQ3)kS`!h7%=yY#EoVa#QV!gGKE?u(r zc-+6WOD=q>Ri0vjVJp+gH-B7vi<6&7yvw!!;oRS2O%k2d_3#L+EgLa|y0=gfHT}Fv zsoS3HjuLnD7r)xy&gx#-XA+S)pUONyAMq)5rp(u8nE>CT`L(ag6V-?4u z&QNSbqz<`bBc}7(S20drAr$daS*w;cDJDx?Emv?C52$sC7*+ebGO;=hZ;!EWYvJQarjR?2#EM`ClJP+#XLZHq1`g_q(T~As_h#7$8N-`9(mq~m zh!N7%YiCp2HXb5d;|NI{%G7wZL_^AE`)=_bgA=d0c%A`Z=fm8bykY#(*4cV-w-G&F zN0+grjh>*09M3)p4&Iz?eVgaz-)X(tGjRH|X5l#RZOh#<2JHbT;uZH?G@w>E-++Tf zbN*bOlo)oj+TkvNv=K8RI5XqV=Wxg4vXznNen!t=xqMqp0>h<6f2w z1$at}P*xH8$FB_ZT4Zt->t(W9PJzu3tDL=-rovL&T+WKuN0N~S+wJwDre4h`x1ksW z+q*0?I$~K}cMl6q&i3)~0I=G~UnA?W{(7g*8B?Uv>NY*v0XKdRs>aOJJ1N#qDKTJ- zoHeS#fx*<%NoBaQZj9_@n2~nwb|8(gc^=(kKX;Q##bif`xJkzi=(iJDMauA+RZ?&r zmozCRunNXctQbUn>o(M1_jFWdy4`Y(HU;f}W1r;{@kC}2M^gxWscu?#1|0o=VFUWW zVWdO2El=W6Vhr&Yufnd`t*t*W2X0Gfi>Aqlpw>)&v-L|#mg|)cAJp;^(YTswD2q3- z&Dk3Lj?!*C8OLOAV@Q|~lV8ql>NSOvtA;BCW^;cbe+#D#^m(O(a7(jR$OoB4J+t)} z@Ya)^2jz7WA;)+9WnGw53K73-JTN?(hkN@C+_d4ERiR$%ZSE{n)K)uvC`5mxGHH7{ zOUruP1<_aSg@>4J)e6?;FM9>0a>%f*q0LwHd#XuOZCiEB>mnm(LhJ$3wr81X9>;$O zkKD)J$eimZ&e`7ILyms3YaX_zR=FLjv_st@wRw3W{pJh0luRo)(I;u-MzK2m9o}Xgr|FN!qHe> zk#WbX*#sz0$e7KsttU{qKgLkD2nkd^k1;d@zD-1{#rQ;bfV&+H!s^i%Z6>LPhLUil zlWgKgnajLob^tM2FWx%ZsAg{+9jxYV9c9;YYpvY6jiPK4&0iCT>+lb-Ze-4xilIJ~ zBJ;!?W$R)dm!qg#H&Y1PYT=@2s|yvQYh0idU8{iBrt;LcAh`yC;}Ku4{Bi8Iw%#=i zwf+vpYn|XgC%B|T?s~m8IQ4u-?!m2B2rJ&T{g7*rrFk&5G#7Etm7^yw+AcMh(3p(< zRkpPY=9b^6F*Wg4f?>LoJyc^*p7 zHkb^Xn`t(K(3C%7yJO|%QisGLj8MHw(SLA`+ukSoIwSfk@QuvJ_apH6(`A}^x}kj= zHg{`&yBRY|Y`&l-M;jwA z#O<&Bq_UpQp7ggovFfi>e%0MrzfcprX4b3s^1e*kR$*S)lh14P;;zi5;?H2(tQAq( zxzO{fq3ye0LL1Mdjh%kX^pTC*Gu};i>U->mquskiUwN>{d$MqM26-d?cx*Z+>bdAX zUhydNl?1)ivCM`GHF{=8A%YaH z6ed6F?v_0bTi4@7Lk>CAu!s}MQe_Gm>h<%%8zQ|PmtbPn;~sat>5d2H1FI=ij0)hE zhhhDqMCr5J9ny$ug(;lfy#9h=;$^G^m9%R^^;IgGx{W+@5jq>|3rX;&)m5ivPhA6m z9c5tD>K*G}G`r+THjC9o%`x-nj-27PE2V+DC=q;P3bXacXumoPjko{+vRUc}(cww; zyHZzDM1R+?X7x9ZY|}%&H=@Us6a+UCNH1dyBQxi&^^5k%S8KeLr9G71$_T2gHTWT} zm*-DBdSWU@Lyil#=h~H_6zS?Y45GQ_MfGym2n{yZa>0oPy0*0497$Q_-gW?@Rt?C3 z0_lp=G5{k-1M>w1)z!%l(a*uIe&(r3TNdvS4rkyN=}+Fb`Oj+6_O}tdkN-ylTR#Vm zs^=X++QDQ>{Bzc{dT7dKaklo*dc$Lh_QU=(8k%!Rw*Y`z0tKdkU;FTO}{GWRfR3gnU->*IIjc0v%8o7p87ncO;}J+qgDi^v??3Toi4cW{?{ zoC)J}TC*f+jRgR(9J>x!06-%FSOCCa0zlz94%>!L7 zC@V!G(#%`#N|zEoohQTAT22O&lUeVv;MQN4*-;4Ut&gS5jvt@`S7*Joxout?4L@jb zwLT4x*S_b1_X!${v`vqLhjkm(Ajxa%}s+{@nvt*rb(|avu!X8Js!0PX0Zky>a z02a(7=Q0~%#ji_~!L$H?WeLCn0Ju8PF@yyizwSyu&ZZxH`h?WfyD3M@H0WO~DDS51 z%wWv>p2^&D`!NJpY_FVdnma!ynTO3G@DM8AImXLOI$Z6-GnCf-X_5F^wKB<_Yt>j= z^=XkOERda7kg>HDmAxn8np<;Xmp6Pqe7t4n0!UTGS&VF^(IQ25jw4k?#^T5lMJ}*1 zMwjH(hdfB#yBANP{rYP)$ckNX1DT@&VHpX}B$6=cjm+$*6epaON|4u9REZN-QgPH5 zM_3AT(c(BVsz|>g8f4ziazkhV06QfB3ji2S02Z)0wioR@*JwRGLaG^KbvS@Ae}$uX z)CcMKzy_&Mx5o zok;s{41Bnb+7$VBMGlA~UsvQnMTC6kq^Jb|j3xjJ02oUE765>S+Es4>0AT{K004}` zE{6rq;--B7T!UlJa1@b%+Aw}S6C2ws6WgzEF=2PCHL-4o5i=?i%gjp|rJi`s|4wXj zXxx`SC~{aF`I90?#E~BL^2j*ykW!9TB#8b(Sod7(nKXsfwF;RG)Sz`q-MV|=HtIgl z&4DH@B5QW)MZh?RR@@Jd123Bv=0$9~lmf5D^AgYrq_t4~OW_!3H%$UHeRzVvZ0=;G zqNk$|D<#~8JYlFGM~DGy$Br(N*~VU@Ztd{yhgNi&zj5!xtDS$p-l)6eE^=3g zwtm}-h~YLf9v=Sq$Q5=BC()A4YSeh0S3(0Z#u~-Mbrt}yTLQ2E0LIScumAwgDICB8 z>-_L^(DDHZeoq(5J(F|`Tp3G{rIH}Cxy>R53TMeqiIsh?B)bIw?41BC0AQa4U;zO8 zCIAZnVC&$TWB~wE3BUpXP^w)H3jjbVbN~wg*gpYSfbXGfq^Y{VsX0#MY3&GE4@k0F z0KlUXfCaREY27>@e&Txvq@~tP7+mY7psbsW=KOVYZaG^ww^#1P?RAss^%;q|D`~3c znnj+jh|RBz_jzz!5YMk6O&qtT2GI&dPPJk|G_1(_IMP()GK+M5)hk|lCrR#FdeJ%E z(%XDg5Gge|*~6OL^xciKE+KMXv5F%{v2u8=PqE~=;z6`BDLn5oRkw^Qy|avKPuR7P zuU7F5aW6&{IWvyzqR2T+2#L(y6nR3NvX3I?#*zILIWLZ^Rpf#=vWFs1i6eU{a%mjd zUy&!rkw+PKU7g8{>={QcQDpBp@+3tLh$9y& za$p=eSCx*(k@JWo>J`gQGdfZUrCcP!yLlZg7U^lG=($+%wI8y$_pOD%q=F|+gXap~mK#8_+) zfir|!GmubH`yfd?=pf&vTX`OByk}td4tI8H3t+x=a|k(IewG}TPp+s`Vb+4JLb!sN zAHBnQDa=@cC6gE3?KsQ^^D^IInYk~N;wi7KA@kabP-eBx+#=lU%(?xVPFf>O5jwct z?m#+k(r$s1ch67Y3p7OAtyKh!uMkGI_ND0cRDtlAv4kWqt)*x4^1e=a_31RV){SP> z?jlSW6pXp?pMj3|&B=msEqWyk7jB6oa@7i7;3DDY?HPW?J?_?H{0Kz;Jq8$X`Pe`C z_MO%zJ9%(3iMk8?LRMX;!SLG1zW}T>m-B5Mdgt0wArF>{QiuZTw0j$0-;+6w!m?R> zp&J*SuK3|c0rpP4S;HNwGT9B|%1kb|vpkP1SZQK&0nC%{&*=_!&gEu%2&&K%OdDU^ z-_tQF&=0j=ZHs}YAH1Oxs^!5Mm!7O(8e4*LH&iV&7jd1F!+(Z-2d7rRiY6?VnovJxfi_Qr>#c%#n_@i#(&%_cc&8kE|%aOTGoG;B8AcU8}@k1hMd3Q-vftu zOe6jj>w9!Vci-WQ#r=UBh$5~4&m^ZGy_`(q4b~}pe@(VsZuM$sYo3+s&BGIU%h5HI zy#O&&m7d}TQrGQ@SrVS<$~!=!m?|Fo4X%juy~HQyy+%o-oo1dNy^yGrcJRZYZGIS| z#Vo1tya5y0u!L!^Ox*;RKzy_nwf*!{DI3c}L0wrL7raGPH`lw&ZTX@+$d@;h*uwHw ziXNlg5*1m3A$HOb`7$r>5ti3s9@p)G9Wf%wu@RqXzJzLBzJO37C=9|~5U;ir+N<&0 zljkLlmtO@O9{{#Nv8>SfOekn{ypiqo3QAC<&S&00dG>Ik8C}Rs;6xSU!L2wgDy}7? z%VVybIzX5HMpY(X_ztTveM)GVP$1nzP4h56QvK!RHBQe1gEr}je0cC6zQMI!fssGM z34lvo`$i`#V{MVdE4nAstMNmV;qt@JQk7I_qY(zU+ z#mhlmtC?gImV@|@YTl-5j7`05qDne9Cm$M@M82Vr$%Vq^;5&F;=ID4U7@$SD7-6T- z9v-U%zYwo5fNavDlp@>&#wBsDt-{HIt$T&j3wCq$0-1D^a5@v8z>-^|u`ajbWtuCw z6_2)P+j6vPi7~CEW%IFjIHKf8s<%D41ppkC04xCDm;_(}0LLZ(3jjDS0a)M&tWGqA zj@HAwXo9n`o`+xy=L+eJ6}U`uI+ z(af+k=BBO0vzY>;OJtfMp%Prwo8(wXSnXEA5aR;*4A0w@+lb_`2TQE0o8l z}>__c`tPKHu-Adhm^=YO&0oixHq^3tyNzjnbst+-e8h1 z@jVhg-qZ!r7miA-|6iu4?%^&)Bm9hcS_QwN(ecL5bq@6Lr=eJNOCdJl(o@FIb)eY4 zesw2dnfvrpU;QG!o}lrROpWy!O)0mw`+V3atzXVr>r!RhM3O$>%5;Q(SDC=uOg(IZ zt2)+|hBh(8w>YU#3)nm8jc##`_XbWW@S7)fQlt?hHs!!;8i9|=9Yg~21q+J9Ov1(m zmu(px?Tg$E-@x~>?9*(~oj7y5GZ^$t?`d<|qK+hOsc>xGX^YKVteI-1QsTDE&6t9 z2XP~oU0PD{qc2g`-7PPby@7WE(lb{oM$Gjb>nhrIj;qBC8&)AkD~_~pScyhEKukrQ zNIu$RlHbY}bg&|emXkrx<@pTPP7S)rogUZ2e6tkp#>U?ImeLcYW%eg|dYc?iy`8pC zLQe5ZvN~&nZQ@#&nq($MVtpsgRC~fyZr9k`%cKuY7;%xB-?|d zdwZNbkym4xG=Dt^)coDme2!A=J6&idrO7gm8#aE0<{(WP2X^IC;_*2BVAmQ<38}7B zLvhx^SU$gEaRoY+Dqg97qJKJICnprq(8MwZ}W@WaoNu zsXu)x^_T`VS#S|_^ZXcH{P;p?$eHyNh-qO4Gn2Yj;@l*$IOb2EMz&_wmaXwClZ4FJ z0I+3D7k4VB(D>18r)*^#$cEpt{xPmO-BImbt#J)9j2XkPl`z*VXF5w^CS&hlWkOv@ zCXDXpt^|wtJE!}nVm$7JEo*eph0?ulVQmPK*SZ!)xz+E>ge$0xkqc%-JD;1#v>4B* z!(0_F@{B(x-CCrpBqiHMUM^|hD^wZc^iZ?Qu8?xyCFPuV z?HfSC`Ql?p(Y{qLa7+@{D~WhkW+J@^j5jG;9|EZzYWPN0s4}+oQ;vdvF%}#vx8*j~1M? z#!uJ;^(b#`epCgxRD-v$n(!Cx;H|%Eg}ITnJM%Y*wm2KotsuIS-*B1*z0}_`JT_c( z_VOM_vDot6V;`f8?bbbh+S=U{?lcMlXG-r$Y~VfNk5W)arNCYv%|t(j&hb@R&Klp1 z-DQrg@jc;>7r^s2njHNU`o$QniL~`$aeaX8_yShP&!C1MePYiSIEl zC5TxtT0R**-99b%9WKSR4AB--EqbXao^<`<&OyZg(Y!rq+o$;pp*jAZ@NfJQ?Ngzq zyh(KOA1g#tmyoGH7~VJG<{*4|XGnuwgi$mZfgsJ=L7JJ#hoL7q{OQie`<`c#mWwbq zF2 z9#R^o2_{A*!kvht_4!Y5On?`sBbRpoOn zHO4{8spQ;wumDzVw}xol{A<>b_tV(_%o=iP_ZspTa7kvdE!J+umIJId<^=%d8;#y zeeU`R^FA-Jo3o{8%%lOlHxMf`H_wib{+w1qT@WR!WNe`DvelBk7Y->$%M>2K! z^8d${n*BSIvuDmmLw-GOBRLsgK-}!_8EPckhSW@EZ!k|Mwl}72Gs1XD=jq!KU51B} zHTb{VQnLdBJ`9Co;)pi*!an@IK?GzC8H%08{)em~+g%+Vkz#MW6?mH<&2tuB8`yZ| zbCs#H$kARDdW&mcNl5^0=}_Z?-0k&YX|g^nj@O53X3he!$fW0D2?|+5Y=Nju{lRdy z7l@_F08vM=5!`vXJo#ed&Dm-*yRG8d#$+lQCL>(gT6X>D21HDG{e35 znJshRjLVU&G7Yy&@zxZ1XH-kaz!%+45+{Cp^JuLlJ;I`F?+xX>_t6HFiTExo z+^K`%o8#Ty8hoc_mu#@(1HN=7^ID4+3QA#UHIegmIzU)ThC;Y&0=pwAt((gm;dl&p zHi6;yC8_1M9~S5DW=V6$Mor5f4)TztQO6~9&PInn8uB$dR@L0_=fX*aq?oZ=%i4Kk zMs{uQ`Ra$!vSjbrsPx7gAQrCz_Y!;&LGHp@)Lh2rB>B4WD)5w=jJIFRM5`?#6K%9J zLH4%hOd+*x5l(%pnXJ2}Au*HcUR~+G?mRFU|+JTRZa)+2QFz4VVlS&GU zsYr~xow1ZkGmD99{sUQ>ug2|3xNR4;%zC%M^^Vt~)4elI*g2@_ ztjXtgn>+G7i_}Tqo9cP%_gq+KG3WT6_fIzO_M)d~nq`O@3nGl%?K8F+dS7?;qrJ(< zaOujnIHO)T9r=$SF)+m$R_v?{Qm^zS@Z89~`8gRrv?{^iW?uXyh>^R>T^Zx4x>s*t zGspC8Hay7B<+<=InPeiFen;Md<-ebm=3Mt-t1`%zVE zU-~|}St;RuTESSK+y{*K5}@C#p`lRp#Ybql3$zX<8hu{JZOK`ivv=pb z*U%4)BGncjE`WbBGtXi;ycX}Dl}5uVO{}*tukeBl0ItsL2hvDESWZ!Anw8wN%1tbG zPdnAbqyf30CSO*W&GAiy)Y#E|36nxT+d9w2PO*8NAMN9dc4~Ayh~oFRForMC7&0r| zU)o^qAP*i?X(X33J$O(p!`GV`@p^MA;d5@-D9$c9hxMx}$1|KJa;<~upk331hxj3c ztMWa|hd62TDgooO_HH51P9|0bId(LQaVw~$6dM@y&75j!LlV`uaEO*W1*cRnF-OSf zs8Z-$Ys&prTo%He9{m+_DZ(MW&-rpx`c6wnp<2dM|{Q``{az~w{xc3i0~_iP1q}$ z15Rq(o&oW=5T!kC<`>j<>#3M_$BdR&Vs0SQdLdjrYO(FNq7b(t_5-3-?qoS>MX{?9 z*K>n{T>9!4Wm>Nj&y6hJg;zDA*APw^>}dZwQ_k~Pfqdig@w0r}Fa#Spq;bJ$GZaTB z%z95_g?^FxGW;kV);P@M3QpJM(@(3I`ZqR|aGurIL?AnW|d= zsMXUP2Wlw6nUs(c)buF2HH5?@edrf*Cf#>z>aA zH;_G12lQ3H=Ze&wgXVl7Ej?es?m~OTV3piOW#no;o>5jwRlc4T-Ee&)!M_- zYo8I)*RpNA2HFW-<5itMUTjSEr%O~64@#PnOVcD9Xw!UI8eB`j)g?*D^!qu3GL$aU zLA5*$-X1E)J=HF*k4J?IeYzGk5^=?i?aJrr3h z&B1VmJr{>hUafe+zPpBrLnRC4{>+M{&Irm3O}$!)2ULeuGsy@RvljqI~^2^<^KK|eYbzIM%9 znUpU#NBjuibfm;N_IsxNnw!90kj6GUqTv`gUm~2Q?4Z-{%loUPoHV*ZE|WFAest0w z^R2{*&JYqrTfxEW$V~h(?hDwL*k${1!(&rBU4Ln3|>N7=FA>{{ZIENz*eAp|aL< z(IG}R3mVQyop5LGf$o6wUTX9THv=E@qg#3S(e3<(*YY#5gqh59<7coiBv*krcjFAV zstx9jb=HQA6mn$Ts`&Z5@}@aap?BoV1Hrz8J!)8-Pv>}9%Zrza;snBQI<6BDmR8*^Y;%r@9?2<(c>n+W(Gs`6w5tRje2o5{H2*(;G}a;+F;kA3ZRfJnv_PgDHUEG0N9#T(`Rjeo+}S+JyLW@u6Sw>0 zEZmN_FL{J#;r3<<2j}8IEDiJ4fxujVyH3g7!Fk@^TX(b#EqF_x@Ucw-0r%aPxeon55z-M{^e>Zb(>-KlERcKsbZsoi<|j(y%1CFkvK`Tu{@mHNNLFB{YnVZS6^?dDL z?en$-fiw`d#nRvC^HyIFi*3JM`~GI1x5h%4|2m(y!39w~GM~4F{lWhRpEs?F599Nu z;rv^C-fSgIjLhZ$k?Zn#Q`KFoIg7MR9QeE~g*5Tfy^Ob{5|6gT?~SWs%p<}4*K_vH z^2@u{^9JYs@s1G{giYl;>BEKPT=*I4#M@h6)*1~jGK2pz)s@<|HKJp+@rt)!9p6LN z*d5C1{}yYQ=n(d-MAiI-Gz4cI{ZDVGVmZv|(*K&Z?7xbTTF*GfdieEBMtl74Rwf$4 z{}2bfg|zxYX{+wrnhrS>0bLDnMa?-gJWRtS|?xZwNX4OBs!A4 zayKh%$MINrUJafbg(o(9nIBn~2eQPIlg4%hHanue`2K6z9m~zOBy)AkfTg40BVrSq zR$F-$3?)s|R?6Is^Uju5YA)+al2p#0AE)VD2V0ZAljZLGyot8@)6d3cnY#1y0_@w| z`6(!OejdygW8PiabIa|=w)cwdmBVlCIp8uEI12#a7{mcA0N{BEzybiCp8zZX;2#r! z1pxe00k@zkbdSXOj0avUd|Tq|9EMh|v~kx>awSOe+CA^KYZ){DF>I|1W)Km( z-rb)pw+EObQO}M<<)61DX@Lc_thr0VlRM}6@a)C_yY2WFZRgjzmeJ$R#u*;oXb|Ik z@b10%vgY~ZYhtarMrqM$$$gF)$zSgk#;aPua;*alhqXJ}%@DaOyQ4kg$dkYn?G;B_ zitH0dM3iX1IHC%pwQ23et2B;~x~qJee2H!Q>$K$@YfBJ4mb(^raPRq< zBzIlDy5oD#Hz@n|?ma(VRXthl;Hcn4MJ|dXCo6J69642yN*o~?+D1PIQx zGW3!+{>AjkeWk}qNsv!R#(ORO)L+6L&b>uA*?vrx&8F7t`?_aHH6hsv; zhgXB-Yy)a41yNr&P}3=h7IyVZB1(_3qj%X;&=zHlto{ zwo`=4rN=2=-rc?NSlXb!AUcg#4&xhFAB~`#SQ|uV1G3Y>rE72}ZpW7G#VM7=`aydg zCM}?H51$3Z8^5ZJBba%S%4cR?tVed{B|?Eze5sytfVKdDmn8rT z&=<`y%%k2bO;*QE3TE)W6}lVs$j-c+N4A_V!}sr%=F~&m>dCl^dmUa|xGVaUn*j;#>CkcnaX@04KkVJ1!qAyqHR3cU&6a z?y@`BTXn9x2SsyliuSzP&AnZ;KE0S0ARzhDVJ@fAvJWn}U2hgfp=M&z1mz&J;9UY~v z*BO5Y;d_jvL1W8HnD$)FmI?0!a$ERLemi#wXR`g@@7M zkm=jZMIRt-(}(D^Ir};qenc5@Ehvp{R!TUc>__P5G5nPMAaDKd*lP0L-l=|P!}qz4 zFWp|pM+dX1&a3a*^To8zWVwy#{SL#j9bp(<+Bc7e8ngPtjM=-uJ$KCFGLK-)I;GWa z^*bs12>abeko3D<{;%qH8x8Zq@haB~=}w95^6I-6=yzP^5%jxLI_Y;(_7V2GjUefF zyZm3(?=~9N+rZY_HzsrDNVpSY!BIS%!0a{Ot?(K=i7L%wS%5Bt`+J*QNOTbabGy8l zK(dcdXWzD>=+>z(Z7UZi^2KF%CcHf!@762Hrw6j33a#%A+R&r?&SwtosvMq1 zb*&#D01^f!0u#z?g8-i#fv3sR+xcC09)adLR3`9>OV7drSa!)6` z`58*VN$y5)VtjXd092l6# zn!)B&@`wLo)lI>+N);@^_Eef-qpS-d^@whQK+eg*T{oEmn?b7NU;R(Y$L_c^F$NmN zOqVi6Y6de<$*e-9>!d`se99KSp!xI@U75gZMM zL2TciP}BI3hW3B1^~U4A2T^tJcRZfMvwLyTyST;pX0I}zelj*HHEZ=2`$0IypJqc? z(ymZpc7>%G3=aykAuP>kwDpya@CE|XU_UP|mkMqH0QV*U3tZpc$pz893{dz5ex#Ov zQIRhxLQ_rFt>O80+UmrTzM{@WlIw1>0qP{j0qPn-T9OGvr^{Z&Jnyy3@_%zmhPuUj;S{tutw6CDg{t$7J| zRq@GA26g1RqvuqStaHBiS^Dsf?T)co^4p_qbPYPq>x{BFXZP~m$5&~XjqXACsiYB> z&wWo*m&x?{1((mpB?#NSv3%}cT)qC+>leeDgwKX82-}^qd{R_&_8{k{J}%%7Z&X${ zuyDtH33pfkz`rE`3jp|P0=}!ChQQ1ld05Qqsr^*dsj5?_9#de+ zH-!XQH2E~VQQritf|d3=AZ3C12wtD5DmU7mR8;TLD0L*^W2f)RSi#q@!T-Wy@qC5L z-UrFV?{WEhBqWg)>z6Aer)x-qvzIndX$wl1A^U4vm9q}O%~~}XFeJlf26f!2wW9si zKd8c|8MlXv?%lYx>-~T{mjus4@>~`?%_40#`8)EMYuSIQe7>HAY1XrQZwTCEi|rq}1@`qUOtYTd$UAAc6G657wuZUt8+yjp6Ywj>?D)3U~knJjy!fvA6w5JQy-!HXG3m$l&&F~+UP zwjA$gE#6{2-n(+~78LJ?e7u75W}M<3E~MjXdjHb%-MDP_GzT5+o(M1pfO{jr9O^qS z^UVtPjc68AuTf=9-VdcU)_piO6}M9QS1SGW!f3{?W!&kgWu0IYDrQ{^iK`$y=k?k- zDg|t;Fkz2Xur49Oc)tw!SJb`-yF$T*m50HC`zU~1G&u(AsuJ(LLG6DAzD$OaqI=x*` zsP>Q=cOk0Xq;?axJAHEGcm_wfGbEdLB#WuD>s4okW+mD9Z88MX!6`}oYMQ}b=lw7a zca40VwPjS;wQ}-~p=Nept`2uEZaRClt6bUz_ZZ?0Axy=1i8?XA)$+I9r3kU4BroIdOPV!OK*V zMjsJ@q)~I67@rVcL|`89R;q_{L?p^TL`ri2cr*gc0pPI+Fb9A?Mu0g0JRSk&0PutW zZ=`Qc$i)K)3rNz>51saYZ?+gf!m-$97_$vN{-+qDIf(Jk5nv7ge~AEd0QhSJm;=Dy zBETE~{vHA50Pv3pFb9BtMu0g0{3`;?0br8=Ueuj|0O*PUa{#CaV67|{t$a|fmBnP)A+eR)F}{_%V~pk? z#-0c;2Y~4jU=9{`Sk~xppQIjDRWl>j900b80CNDSMu0g0^hSU=0Pq;SuWoYy=#Kz% z0GJg4<^V7d0p2Y>|;U=9EaBfuO07Da$LOvYoIU-$pu>r6T%{!ozfXK9?6r}g~#uTOFcN8^h& z5c$*QB%CR8rk$`Z`nT~h>PNE#e|?fE^2H&h^R=`L`H-8tgxRNP{n_$V+I9#1k4!P5VYE)!r^>J4L=X`ZBwKE(~PH)3Qk(aDoAbdBqz7#6H7~ ziFh#Mv@8$@zzZU={E>LIh-YUfHHZV?yJq{R@o}i?6>Qw2qhqLtl zO*oysQ2EK1ZHyRSsRReQQ4-U$y#M7SxY53_y~X2qV&%J{*|Yb{tZfhibPe&W}>-F`7D=@ zV-QoOf1pfnIZwx(>hm;Xe!sfH?$m|8d*~}s)f@oUMu0g0>=^;(0I*jCm;=Dx5nv7g z`$T{_0PGt9<}kj4&y%nEdf5-G*`;F8$M#-}P)eI|qO>b|4kHY9v4_s!oQO?!O5V}m zUE3z8cL~a~Ma0HyXl1H%Y1t-=pZbwNmUgBMrkREV?>@?p7Y&bwjo?8?X<%Z1qT+La z{~V}KwmsR(8LG;Kr|50uJqucz?Yx5u9=0CB`e@dJGMQ#4m+WaqW^Pkb?e`%Zl ztTzAIt^Ns3`%Qh=2~E3AZ}#d|>g=^`{@1nn|GUlq<~ILDZT`2m`QP5=e`lNj-EIC$ z+WeQ9f8ez2Wzq2pbENeTb~2BjXP&|A^Wpo2@cmNwe#O26**EOlWP)!6__M?V?{y1%8({|Ef0sYV+5>K);&MI)DLBeEC*Fe~o_3PR{~woY5r)yjFtq7*XCn1WQ#@5!fdD9zDzb_#E!fV^LH#z| zYHh<^)>qCaS1tE;`BY@Cb1P0}#jhCj{1PdXvzA>scsE3vsDPmydSYa?ZC(3fj9^kl z6OfzMERN`Vd)#X&IY+c+G^9JN_GAaQ7RjPDb`9yIgwxZ}T9s2Yg_TVa&2cr7@QC^V zn)L3oFm&I{v^rDU?S0jm?9mrt<@%Q?NOl?`Qg(n6(|jaZBZc3;VRI^*dqVh3lSrkw z`Z6^N7aVCVl$5PB^1A z*5hbM#yv?f*dA7W72H?IyUHx{L1j2#dn*A0#)rj6LzeA=`<6UwgXda#Rs_#SZ*;!JLxI^+LL)KQ24hcJ{}HX8EmMq?P_%A0O5fL28^j9M z?sBs3RTwr=e+q2^cn`p%x1-bAwP|2F@?Y53)L8dDVwlik0{8dj)=B~Qr*LN<2pRv3 zJf9Dq&&lJ5_JaGoJYNc!FUaEu`GWhBJYNf#ugT+weN5QOulrp$DBAz$ zB2lOxDb#d@y2?Tg3;1IJs{&qQz*z$RM8H`Be#C%D0d4Bp%Zr6MwyHPQI)!enwadst z|8-v@al-yoVdoGwJDvVx#gnn35)aiW>IT|UTwg(n!VP5Z8iH*15$}vv`X$Ctj&pa*hnPS;mBddaVfm)G zyE62AM{$I^0-a-6KBR$}vUG%g&Vk-`_z^hw!>NW}vko4>zmNP>&zcu`|Gx55kToy& z{{7{LrPr|J5dGfr!@_IU;UD`B-Iljg{4P?LyN7Dy#Z?+5`zN*Q{-ACNR{M=z?g7JU z<>RrB37dAgNBH`6QI~sEZjt!NY$J+(Wv_IhGGHlWwgl;N_d(krkl8K~7|_-vKEVco z>~M)dx$ujeF85GA2F;ryFrZ)cbh+C_@qFWy?0PD2)t~*s&`A7Ebf^1}q5G?y0bMtAm6=Fe>wY==%Dr{UiG(lh303b`pFj4o-}u zD?Szen}!#rC#mg`Odeu1W-(93;6ivoyCre(A3$UTFv|{XIf1ALLaQ`Kua{dzMz1_Zj9j4UFFz5au<8xuAi0 z9WEc+1%`Qj1LJoK6z27Yc|!y9MhgSt4InbVi-7M<@?8{h7s}^%5Wu}fK1LJL!uK}$ z-Wh%Gkni2m_b&M^iN1^FyDa#!U(3O+F|P{y$+@t!hN$5x8kdHTPRF%%YyybhCw4Oo z46&b~195MJ`uR9O2O?CWQqgS9 zup^SB9>$Gs*_#F~4;cW&x@XaH}>0Lhg zV0_lfkS1&S-l9DC(|$8ePIJ!)-hSnqi~PQJxPQ>y*j1|)Stm4FT|BdhxY)Up-hox- zX5=q{JXE0Q*F*n9qqnw_mW@~~V`2{*05lB}s60-sX%y^iclL9%xIaMCWmrdvmLgnq zni_*jj!Hi%+0q3cv~y9t8HBFNqevYZR+&jgZ&55>7cWijBrmLdTj~LJspjgTd+h5F zc}XUU3}mnu(LKWMx$9L(pZDfgvx+MRXf77uq@Q| z7K1_gVr{;escQpPtR+l&!(KArh^UfpDm`H^i>9>7$KbC{lH$euZEXZYVSIr6SRWlH zd-|(Cn_5H_5nZyPJ3wT3w7zhh+nPr~+%tk_kvw}4L}YX2*)w>aE6?7+^Gtd64W0w# z**|#J$aA1P!#k&ib3Ulbeh}F3#|vBsux#)oJ(9GV*V+d_TqKf zeY6SBGibbN)R4`lOEH8X=rMk<^#d#>LzhtB*>}bAV`{#Zpl_w7ti!E|fkT|hp2q~& zl0A=Zi*TqJ;RWpxu2+Owl#jJVxK%#F)3O`MPnZM9Vl&3t&TZiqJiTze#lc|9ycqc| zNmiEZ(i}>me_TKvyo{RTMX7>S;c3~6wTR(?sii6on^M9>W9LP0$=pAnMH$5x`}6&o z+JW=$k!|RZJFBy4y34NCbXX{L8Z-CQ79s+z1bK9tjkVB30uv4t=WOD<#@cqk+TSeY zcX(^`k`oT{V^P5#C3ovdOY&4_av9UG{(Qf$|A3u6Mw;a$77srW4d&={t;IZNMFTwL(Vg438@0aJ(!Lyq@{|KJz<@s#z+#t{O!Sh9VZU`Q&UfdUh zM`NY?a`1dzp05YbH|6+{u)(F1ehJ;t ziFEos`q$8UVRWCUmazv|b6ug*B}GI&v2lJ{Kv^^Y0J!)1&=MG{yTWyq|CiBcrKLZ z&B0>}-IoTBt!>W=9$TTkE_fysW-@pt3g+jlpD;taoRpEspEE`enM$XXPu%Y}CaG+A-LZTrC7TL*P0Oczg*(om_AX_baCnvc zN_j*Pk{VT(Fq||}Fduaw-?oKx6Ax34bjzG5mdzD0lZ|SzQzqY}FQE|OSvf)aB8z=*( zZC~iXUYYLMpc7aeetyAeG)n!6xQEqGv=;NILE~k&j?SyIQ#|eslTm|OYOdqd3oK(lo-cmSw@1w&S z^f$&Y1vgt&eheHI4vynFv7LGs+)~gj_Sm#Rah%9&yL)UrK8LqzY5lo0CYwA878X;% zV~eT!O^i`KcsOH^$HiO(XLY+osWr^G8HZNb2s+l%TkG*LJsW|VDJp0qMf3O+ua!t) z5wNQ>7ouz_S=0gU*79V*v#mVC!LvY~k>FV*Pc#oVEKf8Km&vm{kRe0z%4zWID9>2% zESCo?F@<@a!VbPQjyUblV4y zYRRn#9#w){B~P3aCn+nVaJ4s?zx7=IUQ&|Xzl3V^iv}7=rIv;#>xm>geL)_jtm^Wc zp&n=nr6Ofr0rg-DN|h4UD~T;dG=iNNhXHe#e6>A!n{WLxJD4C`AzwB`NJKu?_G^Jm zeTVpR_@*O!CU|X7HG-kP6DQrWe7NSG z#_^rEKCT2RX>A+ ze^H=2RbNf~<^XY81egQB=@DQK056UJa{zcr1egQB84+L(08W5mETmGd*_P4lD4g)N z-dZeAOOo4sKRq7k@3P(sdF{Ff^hDB45FuYMA`iW z$MljWxw*5e9rYu@Ymc+bokMut+-3`-Gy9P`3rrwSmwS00bpVE6BFe3UWNF!7!~D?3 z?(#^f94}#JM=Mk>1W`WU%aZNwoIi7rOZ!ThlzyB`iFqKO(ob?IpHj&T6oxm+gWgec z!h1ODF}^M6T~w3b)9f^2vKCbncDTFe*%XM7Dy<9i7ToztZJh`kpb3GFv9PtR1-^}d z4VsPecr2*fOZBXvl9f-&6${=7?+O*tq&hTeN~gXX|%F!u{&W5 zqQ0Yb$_98rQgGp%kE4*s=>RrwP%S9d*OO*9kK+~UFEW?O$|WwGzXb1kaf<}uSKwhT zE+tqWNH!W@xm{U<^M)ITJF_b`xZ2(q58$q0G8;>cgY&1-^EHy=-A304SwrR+EhCwK^#x@~eq^mvGlHzL6sk?X1O)Qtl2 zzxA~w8dE;ekc&au(>$q@GtljqqtlbSsapoJucNmk_ndblnUjoSdke1L`p&uFdb&Gx zD-@g_v}u3^QECj@D9Y~OVtGyaqA;geC}A38iees%SV>%qO4$pIViiEUcbz&p>q{Cb zr*0HS^SoB*Z9Zh;d~uk9z@@7^`Rfj-jgM_pi80zh|gqFQ9vH z{1t=LcWcB^TkKy;k7&gZ5^53=^FARZsRa>4Z=`!*{B4VuWjG-wA7)6)env+&HCtLz zucmukXi1T^w4{1m-&WPFW-qT)w?e6qv64AbBc@(orE(ZfiDC${P&smjJU4ln-4(zM zR+A17tfnW|Jti`AUoTq>z05I%fr0wH)Qg;h!MQR!QvtHNMh4)sn+nN#S0u?~z<)glZHm=t;ixGPaSJ z@ey4$IMQP~+!!UBFzUUVkx#rr!BsZ)>yzK9Y&IIF<3czM2Kkpji~f00U#bR0cc4}) z`=?gf+aS?IdmCHvuRgJY|F!%d!hdaDDf_9Wy}!UpJ+046De%bU5pflCebw?JVQQlAR)}cIIA&)f}sP zrL}q4uZflz8jjxVHy+ZJ{nk4ym1+k!S1roc-Vq;f96WqE)-*tbc%pNR*e=a+bQiNTgYvD9 z!VnKbIW*b}n_3|R^9O5YedY-IN-&)~y|+AXYrm?C5ol5gOSZBVGc5EXUi=e;@>YC2 zJ!*x9ENC7JtG!ly*OYNJQ+NfugU&fRn(7(nh8}XBzI#jT6@-ptfa}uiwR8>iYV`4k z!|3~6;9>eSPlqLoNHO~k_@{8r4KVh|R)wqd)sD^Uq4kHzqFVypfcOl_T`{{~(3A@< zg!?$-QnUs*!S+;_&ctkof)B5ZE=leuV$Dsx@0Wy3$Ip+>qtKP@Fk864SNvvvxx!Fn zw7-J3yOwbk^Vhg~xK5D$1_vKZ?^N7#VX=Ywwlc^30F{(LhZU#F=#RWf=?^s3@4Z$K z@+BQNr7P_83~yIj_aWXy+3vKmMT5_$1%uB?$$d`m=k85J#)lf)wF#Qm4)8fFxlcjB zeT5CAYx_%;uChCve4+B4)Q`ZyF1Gon=R7zbreu+@TtVq2j|(<>^4S@f{lB@aC3;&BH%!Go#ORDJ7Ur&W>?Z? z=$&5-tJJT;?e4Vn=J6gmwm6cztxd8G#GP%_&+qsd=E!y>xZcx@OBjo5@>Q)g?`_fuV>IgKHSFS`hEp2oE06tqQu`Le#Qq&`obyuw8i zE+8rOfz$fk!+e)kak@uv@tAOL&*X%qW2&w{hV$xJgrQ7=W-p&h9lTr&QBp!kT2kPW zD3WeGuG+0_B;90?!~*1yYWG0>zr>%E_c3B|hpLgdpya*P>F!_vR;hh6Jo{B=0L7pT zn(G)Dvd53veBxSjQY~Xth;77b>7rBDF2i~x^?4YmHCOQCv|96YS?! ztNEAGSv&7_lKN|s{DJ?E@P8RCHJz{i1V5So6M@~xza}~T39fOC8)OM{PXjI--UL=p zV9dr_>&xqx(pu}qD4aLA$0;8tMA50C3lpfSq3j9$WJA=-K9o(P@v9X`@{KtFye0z7 zVe-{!K3(iRu=1nNLw$kT%WfuYqSTp8hGRz*py~9ut2oE7lPVI&if6I@eKMF`&JXu` zgYN_8t1HTh*AX(U|B2j9yjJOJ&mpyU55DaCtO#1~w7c)Hvp_Lf$<0i&_Yqma{XkLH zup;vQrZ*RD+jWJ;J@&m0_EoGjpF>%{y@fPmGFNL)=!=s2#|X+!xv%q&<8im>=Q@70gYGN8 z3IC|zeH@d05{Ex4rHfvRhtEg&WFHnqNDdgbmB>Z4qpxI}I;CKGpo*!NP>0(l@BXYW z-*Zwc3Fj}LP$bz^{6Yrbb|lyFz1PZp(W&W0r)IM$9ZWR#-@ws=zO1KEx@>yc-9?Z- zZgtqVv)a*ED(=!-?QD29p>C+vkriOyr0m1_Umy0UsBpAy&!cB&f1?oYUgfDP#IbJA zvbzVsIuA}TnsQWksmQUUF0q`>m_x=T^B<-B@fUiOhV-U{>Tdyk1cnPu;J!zk?(Za* zoy!2}J`5^0Lp$zT;neM4Ps>`jHwS?KjsSB2ctZr30{}PkeGqf7vq8?&ZQ*)t-RbFQ@Ue@^{*b+S)7X!#QQj?v;qoZkzSBb(+QEWRS84KW!l6(G zZ=ds3YRs&=iJ-)0ZBK11&BTW&mnx6n=Ert%r3IUb8_~1j*A~A1Y?v?|gF$u8;%8b5 zdwRyuk{W$bnL@;(SLFm%=bK{Pm;=C@BfuO0E{p(k0Jtau%mLsn5nv7gZ;b$RkPnqN z`*-yu{`|jKx8EhL`+ePht+j4T13hEvU1|1tt3~}Ls9SXH3&AwnL{d$ykt%RaMzhNl-dXx?Yg=#dY>W?`+d4Mwc zoTr&`cS4#e8){KZvQ_XZ>28E^Yha&fPj9+ufEr2tLcu53pb-E|ja*!sW7+|YVKYx5>AX(E5z zL<&0bE`HKF5@;p62^4lXF#cAVc(;LVzI{7RZ^L|7@)qVh0bAx+%yCe7JEH>w8r0{c&(wchCQ8JoyQc|K`Y)LX#hkQRGa38xg zM!+mlu&gjk6kBJB;;^3sw@nT_rI{bJ9FrhRY*tPL2gb$Gfr7hP#52w_N{)$@fqjwhFim$EM=X z$wR$JGDGWynS5_6`c|P!h889}PqK773$$g5^kcqW@DiUd@$L2qp%2l1_1lOOgX9w* zB0|)uuH+MnZF5d{aaA!rc@UgDV9CbTDma1uOkqt zkN1^qCkUhX+CC`FUOrUYfAW2V%GJp)psROTp@rcc$NeWSZzcI1V z5@GZjq>^UjnYapnKI3df+Xgq25UYlDgF7EpJ2EMcE-Oubpf%24H{%p08>cyHz0CN< z4H087U4{j?QNV5rlEJLC>F}3G?+~;{Q1qN?x^FUhMQbv@6~%O`0Aa8luf*7}t4$Bt z)*v^qDYd=p-mV-ldu5JFgP!cFj*QlSFX!~68n3M+$;358+Etytsd#5hE?aNe?8;un zH`rcXlhRYSQf_rwCCCFy;4yZmS$h{Uja9Id8Pd}vo%W#LQ!sOTHtjC2{|t)tVt1Vr zsk`a>6MfhC4(fgD1Xh35_eOocpN@-3$2I(UkSdbhh9)b@@2bk)BC_{l%U^ebZea3= zjaAvJDN3b~y@n5K3o>{jBcCyV>m^tZ7ZmuE{ms>)`w{7e_%-j0mIp4pk+HtEaL~R( z3@Mw@*B^)Siux1$P9OUuRpV)}RD3lVH*3`FDf|w!=jy(W2_ZPNd@Z^1Dcg4HR0$tL zG#}1IbGxDu_YC!QP6)wmUh&c72aO1oTR*V;Ms%Ksgb6j-U?gi|pJf=38y!|B=&KR) z@Y{W5=V1lA*;}ysvGUp;%rXkgIv74k$Hh(s_W;QrWvoTM1#))-NeZ?N$CUahKqjV| z1Hh*vz#ITR69MJ`@Yx732Y}B-fH?qM9|7h7@c9TZ$JLFgI*eynp0Zr!Y%!T!Kg?d` zAC+D4_5E^ief~lFc4iOT7u)}Tki+);^^}s@9{L%b*<%Jb8R0SY$^A}?4nuF&4Vg#52z?UMx900x?0p=EI16x(iDH^tZD;cJnh%mLu*5nv7g--rNn0QhDEm;=DKBETE~z8wMP0Pvj%Fo*U? zwx^7Lri|(Mcfk#f4pB+Auh+}b74y(y@>lPr#j@p08(8fZSrM~=UPFW1!5Yt+BjsFc zPCwopO57YJYHfbAH41wH&4u|aD2qHW5fs*lG=bb4~B>`u0jO$UD# z1!Mj^aQ-lvb6q7XSaLV|$v-5hZhPWpt=F85j)Xe1PAh?f(m? zVfAkH&)PKEugvBXX!r_$Q_?eZ;9GW|ga|rg_>K@>y(@pt1zn2_Ot23blf;+P|? zZBZ&51`uB;<%Db|m*7O>bQH^&9lY7Jg}@#&n5_}In!Hs^P9&Xwc-~qC?Luw(+l!o`EZBp57?V z5+Kc#Ar+YZUdd~cnx0OeQ9e!AqlMtB%}A7?%?WBv6fQ7DH$VjSmM=FaNUkZhPm)b_h+`iT8#4J3q#kg=Q_7;%oDv2E8xz9zF77 zd|SvGWi`6y`wMzs$leVye#UG-%&pP+_k|?y^QvPR_^orb%pXJqdSS<=>3 z3e6hwq->dqRBJX!JAE##^KfauC3OjGB9#Ost2jOb*1gh9O&UgU>2R4?wN-9Rx5UFd zdnNq6Dc6rm?5DpSSWulU4R(*cM&3*fgla#(d%}GH#C=?6;CI#Wp2w&67Pu(^Ae+lX zI&zTDi9wYm58cuC5rc{=Jd~kPd{%J!U4;pCaTND!__ba7#=bIB`v19}?=8yrYT|6} zH@}Veo>cimiMVtgln7K*@xARCHo+<%dxw5!u?bcIrvR&jQ-D><6Pkfl%v@4U*|8(} z68U4pT5E}}sO#_RK6_o5UjCs8t+BFD)Y;BcqO576dVgyuYl$$KO^cJ+G~ZNNZZezZ zC$nvP^F-FTK3dV_lFrnt(>-rCQ8gCn6AecqyO*YN?9EXbsm#R$y{r=|Wo$y&pNw5@5 zBltWZTqG%ZfThA6i0hZoD1OkEwb+)ic)OtnPR3*5gG~dhJiPK}jxx>m0!_ck=4n+| z9vSAgPcv*lZW>U&O$)9itXZFxEU&elG)A^AX3yhRVaS!xE%aufHqvV=ANpD|HKAMR zWy7|&qL&wPAEfrZY=+VS?WJdOCy-`=oZtxL)6eD$LC4MImc6fCWQto!RAsb!40@ zS}+asU3wgDPYQrCQKRNs#%-yRYTWj>G>lsc%}Vp6VcZTwe0cWImD z$Cheoz`uQXpmN}4(Qm`+9jdEvuR>$@r-9|h?hl#Q?l!E}(M6hx{|eq_F=J_EHJu)) zIIm#Fi&we8XazH0j4pE_+m_nu?Xb_LX@LRpiw`$VJ8lF~OZ-!C#$<|hWH`;lA_ZrB zWFSnKtNEqfTySz_^9gS!Tu48f<01P$AKQPRVmy;*ZEXK$njOYE&==Pi&lk&Q)#uJ< zD0>5qEK&RC8c+D%s_)U%Vn_XT#4#Z_3ZVwJeHX1&?qJT~wma!@%!GEET&80tYj)4Y z15BTWEKeAfaW3CZ+N^NS56Q6f+w_%w+YwOv#cEs+71g$sDx|V$;0AoQ0E%;zQt;j{tK3xFZ70 z;oEaB%EORFAH>ce^}TZ!4u@l979gvz!Fnoy(ZPV>U1zZh>U||XNeFc zy~RGgN1u$2R}L1ilyvt&;(JW&B0ir3(wxGVoD1m-RK;cyjZTyY|{Xmc#zW2U_T^)Y4qH! z(s_9jY0!@`>A0W;n^V|^NNYo=?g^BR-^Z_AjFvgG*Ahv69f`VS@ViBKkD`-apgDZJ z+YxUM<=~x6u(>ZDYv=wnAen^prpre zvw3>$;Viu^g@ZZnCoU_mIRHEm0p>W=vyZm@1{j_)s!1|cnv78=pU~Vst_IOMK=M7- z&>84Pw{B}RPn2y(Xq5L63n~n#q9_Hk! ze~lk)7Pdx|eH(DN@sb_AEfM`aAJGokcN_R6lYh1ha2EI{9Gi-_iW`Q>n@1Ux?Mz27D=hp;FeYl)_{esg%CI9!{IiRQ{UBXv#e=(!8EPh#wj} z?4W~A#hgP0Rcsl?=HE11N@eGZFrA&f#=iBPm_a9=AP!`jVzTcH?%xo9{f}U!TQR`R zY_xRlmHzX^3cu5v8KgT&`ON8p*zU|i~V+fz1du7rBBA=)hgPtNTW;?KBwACEZP5L`T=zTz2~ zvx!?2HGQz+z6m$e#^);o*$Tz!gG(hF12NELk87&nG@>%t?;d0~kGrblM*bnNI+0e? z^~b0r4xZL4!3R`fLq4Ln$^QC72JoeDkC?~D;htf3dP?q50p0h>jc-Ta814?haZgX< zty;){*{hUzKHmZPTSDkkrq+GWPSQD{g0ibbANgvtGCn~WLj)U$z%FeyitBl111b|O z36`=y5vo7?v+8FB3iF{&tUf~VeoBGzJBT}Ts_Rx#&1ik}R%f_Xa_aq#><7vp5Y+4{ zqj#fZKLaKE8A0^Um$Q`pnUW2wTIu|gY{~7Yw5lXqbvwz~n^t>6vejm?Rg$ev zne1PmGTC1#Sp*ohn5>ACY}xIswE9W5-|Zsjth72SB-`Iiwx4ACr%2XwSE$VtMs90@ zzfF}XG*85nA=KZMtK`vnje)Psvd=Dza_SHc*6D@o`YoJ`35X2hg z+ce4m*&LW6o2BgUPo0sUDt5b6*ITH@-vsL&2@XoIWMiw3t5}n%-USCHg}3DXsz@gtE_7k z%hp7=wcAt9ZPMyCA;PVj5pGR{TTc;TDf{PCnQu+!7fNR@MZPWRZ0q)xbADQ#AJW;j zna;MPv+b1W{OhUHxmD@xqsSMK&H}fuoD0+H!jR5_W;zQQEOiITxh$ zyR^DpNGEHilaY?@XKPQRwX?89f2#6qR62(!@?p{$c8AJYORKez&TuoGVbU3%G9Ap6 zK2^$YR2%LI~ElY0-Y zzU9^BBfC^9lUX;CiWfbkN8m^4tf5Sh)perC^^#)tT;=}ViY4XY`?GOmm)bNMV%)yE z{m8EA{gxPcn0Pj&ezHGKyaMx4(z8$JNtVUi#j+-2QI+2!Qq@fUZ4I+vC zp&^O+n7^!;S5#Mw99;hssrId`t}IT>kziY0=>?UB+02RA64N+ItW;NSD*jnDd6jv_ zMQ>7>yBYv1Dc{V46RSY*;yY;uYhH(|wUNk8z)m(?N@#m+j;`wWO_CDKA)ty$e zaOKaZ`6uLv>??4ut6?E(X!O{nIk;=W_shf2Xu?PH@Gm#vv+{7|$KvnXxw^A1eC|@+ zWi=`kF%yrI33mxUePh)z7Di8qUyh*pv$wEh6L9jc#utqR>@Oq5(0XB-bbo85`&E-p zm?qudTj_q?q!Xq=m%UXPSY2J6aVlKvTT@*#vL9OktNV5v=-a(EtGZit_sjbBsP5se zB!Z;6J2y;sukK#m?V?jx-kDX@(B1q5TNr)2rqx~jut?FlR=PH~hN-U2Un>m^WyijI zeU>YH1o$>TOa{rFd|9E{pWwR|Jp5?BdHAOT{3mU2EEp$y67Cgz^Yq`qulR3ogFmm+ z!^ipN>A53l^tZIZXR%IGJUjBu)5H7*e{UQ7?f~y7wbCDgU-7V2))LR>0{rJ~@NLST zemB1PbT0_-E8E}?2Y3(X)RCU#l4q%VRQR%tT8ooy1M4smjx}4o@6KkQhhNr{B(H&D z8`k{u0({;My~!~7tFi|`Tds#YuzDKy$}?WIC|;2rz@T%^m9(wxyK219259FPW9yWc z`nBiTIR@t^<;j1vvhQ!izDLqwhSm#{p{`Lo=Q^UcEm%dZRc7bdWd!bWCvhn|8zFVS z@jA^v_?U4MpjiJeA~~!NOg9&Np!ne7CDhZc;`w(dl8cexj;0$w0bU#ZJmUbvQe z?ko3mkX#-aWF2`JBwj}ri2E;mI%ei6snwqsAN8}awC#>;D{|*fBCfuH>cDCzWc_5k zLs=C}j7b|B?i5Qt>s5&0?h{Ridm$Lx^oa0&4}VcByl=36Dv-X}RjjgETe;IjF$?PK zF2-#t?Q{WT1%~ZuobDwGFsC}FKYJ$?=bXHAt8;1Xx$dQS;477EUUlB;50Lkyex@j9 zB|oN~r)6vb>GxtZ0vJDO| z^le++b~TMafzK6vSFF(U@YTl!nqQp{(3)T_xxg(}d#J`WCV1 zdc7P&)uEB;c>WuY@8EdefG2yS(2J{!-J9f7RZ^6BTU*Cs@S#7ER^hxZWS?$96P9fjL4;xW`4su%`;4-ofWezamRuHV5O z?h{YV%*~KZR^G|;h4g7mmp851_}xvmWW;EiXI52LaR{{&5OLe=zMXn^+B9%^W7B*e z4GI#Ai}m+`KQGXEu4He1nv|*0_UWE6ZN8shA5CBJqJp{8?n>*P|SJx*zZ-~7JW^|MvDa&NgDCK}!m zhA_-OWwW`POKb9aq?fSG`ET7{YE29b}=;BCHi4?R0}$G zWxoXV>VIXM?D{&h1CVlErG6H#QONRiEypY0QW%WeDFWho zf=@{3N=qoagk~h6a8p`7_X^w4==WP0bS ztu}uovi%11*rdCo8x>ovNakn^Q2$mP^!qwc4)y4`KP;2o2nydhruhS9x7R2v5nDz4 zfI@MS7AVPA=LUuXs%AS3Zqz>sc4*LjTqW;mwiCM9xvJu+g|hn?Bw^H*-Xrhol-;$! zvI{ArJRgzgE%Ma%@isr^aCZ%ji#0FB=11I!n7jGbf^7&a+l(bb9V|^29hhtv>j0rX z;Z-oaJYGmzVAJEvFB`Y5qjZ*Hpp%OSRBu z_h?Q}F79%^RyOh`DVAn_q!N35tBhPFdnQ{y&)+Yn*^*OF^X-@7e(eSJ#GvWp9!NO` zC`bJ-1lQTFLbhK!LkGO0Jis7>N zGR`HSUZJ|am{Yf<%o!W@BGT~oN5OrNviXNUFmCfcc^>4)zXH--itm7kkLXdwk~?3o zgEXT0Gi5Hgj}by!DpK2-MY6->q)GErqxmc}nw+JwGl)XI!npnd)bAsc?sJx?9meW3 zwZM6|_|iSBHQCfm#5AcMR;b@YSggl28Oq%UCskTZH76Tc93iH4xewD|K65PfHm)L# zw^1=V+1Ao`cC!-}-8+Pc`)$0A7I=+1L^Ze*Q6bsunc_L?6S{wu>`c|NG;jQ|{7!ZU z#-q|^SW^8+^nyPZ*wOrJ%+;UTdmoTdT~xfooP-zOY}Y76^>k)4*eM6{5ILV5bis7}|8LtO2;tWQFry=TJ~ zd5)>^wQ;rV6Eyf*dlKw31IaUX$A&MdMXFuwr6H+Q%j~6H+T@Ifi^_<6ch z#Q&gKUcFhfJoNvjS=c(#xc=2-;qPK|l#+WMPWpZQmy&-Lrdij}nEk(2=d)v-pQ$>3 z`2S6vzkIVgfAIfzb>5k9B$CY2Sk#X;Yf}*2IyV5c2%;z{pQu@5Dq(bbp({5!M`83~ zUuq1Ebu|X*d5D9}5vEm0_iE77SVyJFe#zD+M7|kK_I7NoG4Jah{4V5sYvsFX46f`kDP=&oUiWnYWsS|)r1{c_1&hf6!In&o>+?jv}oj~xsB zma4ap=gR*=F126S6w&%H60HodYxHZfpjlQDq^4ssumL^ZkO3sNkCKL25hso1d)gLc z{bRro*vi(T%<+oZmKJ@OVvl)O4G{iz3^>Fp(9KjO@Dbvge^T~wMV&DFe*%ZZQ)b!G zuQ%H`+aB2?)UsCCjfKs04noR=<8TPg2)5s-917^YI#-*S=aiW(3zy}A5&NnrUwoJd zl(00rFp z@+{DUam&L22JP7u37q^v36fXvczL~u<8%$#|VPG07a?wiVzGx}kMpn&kYvjwYW;u!Cl7HEwTFqB> zf%E(#8RT{Rp9MV6FM`v}lD7bla-rfmg#Yt^_TLL9S$&d_*#PNH8g`HCR!&szn^+jT%{3a5H zsq^!VO&wWaoL*XvT|l%(Fnh-poNSqzL5CoH#SQF_Mg}_&x-R!!ae;Q5bxIp&B26!S z_?uHval>z^oycTemLs8B^D_J}1V`RQjy+w{}rr=P8Qw`xp3vY9_SREZU}Wj<98n5ed@SRTCaU{(!71M?TWZ_s^@DxD}3wK8*mOJp$! z$EkLg`3cItxH<23jLyuJyQj>hTQ6m%8h&mf-fmQV?nVrqKE_rox;b=gJ@5c?>ec&s z%eM-y`Q2thOCzKf3Y}wTKwfYU$0dlwSczoZWaw~uJAGtMaM(V2pxlSp;~?85wFEB~ z)fOw1-l69mYQ-oz6T)&t@wJ4bIMHZ%ErA6DTe4ljChyAR6L)xaknN2cxm=?92<4tm zxeIP9s-8u`_f>ujgtB}q*EKJB<33ueNf~BH;RZS6!iwDwTiObs6jLqoECCi?Fm^fGbZ><>u1nJQOZ zfo{%<#R?npHP{H#o-xg29i#KAoulZrjP_Maqcf}J(e7&3Xo>eZysD05*3N{du4g1X zhS5{jOBh%4`UR3LO*m8c9Sh0+lxC^5Sv)yYBlY*xR{BO{l%r<~urRc0L9I%I{zMg^ z!B$NwffBL%hc$@2VdL8_)pWTTfk1r-0(n~rwh9Dc6(es8LHEYt-0PHm zd#UcrjYKK>?B|@UZ}sSXLj^=Z#<@6Kzm+l zWw%l~_ayqC2{rI+B60sR52#-VHBCzXZQYKY@y0nO=Fm3lcCpW$cug$(^5lZg#IkqD zPG&NyvQNF_!P2zk(n7tYktctS?jEBoQsmXx*ppo?akOK@HAGjE$YzfksfY~}yq?s5 zNjZ9u0Di?6d*sjJ&gHM=EO8A|m7?`cS{w73>Fe(8Hu296OF1L=<4^t2)bE(E1@#HrK9D%zpA_2zJAV0p7#?k8m7tY6LJL*J`v0q#dqO^mR~-JDSB%;mwA!=CJ=+Ngki7B(nMG?dsH%+U2n4?A&)axPhKEMphH-VBalYIl&MZ-P6Q2gld+&N&O7*N83ANf$DF^12?0Xcsue*z->AgxamtQX^&+^Ip zTU|Gh0}~GiZ6CbfD`i=mvB{p@%?Z6#*nLE`=39CyZ6;RT2C&i)?JdRp=~dVsRa>pE zcZ0N*x+YV2Ag91^2N|p1PeeoOg;Ax}CJU`JzZcB|Mk7r25olOr!85cBI@#%|LYc>wrK5%Q-WM? z=M~3AtfAhIb1OB@1&l&$ss9*mi~Yw&L7(K*{m0{`+>@E$N_hthbbYKVO?2a;J1I$8 zTHT2kL6q&;sK1%*r_y8x-dsE1smAhcD2%mX#+X+tjw?268EizAvl+l(J~N$kMYak3 zCUhTsPc4qIU`Xl@s}T{pixX8sR!cnAs;)b+6~6i-LNeb~OB1Lwv1~4n>pL#e^DQOo z+?qx^H~fkAtCoHD4ut0`mV8HVf#K4Pm0m+SqTBXa{m3sPAM149eE(bRs7nCOL$ ztUj!Q_Tdi0MS0vKHWmM%3Z5>R=r0er7_*_zj8(Gv8HO~uDl`^~XPjNM&74pmq7!k;gV zSEdLNyOXG&;8bVzd3c^EddhaU&-4sQXD6%szmb^=BFNwQcGpfUxqsl`3D7t`q_snN z2+>$tU0g4``iF_jZYgZw-y|Li95#@Bl58#wLgKENEkw?7yYbb&C@SVuM9J?*zPn|N zM9v1O=W*E7B5!`qpP8I75oNT_M5oy3+euEIa)vKBy7rWlJ1sv@XdjifgP5oeX!c1zEE#h( zP+tO7N6p@ouVmg=PXgoBTr}ZO4YrDNl z!aaLR(4X&x`yR*PgP7Q#Wv-dfeq8HuX4x^j(ve!kZF^f$I#2pLi+}VYlGpH`pZlJ~ z|3?1bK>NnMVTOw21^n+0>;wGAyjA80_Bdz>>( zp86ug(`zT8TgilhXiN4w#xE6y69Nu_YRh{&Gd73eOW1P!9bmMdqurRk;1Am|J8+4g z0kJb`RJaVeCB3fC2M%r?sH6aQ-DVH_a+No_j&3j{Bx;L37G;_j$KdILJBE>3mL@glDCn{Hhc7R<{mJUcHvVfbjimmJYa5Mu`-I{? zKefY*f?EK!8=><$B*Mz@L8%>66l{avG4ogS?6{5>cp%A+MUGT7Ur@F`o%&Ujgbgpc z&&HfO{U#6h2@Z1p(3#u>v#nNtWSE>n5@Pc1+;9yv7#=J{(HU(HR-j`^2Sf8{E?A3< zSJ&JGpLFC!wGGD-RX&77-qeJYf-OR<#!T?@Zvsk|DadG%#DYft#ubdhHvtVHG5ZuF zG4p*Bl@~m*AKIFs4h#?HXicnY)ZGZ%)y;U6raxqqjXkGTkkncvhIekzj{auE5_zm& z4*R)25J-e{Hm74tLeO!6jHI`?hj$-Q>u zhd=5%c_Tk>;y*uT3;tn#)UFF~>rZ3PT)yJt)XO50)nI03W{R32Ay@Q>y6n;wvI}6g zWO!%VKz=)E8eSxRa>NnGC9~WiyceO<^JU|Mj&FZo&k!r)aycCrDy){%oXHO}qLzJ! z)RzAHfh&$%hacg`EqaJ_iUnu7?31ELnbBjGz5zRSaR`|xF=w2Z9KcV(JMH_jo- z+w^TFz>MkV_+Jpo8o5gO^Tl5rGWQ*=6haWo9WTAIl) zn8WIwd2}G)Xbban%0nZXrzj6qYMzojRH}J;@MND2QO^{H3O3AE@=(RfcGiu@&vBel z*k77$3yzLcn83mFq~yIap~_F|G(}}X^&gySg0v25ql6&TDn6`z5-PL3*W4L_LlCRVOCAsg_X zE%i(1Vr@qJ6}_xz96|~ZdN)b1%@Y_aAdJaKP(*gz?AfOdGyLq>< z%I@B6oU(_xJ!`J@aO1i?z1tXcFYgwYxV^nwjOF(6Zeyi=y;~ZWZa?pqBAwgcyQMO;iK7pKoTkySAxM+@bD`@g7bhqe ztLXB$zD9PALr|Nb=pE&8{f>m}HE7 z8ZJjRvw6|v2Z&WuCpC?g>WkjcRv-;66d>xTWPo-wBs%j zZk6Fa)sFj^a620AUG2C_h1ToY`4Eo%z)rDU>F*3Lv;l+jRr53{6e<%pFWsdTpIOUvcfqbxFAKN3?s zQ>GDWL!+go`FXc4bs-s|yq%tu9c$A{`?~h{98J&uOM(Sf!(}ejydHHl{jLm$lyCHQHg!5UwC#u&8~j{faA&9zrn#c8Y+FAZ z&ijj(!H{uFWJchS1F3-S8u~KEFB}(fL|t&#@ue*!MU)(hll=Y{8Chz}l#*M7oh9;I zj+Ck|zX$lTWXWam+^?WBzhuvr`a|oF`ui9R8|x3TAzwF#HtSCqUw?=bd75W6>ra^f zoAoD=hVg~1F?deFSPCevAJuOy~o~S z!L)^H!Kjm=e6Ss-4puwHua*jK9wCFq8Y77<`y1oxco?&6!_^0%x@>ekM(ye=&9K&G zif%_WvD#JbsJ%RN@Eh%z*7lC6UhT&(q^&T1r8PF_wSMynxaWz%Fzs$KY>0fP5P7V> z{c>@4j`{jKI#xt`{q1BGap)H6?|Cs;d;RS^buiVRDVaINNc~CEY_s}%S-~^ixqRst z3J~k>*k=71HWU$)eX;&LkL>@SlyR4-GaGnhi)9>}I+)6+s1Fj*SOwZS8uW3Ps5VsvxzE~_8e}hLwEcXEV8@ePHKC_oLdBJ zeL`xioe_ny*kx?&PEZtGc`oa6`&ks8htp()+%S1G@|V^Ygh@mPNg7e?ASpE8fmSr} z{>g-UMoYMLX{H5a!F|#f>O|7^bN4P$j$zQVDCsof7m*{!;K9-!)P7#VmW(f?1}vkD z{Y^UQ2eqWREaa8-hDD5Yr--$9Ex1WI7qfec*zaGctOvJ*k7d>2i^`hs>W4MT%J`T( zGlrv`I>$IcMF9{oMcwL1J>gzv%%X2CCUq^5J*v0EolNqY_(uxW4op+MnJg6Cn_Gg} z#Iy$i{j4QqZ^=(gIO>R;B_=8q$KKc)r>-$9AWqHw9@*P%l7!v< zEL6J*m9mGgiNv2{omh02Qhf#Y-^#!=_h!TnGYeT^=0mVY#$Jo`@m8dd(cZ}$IG6FH z?i7kB?P{-?GOv(b4so}St>lnam@N0FdX{S7)Y$f3D&de=pEfsKsA|bpFQy`G2UT`r zebc)4;I#9Fes>>?rRuwjZ_mK=+VV0MDNKd_TzFL?kt$mEJ>(DJ{V5SPCO8zuz{D*c zT*yurd#9Q9DtJMNBKTRQ=uQsp-eGsN4sFTjHYWFcmGt8rmN!M1t)T#oiR&?I>aRyy zn4^Vu*F5capp9*%*coso^{Gp*emyvjgARd0#fXP)<{r@-jcK6Km%p~cVWM-0(4Kksao(Ve)VT*vM z2*_>{6xoGSDvf(!0@+;G7aal_?}|Ihc-t=qSI zCL#RZ_xaEBOy8)?!fvdSNj;A$!_8daFkdpbI^Ltvd*VI4rDPQ-Ke zAB`^k+6*0Bffuqin@RRb`dXnDrJY_BhX>>`#Gez-I{w0!>L_|8{WTs;#za+KhOkhI z$z`LX3+05ly-P#5k0zgfZtqZVv9YsxuRA20er}(VO!J=9y-BJdYPAkLr_6ghWh`;T zujiB?%zNjk>3zK{G@Bewv4LZC^fv9s6zUk)d7#Ue`eskyS*g@DuG7uU0@8EUZds7!uPn4 zH|fjv5;8XLg~lz9Zy=V3u~~@TFK@QEerD{7Hmb&oj*;QTc|^m;qK<9C`Eqsyx(w#L ziMQshbzshW0Wr+Tr-5VaOQDer)_r`sFS zy>LtE*iAvzZM!3TWnc8y<{!_#`{;X&-*HH6;$Y{MJeRYR{7kxe<4Q$fkGKrm%Qk)G z961Ri5##vcFzGsVnf6NAY;mF+an}>nx1N9b$;I@yQu_Kv(lp}@He%D_n)cQkZ49Kk z8iF<*@++!B1e^BRO=J=~tjQev;>F9dn~BkM4(EKAhlz5naf+U+>i(N%m1Sy?&obBh zXhN=-;!gNtG%~{TntFms4>Y3DS#NzwC<+9`F7QI86 zmkdzM`wYwh0PTE2bco@quB$@0f3R~0p75y3N&J+&b)bweVA#;U-+AA1ucClf^l$8K*6er!d9G?u;#lA7tTYTlnzo zsW===IOu(HnxI20>G_s)YB&dvUqDdSx)GAzb{!tnmHw7tlgg6B*VN=XAC~@(AT0cl znZ_Q9Ix_n{?QLIz`hHF5SH^L-yPB#uentFCVdpI4tKH+;_~2Xx?jBjDzK{u!7+Nnh2?!UXUKMLNu}|VSu56=Q!(~sQ#BgDTI{&8R=DOMo?^<1XY)L=qm(;ISQ(d_Rv=g`bU0%s{1_jHG+Q6L)B>>dZVD9@lf@bhw3hZ z@%Qk8TEFS8OyWF`JW6umRi{dUY*iug+X4BE{`_|_(;GW4)Twa%Azx^a% z!MrdtpIrIF|Ivxq!j*EdYfym6Y`UrRd^~2!v2}=*dD3(-7vBjw;7cN-4!a@%GNx87CFwN z-l>^e7iOIJ7b$l8Z!P(u%D8#h*^s_xOudFWQ$J@ZYfILq0 zeEJfAEH-GAlQPLdFC3;znIy>ZdfbhRmE*nSh>nzZvIjfdJRkZ>h34hiJG)5lyiT8L zCUNKS6U80wQhoD2JS}feu1U@s)IC*0GjGC{iXYk%)Y9-zYY7q$mndVcYt6-+6x*8| z&!Mi#dRVLM{rdfs<6qo0ejszdla~DZ(dRwv4xyQSc=}YFt7SVPbViOWh+Rp_qaHLR zG-{c;YZZffiAHzf3-e_T~ObQ6cvFj895tmnk!HtU6XE;r-Gj zv8!kMNY`uSUC~%6oBZc^G=DJq4w_ z`=%?eJ)M=e&FNk;ZFz!jy}V|+VtG%T?xoX~C+OD8Yo;rfH+{M|V!3aB@xbIYWSvje zj*(rR)PRi}lX7aL#|?&Kb@Jur9EDd+GM0ft{ME^qHKAAIgh5J2aTvKHE%KJ}o$wJn zNeanVtM!FUiV!1rjCj7#VYbq|90F{)W=wjrI`)0E3DM>Ote%24fqcC#VK(bSuS?K5 z6^2E03gu+8n)Hh9=}Z{mUcUC(Zo~E9Sm1JB5SOD<*gRK=`xFF}(r92hzmi^sgQEdz zgARLrxg({zY=-&W@J%g-`LX-Bs%kR`lj?oS`F0vBy;gdm=91ezqktdP%IbAYwESW( zPO1IQ0Dh|K6;G(Gs{`Gr8y&H$Om7)O)dbTQr?k4%`DGM@n|4bYZ;=YoN9nY-?euM{ zYw?7iqDd~~hG*sWLsKbcI8>t}o*i(A?cSZ!ghPH#;DsI=?mp>26A-WwU1TQjIIGJ8 zer5y@bxHFCg7K!E6aOSxON3XbK@s&nBILO3oeL{EGSn!*_xYS??F&6#TKg_TCOsXz z8V`^}XB%$-fl7JuReTIr=HPDkGUPSCZmLWug4gDDyTfSx09Oj>U|Beg_MqBTJj;fc z0>~SU?Sp*0@bhUX!?DIdK1!J(O`RsS?^5?na`b84;pSAf4oz6qpAPTX@Jav$qWfaV zbanRc7u!bFx8`a#3ljN|BQv-+b(+U-77lh~0TZg>Wvb!$71WEt zpwU@Kl{syk=H2*Z60~)rR?JyP`YX!k`INQtl(zJf&R}-3)JF>kL!nXWMA(Ihp5M~O z&02dDy(pMMVj>;dTqorW`7DG^WgryCb`-{TCkmLmRp)p@6G*@$2`6Jb4r{^TFv+CZ zcHA<%yd z^lE{w5y(q*Pni7WQ>cPYb#ctz zbEs1K4bYpvI|mxXsw)0;&TD)}Xg5&9m}|?LftAKDNRhr)JrZr=AvrGn&3Mur<^#=h z{S{pvs^gqiNIao=u<<%lbZ56M1P05B{PlqQJ2`>g>28vf-B4Qkdz7KqV%W;8jy=qb z*SUEB+odq$dC@VPhe~v;q}kv8KI-kGXTHF{VG(l@d?>sBAdWs5E%|29w*w&RY0q^^7rRdgRZp8VIFkK|na@@fjhj_%>ZvKdr(R=j z&=lXx9Y=3de@bm^)A}!|WZW{X`#jS(5}ovK(r20(^g7#F@pHk>(V`>5{2xacuSKv= z&6Dbtws}&0VjIF5Sn{N|SP3l?U}ke&jh64yocmU6IbWYslKvN2EpN>!%vaTDX#0>? z60wBy`yC*GOeMZZ3$iQ6!1*K%+qjjS8gJoeQo%mbz>?WJay7+mDg+uV>xZ6H8z{H^ z_K(UmEbq`H5tfH-`KyQ6-nY``&h?pipKSYSj5$`)H2LnaXH>3PzMiL@WBosYAjBdK zdO2#kH&5SvjLyE4jJj* zmnfPUtvb@*g%jg`?m40m(Pv3E9nrZSKgqC*(WpYxb7UC7Fn!a64V;3=XW`i9b@D6}%0HYX*8egHlVixfv%%fB*!(r|q zcoN3P)_!?H-}L818XE|{pW-W=8nh`suAiR;52rsSd7EOoi~k2#kRE_Pl?0V=a!x7~5)LTBK-%~DgGjLBY1 zQHT*bvA(z?)$7L#Kt^^bvqOu|S>+o%qcyR<72~1a8>2;q`(!Qx`xmO-wy9BVs)(|lX4X#ARTHmXR}QumW|qf|&boT_{E^pz@6 z-}MBJd^*#CEl*M@`eGBiO$ZqcYB7^=4J5VLZ?Xitgajr zuP_MVkcs=c3W-bZoAxccZ(E_C$k*A)B!WOGE=@A|m*VmyGia$vRB`&T3`xm6zX8Yd z{g_iUFV498{h|SmR~~9oe)jCXK%6MeM-1suJ zVltuMYky0PHxKAHeGlAVtv@Hg3ARkXKYu9b_btBrJeW)GaG^}&f^ynWc3;SXOwCMp zBc=H6HU|LY6%Q~6fHwz#IRGGU_!#EE=2q$x5y;O}1yR|1LDG&|n&X6=LK=4w;O^!} z#Hx8-yIkDCj(mSD+rJCapK_lWYV7wBr8uWCF$*N~r1$d;{W}yjK7g049}DeBKZuVC z_QN1Pgwr~!tSfqveOhM04)mm>%C&0evdM zCXt7Vlo}tQeC((U<4vnPBDyc_qlVXD`rrDt3F^Fr=~vXVxjVyD9Ze}J%yWa$vFp72 z97P+W?pJu3YA=sZdXI8d7kvzeb&)v$+#3Mq2zM%6C_dslIEqnU%~db)kiBJdW*(Aw zNId91VIlNwY;St6S6e;w4=C5(DK5&g8;}D_W7WoX2*MlXZjn1J)LY7X8F^;3-H7i8 zkVWFNEdoR!`y%Vu7jehFNI3RIl(8?egMHtXeUTf?&wOrQMwNY`SNlSz_JvgK3whZW z;3=Ed=Pjv1+?SOK0j@384_i~+s#IUFR0?rlRjT(-N%f=FRButL zFIp;vxQCSL15;9MYE5;UQaxa)6ym<7R9~Nx>c_3A-l|j&S}KLOZz$C_r=f2LN{j@dJ?Mn4!OQjI^9i{s2lvF=!P4#xA`iiAei2I&WeSb=-pSPxZ zhf;mjQYpm!K&gJHRDREK6?Cq@BDFl+n#CQ;;vvgIA?`<{jwc|WlG~)*kO|5}M5%;BY+v6;c!yiC~j=zNybSrq_}ZBPX^i+;8IF`?^TH+xT#iZPQh~G$^{Y@{eTp z0?(g`U-N5010OfOrRM5Ui2F9qCHKvWqLbZsaFT=eG}2$28J4--(J1i3{)P{B$FzX# z=E_T{2C4t5)H`Y&5IfuRUq)-={=48;&@8wgm-__T8}~othGD7Xf65K7lKWqB!-3>B z7WZBNxc?*gak?WfcK?=pU)mS(K%9p(ZN$HHrk_tRq9FZhF+p)#S*9CtMIV!HyzK^cCN z##5V*kRD37owD$&nVsCFtS`NLVY(aa#=C25+RO`y2sf=b{Q%R7fJPui!zF zo6%3`yvF-!S~Z9E$INgI0^l}8W?c0<+BK;Qx;UotU{psnW10CPtcP?vqU?zHB+l|e z^`cJDX&+v2=*n{9PFR)@-H@=+qm*@*i9SA<(Z?UDAoI}i5QHu2L49HDHWErFe@LCo zY+g;7oU1+KkC02roe<(eO2j?S$NjM4G8Sg!la4q6NX6| zO9O64itFx>*>uwQA_LJbfc#n9_#^Rn|9MA-K>BN?!Lx*Le`nb5GKdcCrhkyf)yZh& z#w=kuPxBj#SxPO1k7WIf9HsZ_3VT&+a)h(@KGZd%y@lsQ7@zchw_96?`781dok+d0 zo)l3hso;fvMA&9+xa-hI+wcDoa&>pu63ynpgCqAuBtH3k@M z>6n^@x=MCBhwtTEtn)7YywGVYw%GSDm(CV7uv4etlF5vMp z@YuLTJmzf?j|Cy54UZ(XJLc41#=M_91w-Z8is6UEU9yejFb`b;PPW)C^30Px&-@tu z0nhy9bUd>?o$<_VPKUraO?iTDy}V|+VtLc2E8BUXrM6FHGF)H8OYOld@+8_TXr2=H zE2=1cl$1{STjNqeY=O^jJ@4mjL*Bx9`Z**q-Q$(?OXDLDkml=0iOhVhPjgH8+sD*^H4FYm5}@5NvH}hx8tD@$ZoMX;i%2 z=1^CdLlw!-q5f9Ta*Z_oEwks>qVOb*@x`4emt_>=;Nw)QpvySZ%VUB8tjy~^1I9_AjCeV%`g3ttfonYRNpL9842E%% zHwC2+e={A;w5PK^+~#!mO%JL_XJ*?DGZObZ3f5I{ zr_r++-=D$T5`FPNIEh!uW&wZNuFlB^Ma9O56VWr;W(L(}YD;MBh=yM-ykrk#lGE@K zm?0BP^-|BLA;%LUyG6CP$ZTy5THm?G7Wd6|wJ{grY5=he&}u*@{o{JIi}yy5a;tDF zHgF~5iu3OF#V(#++p(2xY9Y{pe5724$12OM$n1qSFZW*ouGee{R|++eV>epCqwg)# zkT$gJZ8BhwIF}e1;X~hErNZ}zUc(ClBIv=#BGd#AG4m_0C zmhgBl>$2-w`ezOMWf?7n{#l)a8V`$GCh0AFA})Ww&A#RIZT6j6PT%ewrSwB`RA*K) zBM5GAUp%uT_w3m>vorUY2?UD(x?RU+&wv`zJC(dLvy$E=M>PI#+N+SB%$hk*o0)BT zE4wob*|BBrvwM%SYv#~`+g{V*W=p;8$?o}u>@w^sGef@h)rD%c=rSH}e-}fCXOLtCZ6I)tmCPVgoJz03g zT8W8f|LG&$-;IIvoSjc^!^LEg*O=pP|w ztHttU!*8I;g4p+7%52|}!+>x$pvx1ru&j@@R> zyysFh#-Ht%bo=eX<09T^!MG%A1Jiw!$JrRp;Gx>GB#G-M*UF8br~ymqkJTN#5$65` zQt6N6OWzG~a{a(QB~9zneC#aUiN)ye9_a6>57fGAJvaCF#GQ!|e}T!l=By^f}Sb4wzvoJ@I_$d~RrI=NB~Fx$%0Cs)CD-0&6ec24A^3z+#4$ z41H~M&MXxIte3kgGfRG;%`7G|nkTI6M@?b8WG`3|R`Jvdc^+RRPh`AOB;Nn3fU`0B z0)3kDSnoLg1N|}Rm9=aNyRfp|X2$z|ku;{zS$`$qdmzhIw68oAL6P@=-G@s%7UE9#v#>cc~)3gKrIQRET>_9P!Wm z*x69Y1Si`X@v{ULN4xG2-Kr!$#go6~)6+VTY5dU?%s#qy@4 zLk1D*=p};`6QeVZ5yN5ET6WGF^MLr!)MvnywrjOu6>w3v5Tr zQ&%!SjM`^Q@A^jOov?b;3l!<^wLv6$E87^o$>^875K-qIo*TPmM7@e%WI}9miNTzzBFxlf^NOMX1Zc|)2Ay((ow%rbbVh& z*MFe^woujVl?i!LRFKuNc^PPP!GoNQ-7$Df0VLWeq2)86<09KdlOiVNT_xbGAwjui@ z%Q;E0V7IYQw)+j=WPD)#F-V)HZ6tfE2WMB*R3UlJ1p@zjip>WYsuXDtTSM_bLFpT| zg{RI3?dhy>NJ)qd?+2m0C+H#!GQa{Z4iA6Tx;z#9#h)5N|A_GRxVryX**tEdr2 zZMmSMJd>^Ymzb9dd~KRinmq8$#sd|nzymojAUYJ3e){`#bl9HG`e_@|-D2f^y|p|H z^;Mprl=t{_<+Z1?^0t(2Lp-6p=4A%}{ZXvpp>ob0nsUf;8;9R$#V0%4E-3g^r^BZ` zo#FEY=`?qG`V4j^Mw~n9T+u5Ha{Fz0GtWc}$4LA@Up4 z#qosDbpHTJ|MVqS0RvAL$$Tf;G*=cef010#YOw9?wP!?FuFfSh&Lx$j`#|o$#Lq}F zKpt2|jo(Ijm5!TxfHn5h@}twCXal#ivrAV{s1jRy{wV4gT;1JKx&G_}k7vmZ=7Z#7hyBLc zJ>o1zt>b2{-{H~D^c->v77gVeeU{LyPK4=!udGt|3bn#ls209Lz3>$(mao3Ex)It| zIh|$^0^POl(Vf5uqYA5Gty3BV!*6VMt*5aa$PD-Q>as$rulmm{+TPDBb|r^~Q9z*S zt*H%qX@e1e4r^{Sd84<*kD_lzpSW?x0`)U26XP{+0c#Ebn6P<(IhccXsn|W@w~7`T z_hKtnjqDPjhbl1*jObK#7wmw>X<)#8g3#^`_fN<#+%&U&)af1s>>h`lFH{mKBUEp# zeeJGVRf!IN47yB3oLH?CjuWsFFVl&3=EKuzM|8{@UzfwU%9gNnJxjs#GO}K!z|*k+sE%#d;V#TQvpU@R+!NQe;*1A)P7{3v&v(^N$Oh3u z29aM63%bg^h%|HDMM}A=DJtCgs8B0I?o8q92U-T2lm0;~hEm17ftW|jT1Tg_HE$fq zIJ+&!bx)6LZ&wl5t|G2oEw~zj3HjAZhFa%fe`OrKcHzKmGzv~h5l_cqVE_{>>+6f=KcGjoO5dfslYENf8d`sz8G~PrVH_C2E5C2`NTI~XF%ssmnJ-Zb>yB9rdfHc$WS@dX&JI@VV znjR*OriT?|)5FrJ>DjmF*5l+-7{jD z>se3bId~nqxTJ#zqHBzu540Q@rm%mLtE0bmXQ{|*3i0QjE(Fb9Cm0bq{)T{@VtqUd($v!x9m z1gq)V@XH1-w&8@e;a3f=OCUqrUS{yG0oRk9U|bz&{Gbp%Y2n}4V)zEa-P35u*nNvg z8(8CdHuxWEu(`4cSc7V#2ULhd+^f5D*#7W0X?iS}13K(Zl(%aRP&cVwc`p?D@k_uh zf-0CtDD>l3LIl+`kD$um=C)TqKBNeVQ?;q@4A5bUG9$P(9R4R+J>k(0K@^1Iox-(KCp*9X$kAlqUuEaJh1)c(`Hf~` z(|w3&^7Kh6!U&n9YMLyEBvlJ#*(9l2#b?DPoiOnQj;zZMKsN8cbVhX)X);7pul~O- z%Io#%m;j zXcT_IC(@m}WgY*a)<@A_W1rZK~O{(q?<;>uu_%ImC{`nHx1wX-jnx^l4_D^p)2<12}1$mmYWY}7zQ zd$KdZQ440?m7z_#JK_nUpOOQo$JvPLBVGd#uj3NlQ?dghl}`6}Fgv=W-CDD1{I+j! zyEn##`;TJoKWhA(h-|+9L^VN$`5jHfObt4Hrgw>jY6@9ZQ%K1k1g=sd?Gs0D2G{qf zeUh1<%0o{fH)xCas|&F!uqUh?cN zz$MLyv!E|9o*3ZT+gAMTI0ymmw!@@+v{F`l4Su-CipDJP-Kc>va+n(%R$o6%e1Cz6 z)#H62=EtDe@jh=8p6Q=)#uIA&bgot#tC^ElUS&bXjfKYO(~?vpl)XiZxP*u@VQmiK zs?)H3f)PB%S~ahNF5VCq%Cg)MHMFtOF-~n5PgTL}#L%vqv}dVadQ{pR0A>e(IRMlG zz#IVj0>B(>na`tvHc&~=LgwJuTp1BrQ7Kr6LR?kc<7kPi$MB<)S%dkK&C$P0HQEb? ziA`I$&!sZ|x=+A(S@w7duAeNu1P9q=E33H$*pPb(Qc1R$cqvZwganK8T`5fxHEfa5 zhn-(Rgh($YlD}@=%kaL3ZyTQHuCmOmnggxXtgD)|6xCFS>sB>$PMPJ9(sEMKZbxLu z9CALQ;$CKX3gow-S8Nz;&3aT>)5K)05Z5zJ)*jJdcf5c#$G2$1yNu@~JYGQ-xyV+K zE9U?=xl+xSM|fFt?Kq$aiM1z7ie5@)(5)@R^~;Z$1oW5YuwW=*DW<`rLAA6HPu*~v zo27V+D@Rvzrv*k^7=t!mN_)E@l|)f$VmeAi$(D7WNk&T4fR?zJjax2QZdd!O^@D3w zyG*CQo8xKCTXVG9pkal;%-T#Qngxv?Fa(Ctw|<{*#0ynjwO)u%ot5AeOPt#GnCXrEQt&y-lob>qA#lbv$^5lP^y7a&7;)EMdbyQ`fL3eN^@&- z4W$PRrT+AbhSHqc?An}k$fHziqErK=T8`4}be>RJXeenuk={4vb;P5FXCQvaeb$Vl zKI+}#|0%G$>HmE8DhPI2w>#_IxTMwbd~zSFd6C$3uIJM!^$tQ7s9fuvxGeKvH=C~U zZ4=LAqywo5VyOAOrl$)b{UKzEva*7bR3j|Qi))AQv}c&4ejv1Fs`^|{cJHG;jonBl zn}0v3d$}x3gW7Eenv)9|AAWfGv81!6yA!`bK-F=)ws6+8%RL(g#F^3YO64*obZm1~ zCl^^3!i}_hD6N=R8{6vX%+8t~89PGqbXLfky%_Z(Fd0U2C2B&jqvNh*a?l1if)sPpMROWoj%;sx4CEE-4XDYl*OE*j-3k$k+ zS-6?5C<|{X-9p;#1JJfN=^5*fCur=F>_B&*eMS=uE$4_w^|QBhdT8fI;M~LR>K&-e z2BP_C%!<41fhFEuR{fI8lxa|c%dyC{C*8kFeWsF;rAn&d=mQdg@w#ke6M*Vb( zs?D)$jZr?!Emv<7Joaw8rKu}C#hf@HUYOWW%vmiu(izGvyXSHwTJvIm1wdihQ6j>m z`a5pyND-4N)s`LB>Q;K<3)iyaK+De70O|uQ9<=T#b`aBJzV6swv9<2Vl36;fJGPRJ zb%!sRQSR3rJ~2xJQOuT?20w$6r&}5*w{#~mOGo%YCUmPn3&wFUpN*ms7{)Hf9Tow} z7zUyv(Y@$s4gmE4Fb9Cq05Avj(ijx|A|B}tlV_>^+5nTGP`8VaS-kDNr{5XeYU$%f z$>zDMVB`bUI;i=4*z586WcNxHOxz?x1tWfUs0zVQ_^Eu|)8*kgEc3b^Gh@XD^Rd&7(5pn|{X(tGL3=6P$b5G!hwetUpEbknDIP+^gF-}e05~`R z%mMytkL7$nY;@w*k`ctw)v?IrIG7F8Lepr|R1=py_$;0FeWdw_(mVuX&Gw0*o2uK6 z!Mow96k~dXLvU=aoUKaIB}@?taSJuT+0J5Vj_&PDh3+HP$Cg#SI}_ZmrUhfSny~EY zC$|j99JecUU>maj5h@<6I-I2WgH^i`+(D98=`ky}JH9DNJhxy2SSLzVmqVzF{YeL_ z5_3^lNMzdeHNZ_)RuknBRNQTDk?M1zB8+@Lc6$gz--(&W6Mzq}{)h4`I+{Mq*UL>S zKtgmGfrd(1v;t+893HDio$lql*#Ug65G@WCXc2Ri=%dX(T(knEkCM`TEb{2VPIoxz zy6gLT_|WEM4N}&BTUoIDBPnEgACmhX z7M+!{@sLxAbC2TiXis=!CL00%s2s})8aEuR_BC{`h5$(6QCYMC9;rIGak8f3Ut`m_ z*#`ql`*zT6$!G*V^Dfn?7oVJz{H>pH8ovM(v41IGx zK2JB#1<#LzXJ52%o5;rU$;ienR2QtL(~O3`O{QA(?+Nv^kd6BB*wByWDE8x`LO+^2 z^y7M=9{TZu|6M;WrjXcONRI9OczViIRgJdRq5r%7RMFG-r&aJt^rz+yv`87xYUfME z-d07OZ$El_0-tZk#Ljd0{$AX@O&xAog$`e`jU9eyJaHTyo{rJ9ZZX^%zMq&JfM!YZ zkwPWFt{?*QUx}4j$)EjaiHFH8YMaK+giR`CLLZB_Lui2ZtI?7wOU|kz@hZRcIs(;|dmFs_)*ExU%H0zvhBi4tRy@uM15( zLB^rrk?;9z+n$L*joD~q~T0d9B+-G8}fe%TC`&30EQk2zT$nvI@B9*zB(3w!4DMi1@G zWa@{Z#ME>jT84%_oaNkU_-|LR9jCw6B6ctz7mBG>8Z6a0xy#Mv=>eKvV|O)ZpFQj(D3ed+EfkZztD4Qtp|v4mKbm+lAP$!=R1|Cv^5Uf0$acbGKg^MAQgj{Kn@5iL_sdx@yzta#psv;xo^ zI*T7Wi)2%*@noGX$>xKu@YtgpRz~@}E_w)(qFde)BytNuzK2FT+>S&+dUT2zWY2S2 zWX~;23ik(*MKX)!?(31HhJ~+8l8P35g^(>8H{g?uu|`Abb+P-V3JVJY^LUY>@gZHV zX|WE9-K$9jmq7ycVyjzcz3Za~KXu zbU5G7HgRx!nn3$TlC%jzgXu~!ui?d2FW3&dy@={ZNOz?p+wrg!spKK8H(KS}6SAB4 zwr+KCWk@BTM|k1GU9<~ExyNyzPU|(a{(HHeQDej^+DCW6~&ZI>TZ9$>bR3a$C(4b^8&ye z08S17a{xFc0L%g4)BrFCfYSoN9Mrud+JTAsb6cBNv}N1dGOxIu7{#%>y-6=&;S&@A z#d!pn5mCMWx9=5}~ zHk?CcP5-eD$L7ktplceA;f)G0JqLT2T^r7AjrZ|pJcampjXU&&bjeR0v3mz`+!93C zu1v|Xcu^Q2V_=ou+p-FDD}crBoz3>j^d-ZbG|3hkE=$jK3}0X%Asvh^2;(b#`i-GxnJ~>tzpaKtdO!od8^aaFC$b*iAL{)3`IYpS?=Xs>qaX zmC=-LHHc@HouM7itT@9fp4s7%&UoT#Uxdmqb%5qpjp!`HBsCiMxpHrULVP|+Dq8V* zkw#RVm6@cXQeUp-pbz6SdEzF@`n=~c%4n>^HqHorFP|pX$u^J4Y0u#L*Uerr{#$aJEjNLlDUTPpZ{Zc=FGdrbf zKdp<*0pPp!0|N9Qr2^Q(RSOU z4zbdLI;23f$Y@q}2$F1|f$=#IY7+q7Hbnq9KUCfvCa0_ducF+x(&*qpa(e=~?f*np zQ)#~c+jWp%0)r}If~;ogjV~p-cfhZulL74X$=LMXEvokaHR`b@rGD9Gv3QpZlb zY@o3ps_351AeeJLK#FR=?(cN_(Yjs;>1=kru{zU3KY2Ob^y4-`7y5n`CA#l`&6Di= z{fm8X>9aYhKks{A%R;YL)gIEb_&pE5ji;gJV`>lIc>9Lw#Ba5jM^vLv)1CC(aYW?k zi+)~G6zQpVLMOB&I`lI;tlP{cezq~wQ(xnYX^SE0raFI~QD{$n&(=6gqx;St`64CQ zW{#<6{B}-HJ>SmB_wwQhHGT8|xa9Tmn>ktvtPiwd4&tdTR`D(Jf&*yO*!_%Z{QtoV zRNC~s;OAiQBr>7XMh}Vx!c3N|U&a_SB@rrPR0MH-A^N1>lR;2Ld|IjqOmt#jzd_{eUVYZZjiF@4i<5Ds2hWssGo5>&s$N`6$DPyF%e>z<1n5FQCeFnbz2;54uNwWA2GczOSF!W$?!fGUi2V2PKfwPI{xNbINVktfyN6WG@V1 z-2+}MVVS%3_npx|rmsxCoW4R!AWBWe0WZVP6R3iw$M9lNx`Np0BTCW&-dW(^PXS|; zUP=Er1-z@ke-b!}(_6`wKHWLX&>aFE#jJceHa;3!TKoLMS;cFp$JeaCCWNa;{VeV# zer~v%*d5gD_!;hCd9szKJERc8v*n~uj{uuI{HcX-YeRRaAhua`9fvY6^%u!BhN%j7 z?wzC>**us;4}jO#4X>GHPtfak%H{=s@9)#dsev^SnM}M&C1XRuCZlev+_ydy&zsZu zF-DhS)~{n%4`zLwa*xwJ`1lk9DWd~pY%pGv-3>qX`AWJ25xU{K3lyItG7IgC^__jW z6KAP82GV8v&Ma$VQ)cIj;*POos6Jy0C9^}Z19jr>@&9baVQs-G*fJj|wn-c@Wc@sm zSX*d197LlagrT>%-U-)uo{-AgkZS<4dBtkYtD9UVE#6?st)kvWC+3t{hIZCYBL^|T zAFRCC#Z$-H)O~;e(a3V%2M09p27um`yd?k!bKqbTIFtj2n!tHEa2`O-;YWb)H`M3s z3{)#Q>*@wPhku#R)tAL?K3MeBF-zXg9NWcikvTA0T4D}O*;kqivoRRxmBL?(Y;#EN zi0$r3@}rh7CMMtc*I}GIV^S3(xphA4Z0mefP1gBRHCg8qVG_PKI51&NY5>Tr1fi+$i;SIV8%g4+9GIw zI*;&Z(eqf*^A~w+9VT0|Z+U)mHJKJ4M&k{26>}(qvZv)Kt4`?I+w@eU!KGezqB&3W z{cYu?(;rB2Lp}#T#s}9VYtpBYSGC^j;feS9{UzD#9z*(j{T_BZE|w)*nOr3&q?%cl z)2EYXRG*pUnfY5EZ!6%t-=R%>n{wMrRdo!n61OMha$zQa3A%MTs+q1RM?G=6tEMea z(5;u(Ojj&#N;*jJ734*=ZEP*!dza?MiEDw5q14!&{@3HD%jw99S(?k7T;mWGy8Y!8 z*|?4tnY2c4AW&B))it~TudL$6GidxtvHREu-sl6bH^^ZMd?ilfe{u9MIe$mvF3i!1 z&CgzR)LVBMVdq8`X>MczH7#-1$mZMAejde7Ue85FyOdL-Os8i25_Z0iZB6h@O}B^h zlg-UGg!;8F7`cK+&@&;8nZVWMZO4}!zDaAPiR(ye0&K;-h4u}oJzck#Jd+l5hx221Fm$tT zryfv>*d0Wc%%3OfQ;@z>EGoFp$2f$nOL{u9?&g(fb$8A8cc22UT;-~Yu`E)3Mo0Qr z%hxbYmvN+_uXFQU%vNU9I`tf9b#zf@D)!M4H>0>Cgy;2ys?B63Kh!xT+-Ka$OX8rP z{!69c@wK1H6lNH!)=Z&qC9CuqxD~|$Vo6a6KUrP%q@l;4)AwSMu9anmZv56QVqT<6 z_oEoQ)4L;15AhY-jgM~_04vSCDDimB&EJeJ`S$U=9Jj1@a`bN8i)P0f&RW)t*Xoj&d+?lr}D*!&He+4#)d$ya?<=(1${=J5!D@hO2pgI?^CF<*Pw-e-oTQf*ccdu$f;j$*-Duq|FSC;ctUJ2_06&+{@4%@HNa?3<^W57tk?*- zt5%usmq-MwHT>YsvT^IF{b1v{jBl}zbgdkn#y?rEW!9WpbcZKs)Q3pCT2>GZnX zE(It&({H?Vx$BDHnl-^T5CnViH_`kx=I(1Wa5r}#uvHqblGf@){Pg#=)Zv-iQitmc zm^AU*0@|-EMCLrJxLjVP7SavfG*BDx-E=7jS0FdMZ~D|_lL}eYC}G=F*56_^?QETQsu+9rOGWs{YiD(4aNHRx7L5lKIqeGA;tn&V!rs9fe?Z16S3bgwOVI&v2M zx&pMzRw6m^ZMpH`idanYTB+uTc_@XxZ!(yzx#52(BxYv|{zpIAUmAZL9+R*VH!WSd z1p3P!18s@*bz$f!xu}%^n?@ z)G?B+G0%zyFAr-ruP2b6kDR6m&hB`SK_q^veKG;rt;Xl8vpw1c`O+H=Rqr;7+<1;# zqeRlKNZj;iQ0}HpemGTre-mI#^NDX7T~bBbF$n)~Ds%6}x6Y{niT*U;)A%%MWlFyT zp_=D-^N>Ro(xs5wEM!R@@^ppFRmi(7q|$gVO@zrp>TTg3gXTg3g&C&2wKg^Lcg{v71%`?ur{qk5Slt&|>)hge&UbHp~pIr53) zxMwxv><&KI1Bl+VTpQvnnkQ#W>9yJcKV1yN{463*(&I(P+!hmccwEIDe&olMB5<_i z1Y&e|>~ksswkAZY5!Ziea*xEX^_)XPX*#wY&O>l&2|Bg_&!O^Q2|2c)zPujAUA{%? z@4$_eU{$Z*EKfPLI@Yg?>eq}MlL-!E$?`gn&y0!k1(Xet?rhp5-28!6*4W*ywgi&; zm8i_m3VLcjamTakZ$E?SLo9K%y!Kk(*TbOwb_1H(gdjEt+|g>jY$Hy*{H=8ASwU z7N&W5bZbsB=#|mwO^OljQuh4Ov+1!PPV<<1D{o@B$I;s8_`CeFCPp24+b2Drl8xfi zsk8CZ%tmqRbq@7fL%o)_a6zsi6O!4v@P*OPo}r9HcY;Sh@wBD&UQu_KG1{Ji-cD}D zFh)y@SXNoX=yKxrmdG9q&s##&MoV;ejdpZqT)ADb=I#)@(CqwKW?-WZUbCR+5XpDyUUPdo(;;pt9XiCS#^WNUBbQu5GqOKsgW;+B19_}ys=mh%s zZZEOqc*yg;fyT*DUbX>OawjWHeJNi3GQJO6y_nf%#^phao1S9nO%>J3tDSaA&7i}y zr||3k*lVX8s)~BzPV*_HuS(K8CGkP=1y)+JaZE`btE{9X%{I>bKEuN3DcRZKTjB|+ z*Mo_@-8gq1MQ|a^3sh3LX%5Q(sm0qY0}g86R%81_BgX4rQ&uLtreyv5G1}o5HvUc0 z)5I%8=k0~PRIN0B#lhne1C#aRwh21gTQZlOiOOc0(M27V^j+Z8VZS_JgWLTZVJCf` zFs*a3!%9-q$)uLc-a$}mX;2S{x;oP@6S2#FJJWCF$-k}SgnrFJ_1l?#ClCFuLRn#H zf2U+)^Rp(u*!m}Nr>N_PsOaWyinJarP8`1PcqZ~xF{c*Zm+CvRsqtM@Fl>BYgf}g-q)xvjoM^>;oVramuRMDoU5 z;?gB2ChA?iq$z|w2nLx<7dOrXtw}-R3H|aG*z-jUwyw|~-N%(T8~G@o7x*YXOI-}I zca{c0_bd{P&aA9gG!|gSR0m-YiZ;7cxn1xp^uwJZcAtp&&o$)#OU7NZ@l!R1uU?YA z9JgQ0^)U&_*I2|%ckQ55-J45 z?lpz*PQq`VF1$6`Y@$)%*F%Bk0Pu|fFb9Bd27oyLd@BIVVe7c>(}usIuLrRkPM#`C zN0$w0`5glxjy5pblm7$wM{Pgl|7c)On+Co%gPq2A{Q3OE?s_U)9Y4d|YK1D-3|k9T zd8yQzqFU!Rp4VB>Q)2gebBxFCCUfk}O~5!%%rLs&Xdnz7-fRvG9sbK4!O(#dyQ-4} zB&}DUL81D~Z5y&adHg@_m82`$1H7uu|LQjXYvm_f%l9RGf2}zxS--bULv!5igiD?r z=ovrDO4Q(~*bv(N1m6giR86t_(|u=GQZ>fzXd^@|s7B-)^$k)%WryehNcRiPm#(yG)v zG~*j8n}w~vM_P9{Bf6Be2wxbFReod?a;Z$~` zj>h@V30n$L_C<^5FZ4%*BKP!R#`$^)3%aAGWz_e=RGUGs0Nb1)JfkI*I>B|BDl~G3 zwFIe0@}Ot71gWj^pu<~&)NXmuzAb1d$oIl79qtHHvBi3%5_`k3j8_>=T}yrS*R+jE zjA5l$kOxpBo{Vf0qaEjIHZjt1o?sKB9B0%@8ug*OImB)iw_PEaR^BDPh2&!^H1Vvb@V|!tBls8F`3(OWA^M8XZOq&+=gTIM-G2NIzVG8- zE2?|=AO8eDv6B>89ly8WmTEG<-&SznRB)>+L!6BT_k{(wXA+iw{drkry@cc;N5t+< z6LK7dupWJZsOd*r5tU1Iurd9oksZ%=Fvo_@Q#knV8S6;8jl8!fzhv=NT=3 z|GPJ8|G(sOs%(+ZodB*GpBrc%IJ=ryIg@x+3%jun2>k1)mLThn5QGBMWZX?*2$_R1 z_2U3Ahw3JMRxaySo~kW>Dbu(1AL4{>uuYV|{a^Xik@P;N_N_yqpEpOKP@7ENJ`Hu= zPqZR+enEaa>vxr0Vv5Sr`FKgHY@Lq;%{_=uoa-l{y5<1z(*Q6BfS(0`IRN}T0L%g4 z;Q%lPfL{cFIn;*lp^{Is4MUrWd8v)iG(?AH|GV+k|9+X@+7ND_g=r5Dx4N&IW53va z%^bVO?i=P9$z&WX-Rq1PEnd&RNs!Tq#dhhVs!iLZo7gd~p+DQZalA+{CxfQIt8Ye5HH~3`7|h zyvo1eRROs-m)-ua*k;44|(MglE7dLT_`q=1R4eck_{4xv3~*m;O|7_Xf8B}8rV z*HyCEmk*7SKEVpMpG1?t66wDvp3rxAte-jP*A!ihVya7v#z^_KqRZyk8~`2(0CNEN zO#qk!z;6S<8~}b70OkPj`v5QpfIkF)IRN}I0L%g4(Eu<93y#OYnnwirZTt--v@Uez zB31b<#F=ic0s8OPCg zm0+#heO$W>tZ|<5F63ahxsJ}wqL|g`?})-_2x<3FBaWUHxz~i83{TyHZ>3vWf0=xx zJ|FX@XOU9xYn3tE_#yXl_=04B1wW{c{Rm?{4vFHmo$McWvHxeNNOPDu+%2IRJc>gN zuRWFpPXX(S`>$pMb!~rVU#FW3IeI@gxUZ|DY&)>=*P;HQ61eK+12c&D6mquShBzq^EJ>$(3h?#FhE)v`>p*!jR>t2U7S z2HdH{98YYYgV?ab!|REyHWRZhGpL5k)Mk2Kp|;Km&MM*`_?0o2-uQU~J1m}&Bu%>W z6=rCt!m^f{#jf_Cd$ZyiA52_Mb3QH_dBw$uNpZ_rT+bx~ot-)3z}x*>2=J9Z2fkvC z1F&J2BU3br@%$x3Fb9CY27oyL{4D@7hg&9MQQE(U5OV-{JOIoA;2!~C4gmiQ0CNEN zR{)p;0GkDV(lQ4Crb`bn2Y}51U=9HP2>^2dV6EwsnB$n(tTo-<;2pbRe%GzlWV{nD zd;d>kG^{hAZ}hxyEluc^(b?`T8uhpF(>M>k4yVPOQ~gnlFV}bE##xbN&jhAppS$Nw~0*B9><9jIj2WD}&r1e@cj1JZb z+Ij;8(Br0!dw`&~i|YOkAF_+dh|BvimEhhEdU(4c><`+6^%TNn-Xjj{`cqq8T{9eox84DU$bQ6>u!p09ObWtsMephG&<|ErznSNec$}3bG5$n z(rDGgZX-Qat?#fj+IafoHxA0pDXshNzxZp5;QIA$_YRF%2*{J0t1lt0Jap&qc;vBwqU-if~jda0oAYJ=9F@7~=GI?mnM z4tk1vPdjLZ+Xv~XSVDb)dvANJPP^l&sf<@WUk@UW8J)LUs%FGK-}c4^xdPY|@)_qk zmUq=FBmmXddOF$8Y@Y_2aoqXPo{zG(kFwCrOp8%2$fL}$gyLamxJH1MDwf{cjBO8& ze-@6OFQfhTgI&U^G3pCRmg~Ng=tAQE#dw1kW8%eQ1L=C=R4dn?%>_{6D43SY;^&CU zz|HB42qLSL6Tbx6(IN}nr84LP2hoy@Lca>tQCbV_OFBCW7vYeZA72%_D{KJTX-ZgO zd*0nepJd{)hL{xje}f=R_SCCgcQ?Z|AX~#57lEjaL4PjoZpuKm$n!KgARP751Ggqw z*p!qsbGBIFPIWzVYq9)-yhuuTc*PpTIF6sb>hAZh<<<7cjg#qQZ;$%C}SOWa>LCg$M4 zsGut8!_?r~nQZq?s$Bp)t}2~q7|;S0fTlGt2Y`+MFb9Cn05Ast40(MLa{#CYfH?qk z2Y@*M^aOx80Q3fcIRMNI0CNDC6#(V{FgpOu0RU5DkB2z`^aX%90I=lrG0XvAP5_t# z0H()2hB*KX1b{gJ37k^nFVfTaOo4glK+fH?r{5CG-?uq*(~0bn=)%(0xijyx@Aw2Gat{po_8 zpIb=br1z-;6#S>R`Oj?gpJo2J>)g7wfOFgYFKqLl*XF;V&3|E=|3z*7i`)E{w)t;1 zKZsq{7O4KWt;ycZT_p;{8zX6uW9pN+vdN%&3{9i|Hd}|Yufy;YxCdK=D$UL z&zH(PRvXC%1bx%d+WRQzo7_Fi@;Pr4Nm}$Q$ArkSYY=uVvUICr<=AF~_0vW;t{EYn zHp21E28h6>}G^Zri+lz?yEyYQ(-ZmV<`|4+?Gzj`e}(ZAyVv43xO?d zvbGuFlxZWJ*o<(-v=N@ujPU$vBk0QLObj_&5sq(f_UASuOiUZWE(r5_zjLN5qG(Sr z0d4%v`}hsE={DAYs5 z>-6z#G$$^n5{u{cFMS;9mHx%@GcJK)f2g;`lWI~r72c|3e&xuhMKuPJxSVW!)J*-N zQ<+39`>4h{5|Kj*0TuxFxDkrNHRSGDgW-VY0nfI4kTL8tLqS|@AxK{PJ8gEWqPG&yWOw%U? z6uEZ$sK&MvyQ93Ry$mP17t|IgYLB~FU22RyaXIPvTsdVc1h%Vfr>MO?s~q}pEXJt@0-)#WT@-cF44GI&C7 zeHi1rNnHI>;hsbXq#N)puW*iqky-V=h)9Vs}T#jmH~?ITc{p zaq7f9uaI4(CA)GnyK+l*J{l)OI18fs`ZEoOM7zBx*xB&qMgqVb0O|o?4geS=dCABe0LB8q8~|1XfH?r{902BcEKZQ8r$`zfQNN*+yUV_1 z_dfg1b04tpfcucXejZC~?dEkOi)+n&f9p;3Ix=u_pU`}^-g~fiAa!nLbg~9d9&~px z=wcF#R0pN^m_?h~n!){q7-5A#GdiUhsV-8BLSw!)%mHB605AuD-2%WI0Co=ma{$;Q z0L%ek&j2t7fV~319QVW}?xxNfFmd;TO{7bOzTh2Y_kcOd?o0ZbE=fi;S$i$bE*XHf zTiWZ^miBs(q`3)2Yg2w+LBURv6Y8xS)G zfc*l%902wY0CNC1AOOq(;J^Sd2Y^)pU=GZgn#5Ofzf$#B{XSw}i14@GQFgyK$AJ4| z_WhH-Vf+^8aoHB=@zfUb`6a1yqC2f6=r_fn(_4amT?{&-CFpm>AZx;{k9i@WvHu5s z*?Q2|+X2isU4okb;?Pp&0B}hFm;=D20bmZl2mA}p`b{_zk?xR-@xGU!}@>;w{dMb*(4VUA#>rpn~*j$;-L4(F=1PpIfh&zaJ z*TX{s8#Q}Xcs8h#ULhy@vmJwuCxi4_Id{!mL=x_z(KA83npEYZ{Y*rUUNY3~q2_o5 zh|#VJ503vv%r)Q9eRtcAZjRuKDQUyTRs?H8kZZ<3P$3R>Tknm!(w7RW9uVwFuac8j ziFz{xXV!Y-^ko9hV*jo;y&5MULvWTyu(|Ol1ZM{Xd&0X$y}Ck#to<%vj-a*R6|D&N zHCs?2)`HCYO!d}=ef8bhLbNtyqq5((oZpQ^v{WmZ*jQo9@mt#b&YG&vWAfSTrkcKX zOsuJ{Y{jI%*;ER#rs^;2wI*w<0WcZxjWvjl-ZzoQ1i>*E3M;jt7EA`3m~in0IruiI zxDP^NvBR*1ix&FkZys2O(m#7<kze0{dnE{ol18b^KwO)f-XIKlHg@MZ{eztrTYLHzd(gc~v94h)z_o zA(?)w)5@pmiN0P|iJd^EV*)R2?f!u#!6?Kt0ULVO(+lgn)E4@lUKG_96`8=|+G16E zNo~n!ccr#CNxw^#POdTneCvK5G!}x!!ge%_3a@HKW3Wku3h`9PK&$vpUkVyaeW&v( z;`T)jutRMJp|PyCOla)TL}O_ajisQmG(#h?;cqXet;b9-mSKuA7_JSEj_U#z>sTx= zXY?JRj$%AAPgA>5L9FlP-Kx^zy(mj z4O9eFKon$kWl=y;)aTJhMMW9^-`}ZQw{Lek0ezPD&1a_XsZ-mjs#B*w;!R-Qz->cwzjx%lXA zRFPq4C6|b4i;v27$uM&QM%8GT{(wP4YzU);HVZR1ggI;+Mw@&VX7vz8TkM7z=nSZe z_PD9F@<4k)U6-NC0mam0EmOioI*c}HbROF2&Z zVJ5Dwna!s&PUiAK6~=-WDj2knzJ0L89w?PiCNEh!4cZsK8mpryEUxGyrm z`EBIZWi-8wBt#M)BcuuNr7VEGx7FJ|qqof6cOyR|!8Apbl7p{E7cvx4YhE4h%UQ(k zu0-`zaNm_erIoJDdwK?TO1I&A+*b&h>hC@7PC2~R&)UJAHbv_3cSYN7cX!t^T5pO9 zrWGzkO_#lqW9D!W(UxhX!CJbf^@5%{ z>tJ2*7pjbb8x>~r4n!upUUz%R-hAZVx)H|UC|V)DDD?CtA0s8LW^ML!p|vyL-PgZl z$4(qa^M5-355-3V|H>4W`&+*yN6`hNk=^+zwk~LEy-{5WYmrh_ zuW50gLStY*Y7TaKL3mwak(MYr^>pd;?5{lrPSav zC-~f`E>~$!_Y|>nsIDa6!L|fSdEAH-?Dl2E)3hJI)s=mdtt!xeVv2DGSI_F-+yK)TIYK)q3-H!8XyqIG_Yt~u1w2-Sv=Fv{` zINCgo4!u(Ijp3aNj~*RFj=P)+AHHdYq#>8!Q%A`aU>3}53tNjJx(#M!_~}cpgh^tG(NS_|;8CzH z)#r9+opY$<&IC1$11&J5SAkpX&AiP2EcvsXh;{zvd==$jt})nfaQS$8ycq&sHawXQps+@(aB*0QQ^uh3n)n)PSa*t7oJ zRhPm9)n@(qc50@vOdnHmE(<@6WzxG)4P~0VKb71SDgXt{3Q$L0%I_4gMt@xaC!@ca zJi1b(Pbe=x{;$$MQT7wc!>cCE|LXDiUju4lc|B?V&HDSb%6~$hysk0-Nz=cv34dI# z^lu11jr{S6#`H_>Mo<%%ujFo$Km4(GVDJY1xEX&WffTB=(@aXURI)~%{v=6E%+sI3 zw}GcWEt)17Lq8*bXx?W6nvLft>$JC!Kv#pvGr`XjxwVeqA3&eH)EbwYwbQNSFmXR< z(vNQ=LY=@ZxzDD>oJckq#s7NOwZ8L{Chhb&(L5pVHj$^VCwokIP9l3gPloW8l;=z! z-!5EhJ?`IY8Y}0KP0D$P%K3%x)7Xc;h)1D5|1W{6BeQuI5HGApe*bLHA-}Aw8f7{r zoLyyO731z4RYi@P|bwh zzbZN=;*oDoO5?aXo|H%KZ&C-}Qu-6hrE%$(+_$X`>comQE23y3S^By|hRL9RPtR-C zukN3;&K|(GZcLoC&YJ0A4^Eo?cN)_#xrabaJmyZCf3xy^chdaFwb`WUH{*%#O`870 z+9d2v`qi^ef=#>`r@Oa(F_Fj(>NYkjZVq+vcel#+>l&pme=Ex z=4IN^zm-?BF*+-f6)r7TlQFs(uRk#!ySMt@V+gH=*xGB5lI?;y5``az7?Qoi0{#KLORCG5!=(qh|TE za1G&%VNv?CVWF!|=`Xowh%~LVs&C&g)#xg{5tcT6s#$wHOM2tlGShdOl-CBP|DE<| z#>>x%z6tfcpW*w)^}Y8sY44v;TK?xP{hYp6axZ{tklQvCGnQvFwYn*5^PxmCrdfNu zD0(I=@BeLkG#lrBA^IlNeSSH+xlJ*~(PM7QX@Y zKc&;Mdp2&(;*s%+l!pJ?aqtwj4I9_U{wDfv2tSSE z^s9JGL)Xa$R0$nZk3W@j+13mzm{Ec|5Sjj=Hgn%k1$-Ny#m@ zmRyH${vw-fnXFUkFoEAM(@W~~wC3feOtM#9oA0~KzGC^k?1h>|4n`L3-pHb((jFV$ z7N&HKlmhYis?#IV;nJx^n5W2k^*;G2kO3gR84w0=2=@^X$(7pQ#qoVPJ}w7AzZHTS z0OH#LVE~Bx1Hu3h4+Mk(ARY_|13-KyAPfNUP(T=<^Bvb?JHi%~>`SzdTlO?!D#2Aq zXK3rHZ|cZY`e@XSQ6d*Zs|4-IRZ!jA>W;=Bc!oPhX2CMF?e^{Ku9k0+ef?)h(c9~7 z5M0L+I5`r-vW2U=;~Ju;5fr_`c_CS$bhnt{F2;(j$*)MQx1IZh{4Gi4N-uVOW1gpH^402rQc!l~4JO0TR zz(fglaAm89sW64Lw}&Kaa<)v2q1 ze0BCwoqfJKF-E~E&{t<_=BnzfGjpA&I=iRW*XPVopFI=QXE!C)(YUlfT<5D4%iGN# zq)Mqc>uL+O;e`*{KRKq4S6#S*RQ)empN_|a4mC$Eklps{O(fFi2z9oLlZr|v7bQ8C z6lv(yIlbXM`>H>covvLXG5=Te534g@AD#)!tZC4_i|X$)N$d@S2Hgo@`g53$*u70$ zE9=co$t1y_2U7LAiGwXFr9LwXO7q&IrAc1Io{PQ6++h}e?d_R6qWMn*yT3S|zh=$8 z_i`9*iuL7!Gs}t8;c9>=UtepR4O3 zQmZam3b&4YSpizrOYD9Kd$nxjdPDXlzfm{&I^9H;#hAGh{{4+!W@qaA-T{oAdiAQ@ zL!G48QOLL~uI%5bM&2eK@aCNRTWFW!dp>aYLcv^T~k-SqTBOAj~rn{`u*yMbEbhLCpm z34m1{ab&7M^{)`iB?;~X5shge?AI#V^*+wFe1EDvo$P(1cf-@+O{Yg8KmSgMC#8_w zNc#29e<;21#z%PDNV-M>zMyjYp_mIFhV$i=#7iYNM}*qV)Gw4Rj#?)!TfvtNCGNLr z>E2yeI^EZ_rf5>rWMz9#u59A0LUNNz0`UsJI^kRV*eP*j+(UZ*<*t z$2EjetF<3J1O=o1U8u(!>W$&zG0W~C-$EVkL{V}+UFFn5D!lTqT}}3=liLT{7^UdX z3!e_@9;{+Cwn%7?6EE~EsrF`kZD{ZO8(xichvb)#eAD-=3jU3x@IFZGT`PODk-Uml zpT@y0*4xX)LTi8W4>gu}Mz+g4@~xW4gi650s^!L*ix*~)Z>D4EE ziiOtX@77#3d$L$}$VX3#uO~^V!&TJ^H&eJ%Tl*Jbh^wkCEa_iTnNr{)q5W~STv1Gl zn9K$q`JYU;luH-hib~c`CNB9KrMHcAtu9XTtmVtqzocb}f7qpv&@+m|C)WEGQr~1y zol?={B$?nfhdPIN!W>j!rlSnZAt5*de0?})+e8O*|M}IThiHtp6Y{Wlo~`}vah~I zBhor*7mZv9)#APi8KH%`T9$Og#gh9lxX!lZCO*9_t-PDo;yy(Qb;4s#2KQIGfIkhM zVLTQ6Gom)h%v&+~XYjw7_#7i^Yps5UP|1yQ(&^Oa-Y^ZH_;1o+nbv49lAu9%y?&f} zD0}(vBYJcG`62}74<#^r;^Y=cms+FiR<{iJ>aAc0JM~^>8Ku0n($iM`6qF{P6@ptN z50-rN)rzd-=7NRKMr|$0=kV4o3SLW^*4ARN2B#*USG@M#wz`*+y4%wC`pVJ!E(kKT z5Pe%?e2wn9$N4=Kt3G4?3Li zOZa~fE%G`3PX*SWbQ_ZPV+@Z|z^utP$Wu~Q@XX&081xs|&`qu`8A-n8>6C-;(YI`{=WMhq0SW&`R$T`e`Z z5BpPp-?Go=DUFjn2Gr08{otSMpv3FCMH~Is>lPd*TfaGH%L8W0u zYFz!6uus&mPa61bVI@4$uo5kC^?qShlO}swt(}*vj!^55H!_}e<=k7|h-970am$eE zwrDVGM)F&%;8C(nZ&CR*0&s-Ok;oLSz1#+d`-D`NPmvR?#8B$b`EapmTV)ouvU5$IN+gDz^SL8p}>BAoh zyu-j}hdGkqE&Tt4g+J3NUy75Ol!C?(5oudSyp-8_S~MKW@(RgzSy|jQKC|oX)0dK9 z`j=OITSvYp8kVa8^OFVP)RM^q)cw>-H)HyPKq^(yAbaq?T*|Rt*TDm8LFm~uNp>XYv7Zc zRE_GyWWuh|>c`l%7s!Xj(axuMUe{}=vu+Ne;GU@CyR~}b#2W9OPTcosD=p6^AAzXy zvkN+MBn67z+k~RfWv$;u(X43NIyJtNtVnsekeugBJG+jVXXLn--dES|+}S@hnyq$M zA3l||rbB;}tT$Y7evM zY>{U@^q|^9lM9JRT8f;#wWWE=W8~EB`;z#oPB+N&6;hCkYQ$H(rTLn1RqVOySN;vS zdalYn(drqf@!8?PSAHO_@L+Q~3a4XlUdu>tZhyLAWNCbI*t6q|rSS#hNHKKW2 z2Hq-1JMJBLYenR43hO~fMf-lq!>W@v6ZiS%vRZvj@-cmbcRF=)sMSfqzo?7*Ew~rH zKwn}35eyT#-d|Y$ysfDCR>N;1Kvr690P$eOpPP$(RksnFv z%)Nq|z3+U`(psGz3T-`}mqkb;FN>1o`=sT0`B5Xu=TEaehI3)c%XdxKZ0TBJbDmQD z9P{tcu)XSFVj6rsB#)=nkY}T&^z3`6uah-nmp%OX6AEv8EC#S*_S1kcz~(BuK>Heq z1Kw_%7A|gfn+zdF{2@^WMCqZ9!;E&B&Zc!?m=h4`V}e+y)2I3*zV3^JvkT>a7Sb``=(~;SMgIS5EDkbMJm+b^C}m0@PfYEsSbqU_E$rk8@oG+ zjdrWghTLO*mv`#p&pGJ%0C%yt)+9mWbP)ASql)oSI}2&wasKAhI((7hR^e9GnG z{PWRqxyo579>IZknVLh-i=n;^0P%}}FaX3a1HyoGztoLtF(w+89;cOVo%uqhqE5BU zNPa*CEL$SFBG!>jZP-M?W61mt*}HG?Bd=9b8Ar^^+k7nBmZ`4W+y{0W@+}}tHs)L1 zg&()re2UAoip=cH-KT^QeMRoUPZzbr+FEaP>~i0K#3!1k^m&;g8vWKKn|C!*wYGr@ zIu)kffLs#0d%&*?achrA@H|(D_9Lx+rBz7wRB3u>YS_(RyFIls;?V;x`62aC)|!gx zrtKa-wbJi_zGCtt0wh1?r=v8r!j1RT_Di9*4VbfHVF&y&XT>6YXuY2hSY@xyQqJF1 z@vRLo^6@%NO`CPo;Lg=&B)>5=Ja#`~^_04rSkuWxot}$oGFm!R$67(}X|?&*EUN=6 zJ{8yDx`~vIAf~jrpQ`+{`D2io&9@4+Y&qMv#nXh~Uhw1@tAhm!{I~_yd`*wzYLB{$ zve`Kb-WbC(<=x{83rbckm1!%6s2!^*$BsmKc#&Vn|2BbDrN0VQY5<5|2ZRA2UJeKY zUXE8h3q4~Bj@GY0eB_l$qk{VZiR-qxAKACn{Y2m4b6+>A2>L_F&Hxbq6%Yo1_+vmA z0OC&pVE~9f2ZR9+b!ugQs4dwXt<+QQygDHq)r*Ob`) z1ws1k!2Doe@)g_ueOUsn=)S5u3KsP<74J({=c8AjpCfeQQpeBb7)l*4$T2KOU-fYv zgYU0P&4iH2@r!BWxyDZ^4Yt7qTMhUPB`r@a+YMciTVPu4NPnlEfZ(6?tv2ofwPJY! zL*$R%*3;^+u zfG|L7I5dI%F)I=-uKr9tWtBkR=dDtrc*Wm94fY?rQeXFbtEuYG6;>p~)#nAi2FS`e zi@Ms77+w^&7W&;k6xP;#N6Be*)kn4z!)(9BwIhl;ll($>-kkwCBl)FwsE~aL533R5 zoHR!AEAw-$HZCn!kmT1Ef;_dSa{}Lbeprs?%)@68-dDAICuZ02gFDUqUon5(STQ)K z-ha&e|Faf9T&OjUpUxrg0prDjXNdyrsO%g0N_|TfmDaZe1is4p)}P6ac%rRY%OFqaC5u*5C^4=M;R+C%j0J3Z^R16gOlQjsJ;7|u(?DSIThcP2 zr#%HkhF=e5GT-Avvg&Es75fk71vg6~t%Z)#!lzBfj4zjQ9a}LI*Om28&z%aVy6R7O ztIyA`!=7TkuKukw`|8t_JfrWSC;7Vix6({_K`>R=rN^Q8MQeB&+0t)&%`9(A3aCuw zpsp?dCmqqINd_RvU^S000K}AlFaSgx5C(uK1cU*f&W*4g*4ta$H2b!?Zu>IAa@`;- zP283c*8mW$0bu}$Vn7%Gq7)DYfM^Q{13r1Jf zl~Ow=P&>~9t!{M>V3XqkE>fFO>Fp}HjhP_^E5A*E-I@v@`Sp%1hsHys1s|6L#|aYVq>IQ=*xM{p4TJzk730$LZgxdV9w##!SeCPu-MDd2MUg8=3Vnt zhJy=>LuTLA=qWlU^i=W2d1mzZ@xjKiX;I-k)l7N;=`PF*Q6y|9PyIqV;Y8um?LKR} zlL-&;_(4FQ*Vwzc-z8ZH{5n${e{KVCN z3XEC>PT_YfHn1SD#lTj9MFUF$+YGer<90xkB}ID!&l!&=Ww1;Vc%ezX1x76u9j`N$ zhvH-^pGO~JG`CwIXY_Mo;%=D+#ic&ig&$W`=6(Ckw=-_*_rCUyRk@fVLR%@MCE_TZ}a@{`P{!_Um{eRcT$3_q)e?@wmSF>GVU3)JJfDyPw*c1vk-%nT4vZuGI50ja>wKY&F%F%*o>{D1aA-80$`Nb#v9+hOuOZW~` z7u7Y$tIX-2u{8mTtz&EH(=xWUNU&E_S)ZGd4C3?*(VdrEBiE5k$!+09n@YK$-v12w zSBHq**;ZUtTnC`)_;Fhj#c`cvrL(99sLHmANDfvRvlMpSreQ@_7FLGY7IwHv*fzH{ z$>-&$*7_^P=T+XKD3=STYjkxyLs3o^@u~$IlypsNrk;ytocu@kuFDP|L4ysSPPau%q~HwDM^a%_YzZM~U-6!l607hUq>M){$C#FY*0DY$aZ zFWCaWAv+e}wgjZ1w&GJAV0=yH^E2f0(jL@8<+U}w%4-`1ZW-H_4|(a+kk`DrM7JYA zK2fcu{EajhDb4NiRGK?jV%4Kij>dKbZt+R(1V~#_f`h9&19Ne`o5{Esn|MnD?ShA4 z-3pp=Y*(Dc zcuGJFmxd@M`zuyNPdUIx*Ra%rW#WN+LlyKz)q`+y-@5s>B?l{P)K*BA2(b0PD(%e> z*p#;95P}WWWt@^mE3}dKq252Aw_nX7i#N-!&23AIiXJJpEFN0Wy9e+Ccf+ zbB87e--0hPqRe~g*V6W!)9MQ=jedS|8=_*d;t)wgY@M-V!5t0?L+t|hu)kGcYnn7B zK1blj64Qie>odoZPQA@z-0#P!LT#As?|0wO5FC|k=;|$v{DoLpUTEygS4HAGqoG#o?41fAn;v<5!m-zc8R&(E7}2WStMyOWTT#uwZv7hhgN~RbU>F@(CX)S5%oJOu>(U!OtCKei{&`=Lf{;^#dZS3uL$XYqDLO*26sw zxdBme!(Fas>D8T&JIuM1BWW>8AUnn__^-7q!Qt<0fXDccowL0T?8j990CXALO8!p^ zf%&TWF5(9ovhdR9G^gH;ibA1F#S{yh+Nhq#ns6pfG=EDsu(hGhPObXtRm9!U_|Zl{ zW9JUnkNEMsZvJSDv2%x;m7(KwwDSX4F*%yrEZMK0^J&y><<9HMwwx5z;D#l&hBrT? zr6FgN-0b8SeC^?c?)DYO0@udBsqN`IQroeIt+|OkG+ACXWq2hSN09 z-kMBtZ?lJckUh0==s2>7hVjtg5$6UC@3B|BTqA&9D=in2cP9!J5x5Gl@FAF%4rG~&{(G18nN zzm5H`vqfg?&g8=@3R3Di+*yS321_iz_8l(cziivYPf z<|%$oD=c$94!DkA%WRBibG|naZ^ZHwLAm^XKw~q97MA?BZOOZ)wta&KmQ;5xNsgWW7OSep(~yV-QGHZQ3@&iVtJz#k`U@~~_Ta44dC zpITWP^2zrNJu7)k-{fU}$2NhGc%bagA|yR7`KZ3JJDWIt?g~RbdtfJ+bHKDEC(%6V zN_28F+S1^{(oma0-rCqlsZrIInybEFG%zZbb8{z(o7#n*7|l92I#9A)Yl~|kt2eJJ zf8ZcW-a1mkJ5DZuwD9)!u@RO*;2vwK*+WSDh z_6}kC7`qRG;=sW8`fD2kt*UHZf!*m90PftTHR$cRKNrnSE6c4zpqg>_-enGzAhxEb(?m|!~l1-bM!%Y)6HFhm#r1jzEh4*d!j%e0dwYFxy z6K$C*`wWOoE{8O%%Uv@o?TebEof-xC^vhAlOmCxoH}4f%1+@&#sLU#~B;Q0}_|9K; z9QCQV9r@8Ju-gd$-N@ewz7_OT-z!1kE+#i#1|*f9$iJabG=+WPfcv}74XFcp|Kv1= zAnc}T-GLmi?i*i)dSvQ9&XoiJcN zX9SeWbOv?2wd%Mbjj~M5TlyEZRic(zi;9)DIa4cbv!-Gg&H{w|+L@7MELJWQOEZ#h zs6>81lZf)O(4p$*X|cg#@|=~oko?@frQ{iXS-W%Bh}k4BrMsHkG~IRiW|TSk2{JA8 zU=l@<%{bg20o0L6W2_y}9%HwX#N3X6datlNywi#W>{a=JBi*r5J|)}ZSx7$Fffsik z0-QEl)%P}fqE}AO5M?ofarXJFWyOR2v#uVTHRtN;k05GHh`wk5Bb~VJDwA)Z*pm4U@(5 z`u{fd`K9{5u#?XlYVq>(hS!g`ZPWaOojku}sH9wP%*&+F?0pKV9V#)$?n zs+>-x!)Mi%e8AggD5vMsLh?S+!lBmnlU`m|5(@n(j-<~q6hgNj z$vd3J!<1@c^g?nrp@#~|#Y!}@|4`%ACx<+pLXS(+i``d6o9PA*D5l?wdq4iW__C_` ze5S1!;E4!giy&tGk*Vx#!a&-tZeWCgR@I0*bK|YaN$~Z7YJ5J3tdBc|3 z6n8E`7v9QB3x)|#=ay3J{0+lLw9bCME#qi@bkNCuEk*qFFm&xIdQE8w8>BXm`66B( zE6V!ahsc9v|KvQrog=5?+m@UUJTkn(@S2x4;KPPDsse{8;;rbtj zp0wOsMu%P&_6gUIF1oiSE7u~-ZYnoD<#uxAFji$+v2+D%gxBNb#N)y2i7`q2jWcn2LOD&deB?g)5eAQ8;yL-OpZzTT4DSt|e2U zd;#?>lAl%|w{n_3GcOw4*0<>eyi-lB8t3*ET4tDDIM|weN`*@GL-mUtp>vOh@~^A% zQ>%L8wqQHDZg--g48)c8w*KUNrBSPy#~2PT6g{Ftx`u3m_f(qcKD7Gu;gjlA{)`D3 zaI?sxXmXvE_23h!S-SN_!lJvSr`4o|Rusf1N>o=sNivv>(P}9rj*XkVppaH)5C*6Y_JZEenQUm#)WSN*R7P*+Eo5-2Rb+%iJT#{dpH0$Kgo2_QDr^<1EG?NA>k#nsT^2PYttcAk+X5 zy9I;+Aa)N313>H%5C%Z*^D0U-%#-Wx1p_(x{t|%um0UP}Eyv5L<2Q2rHg)_?j{lIu z#s{`XqP?wr&U|#{$t$T{v0==LtD~}QWtsa`~Vo=VN^71A?6;eGQ_1%FfcS1mjN z_YMIKs0;X-00U!!=gs->3y}%>tNENb68MmX1YPx;Dy$ zv8XFX1-ptFnp=mo7>u&i>jtB)xRejZ2DrD4_u$4hskaK)fX&3;=O> zKo|hxtpQ;Gh$8~R01$5r2m^i@#+O)H7k_{Kr`u44$hkLeaBn8JjiqrU8Lhu8nL@G% zZ?~Ss!Msh54N}KqIehAcWFt9z(uHIbIW|p$jmTk{&{Z~qDwSZX?ef^?2P^3 z7*{`nhx@42W}N`Nw1y}8k)iAcATO={Bi0NH?qiCq&bd9AB_An}?G7Q^qe46bNa$!G zj?M=1(~rUJ&Y;_jna{bIPa)Yw1=v+7k@jwUeGn2kHl%6*h~omn01(FqgaIJl5fBD| zcxONuFceF7l76GVybRt;RP3Env5y@4rjGsiRxeXEy(z`)FU;k_9FSrT2_rn;`;UTq7Rk{ z<9CH}8bHBM2nYjcij)%y?l2OTPIb7x!(9VC!{}8<;)vbR{8nE<-iViykb(gq-W?DI zfOtA?6 zsc~xIahy*uqt?RNL0Q|xWHHrUCQzS%XXZg^TRAufM1;Sx9WhvuO>C@=6 zSv9no0Vl=Fmx35v*gM1x7X%C&yzh+Awk28Qjs1stCQK+o2q&#X`uN|Jr`NGwHIOW; z%5te(IFzhAQ4YWEuqXAszv(DGRkv8e4y90{J_08O66}(%927(AD4W4kQQz z@MWUpVBn~SldK%+#KKP}FAwr4x3jjVQvZZOw>;#NDA$sBlAD}EkX+RzebU!nFP)BB zI+y22Nz-{3#nu$<_0#FBrE|sjbkvk-l1Bsf_vt9vzm({e;}aFvyk7ZMuaWif@hPye zTy(o3@~Hd?YdY6hSB*=;v(vyDGGVEwJUa51is;5s(A zHoqM<9(k1?-}>#fVx5>{kX-AhAe0sD*3L;P$_(Iry4|&OuO3%c>yT->aOCTzTiJ75 zqSs8CDC}KyQ`q84ICGaZt>^j*qn7%1!huH(T_&$~@)_iK_}cLqC_=7c5)5G)(oTIb zXR?%KpOL0KwN@Y3jZaw-CQsSZmrLdPTq+H_yl(di84*)KiM3S4@6}c0>s!(}_P!yP zavDBm%T#RGHfAsL=kBr|bjZtJb#sl%%YwKz0K`cFVZe&modz9VYIi&I;ClJ2nQtbQ z71E9IBdNSXj??8Bp5s0NrK~X&qjL+`SJvdEudG~m%gaWe_5Xx`6k&;I|8ELNONZIQ ze^)?e^rS^Rs_@1IXtxJqEGTv+SP&cX2x24ee?%&H-WxJV0k7u`U&>`( z#9UaibEB1+-kD6NS=`7FKYw8q6@jOO_y!Dd;h_lhdg6BsLxKt-s@eOrin&?UOI4zUOKrvo^Y9Pb_C|fSXcH}rF2)nPdgdqNP zxil|>7$5(%5Z?geqYV1xdILbL2nYi}oE{JcfLIw227ovtAPfL;rVvJypgteg$?@_JNlyrc5gc)1FpZl;P^FgB@G7suYWr?6_~ zVzz+Vb(lQx09#;>5u^Gb&F5w_Gqqar$%Yl-_lZW$2@HVfvjf5a5a$Gh0U+KV5C(ww zKtLD(;)4NU0Elw~!T?s~?@>uG2{6L@qcw7(=23VV&1AzzoHoFkt>Pc*ZG5h!eDfihO zXS0f~i!SO>W>nPgc4wYPNBI(g)J8)Tv)w&`yrJ<3Ki$I1*QUm4_@a9|>SpY25S~fS zlq{Va!MHKnQ~Ob}HY1I@F?WiceC$c}EbPtsusV!G*l$=^`P7HKEgx3XjG5es^urAfNiMx97u3aLz8e?^#&+99LhsZz*tI`|8Eu--wV;sGC%`(tc+l zdNAf*Y_&L zcmDTY-D2s+I{vC~#u(2M;kIb*;k3|d zqL7I0_wobL2m@3-`jZM>Ik+z9XAQSQ9q#jnTR-5`G2iL#Fy!168AfE*C(G{J=7oqC zAGRe=QJCaqJj8)4xR*Dtz9!QF%>PT_fN^-o$3f#qV4$z@Kb)7+n&cti^%s_CV!K~% zOc~@>vyJ$i+X&COjpn#|H9ccYk}w5L7T z2zK^hZGZN_4>fG~H$)0o^6DS~-QBB93p!XCBLRkU-JpjY-RyDSTGFrL>hHLL9&5$l z>S?fKb<`_~n?_lmzCVaZ!(dYLXc#qW9t}f5>OrwYRl>3SVsBezJ|_~XG2UDQWjjQlzuCLHoL|u!ren;bPNFBKETo&O z2Dr^)${**%c4mI38dJH4l^g@H>1o814a;Q+k3(}Zy>;Y+U}$rxK^X^1vCBA61Eufu z1hX;HbWR*L2wDfnVfs_bj)@Ihfpw3wED{|b)k7&C~j=+!i z$r1R`Mm6I{g5574t3w>N+VJNzfltZj)2U+@Id+wUwljqY+b^uP2C&fCK+3}etc>?! zychn+8}MMQ7f!X2jwwb;XKG~)?z~qzn7NoLh45_PZvt3KKOiOE(#)llo{#I9vS6zz z)6}v^r74R(;93I9W;2HGQCkKG2f(fs(E7*o_3zp^;I(9-q({#e_rgc|#oEDTsoqNR-ty#QG@W z{iad8;+Td~r{IES_lk%>Dwb1QbW<49FzPF&vDF1IeZXgxsfu+0J{1ZVJMnFu--9(e zKhx-EDuqut(<|#Dn|9#?WXfb+08=!40Cm&xw120Ty}EAG3V5g%CR4rZa`|+veCp+m z2L!ljxt4-@elD`_nF;4<(9!KZ#077O+-E|9wsXdjwltW!BS%|jaaz^-Bb?b?N;YMH z@{I-Fx}hxcIK`UlhC2O*R7aWcT{z28S~fY}QlF#FtYrcK6?#@r+2`27i#;9r9Lu#F zc^tSd$M!Twgj=or5_Y-rdzC$9FUh`~A6CI2;AlO!v*68-BWqfw0776y!FoK=$&2k&aQCYg6DpGr4)Ooqd-q*NI~zDj9L^Az9~-S)wS{9 zw*$OmVet4u)_?b4Z;53t?HgttxMl~TNf&PeacFTJ!sU8#Y6G#LzvLc~m=S~4HV35( z=nnYRtm=Yw(v*4M zlq+-UlAe{Up`=Y$<7?A`j65y-sX6q9X%|ns*clE#kotLLtt67K3aU3p30iblY6sJX zSwF4J1x2e-(7D}n6}04o+8|tXndq>x>6E5|&g-71py~dp4TD9ONfIBF^>_tct$Q^E zZTCixHk$jnYK{P&3RTe6yH{7xvM0dCdOuao1?36`1s&)fP|yw^)HDGUfB3pEs^zlJ?6-z|T=x;JUl|yp@g3 zcTfmI2k~LK>_K7I>RwC9PV>o{3X&5^J}3`tDd^hWYb)qDR2j!tH1s&`j zT)^PT8@@Lqm6A_NG!0cM-9yQz1j1vBkxRX<+jXzgl8gYuzEV!QomZqIf?Buy9^G)$ zuN1c0sKw;ZZiQqTDpI=4w>=_ccv1BU3cFHDlD6ev#KCf_%w&J*!P~IK2J&jvYHrVL zQMh~jYU&(!8{EV?@@Mtw8tB#n*dbZe{wj5WxTtKMx24Ks+B12Aq(-=OssK0!6w0VHH8(7eZhI zK)e_b27vfQKo|hxmjPh_h?fGwfUbcq(fRv;GyudO0>S`FdI#$Zgmhm{zj`;BwyC|n z>>H@;_JT{?-o`4$q3SiP0t}zPP5l2!g0U&vwy4nO)*%7dO*;LyjHA~h8$vKHB~QIs zVcQEqyd<27&GiSISgsI@>Xr*FiZ=O83kM`b$mqJx1t+v4PI zV(6g565RF<5l2?uQJW``$wGx7QBP-gwAvznwqaP$XIgY`^Kvu!9d5dQISBt?7qx*n z-)pHVT22LG_7@a{oN0b80E-o)=oY@@rR0O$y^~XXIEcebZ~ui2O(H{@w*0i)AC2%4 zlXHo;s)Z%CJFq}S6XHCk%7ax@G%xYCG@7rR59X~lS8F^j>IaP5RTpWJ=|ZZ@N}F5@ z>)1F^u54U1!O5LTIkBltJzf98n{%P&eOhg)^h|l=n-lQJHqo|cgflZOvV*lf0^Nx3 z9v5NZfK!LtmJ0W)zLJwqn5UUMK4~EGO3bwkHlCaK_8l%P+YT4368ML?VZ<(Gc(Wnb zal*majaj%8v*!*0>t=x3!d~-3z^T=*=cl!zHFxx7TAbz}qM*8vd`Yw-m@C7la;f{T z$tZNP7+b%M@lAtJL}%qPLAtI75u_Uci8tejb+71cwX)EJT1s8^qRwVrN2_&I)}#+y z55$Qh^SX{sNoCMlZQKj;vN{|gZXm?Mr~GQdFKP$7>Uc`GzOJ{6?<4GX(SvfNLWk(t zoA29m^rXx2l{M0}_=$<|OQozu<=06Y@}Cwh+``wX@?r%tI#PML12y)j8~T137}*Pj znL#K}+}g|{NQUQqnRAjMefc2Bh1H`qw+ZLi3ei&XWT!juPN9~yDIbA>cGuExrd>^~ z47Pi9qa~_-s~g=4bzJ)UvuaUI@T?ia(efwij+X0aujgdXSBs(z4i9~0O0+igyi2l> zM^F&!wyT0j13>&WAPfNUw}3DJy0y-|2j3|gZ))d=wh&$PgRSh_>b9}34PS02gWcAA zyzK9Z&5=w46DOa5i=*MK{C&vYGiJt}K z$pAx41)*CgZX`8~`D05wxk-Jf(#xee08cRjJe-8))K>Q;h47f>}*eA)@?G15qd`gaeQ^%*}c#|B%7V!@r<+T1uihbWeI6(et~2RPnz`dP4nt^ z2OG6VXTF67pL53fw4|hkxbROQVtqS3H*w!ubuxgZi-3sT5;DsTz09F%ek#Z~`(o=a zFG~~2QnCK+2g^f5JAPRz=I2d)eYuRbt)cB0lNE;&fm@xFRbl~f>?McQJPuV~ViksC zKRK*5?Q~Yxz?$?}6W&XipvCI3tJEBP|CxTHl6Ozh_jbBBSE2inGj1JI;JB-GOo8LB z;+O)*U8OM#{4(Xp#?B+j*#|EqM+vqlSa%4vXoch$!4{Q)=y)8%jIg51Mp(7UMwI*5 ztlVW@84I;tCnH%_D77SqlL8{nyC zxP8!VSG3lLd9T8J&ceuNMX}!d6nTH%yyfHeC5ahs%yOqH_QN?A<1wu9%=6cACNB!h z+dwC`6SZ7fqnO+Qz&$ZSA}hXti_aJNBwyl(7PRRi!&&ZaH7>~{cp>?+LZojuc0~(E z!;P?T5HOG#S=Y-FJGl_1=DrTU0%X$SYOd+*EF*8(xze2vt4D8UXpAv{a!9^!!8b483l=B&8WH{R#|}4NDjl!zv60_0)=nZMp<3lig43Wy zJW+_AB%ODtUbO#@hS1^G$TY#OmJ3LW#q$ogrjTDJ@l&fe6L+wXT&*N{Gv`|SR`13y zx!&N|E$5qj!W^y1P5PFjt`^%B+&8_E%omgqTD9F%Ouj*Uvz1+!sMmqR>mK2>8p~QC zhgDNF_mtg>2XyDR5I@Parj)Iie1N#U-bzgMv@zk{sq)3iRrr)+OC?Tjz~Ro|r=ysB zlTXR60PDmK?_0Q{w)DzmtLrzC#&_z{cxYT2l3DK(9Vq|PZ;{woCn12p z4ct*&E>|KFeS5ZW_;+TBOMcH$=JylE9Y79N=GzJjhPb2AVo0w|^g_YH>3Nhr7QmV; zU77l6wKqutC0C}Akrl2BU{&!#rT>5xPChH9*L#0h-Vd6$d>ogU=48g$RB$miSm!C& z`}r|0_H#$=<0sax%N4AUT!X)7rYQLiN&0Hkac9IPGhq3%1FBFL^V9k4Y7<__KpZ{s zm(KSFqrOo;-&<6@fS4;)H0!btkqxUsXv3A^XL;P5iW)4mRIx)XCzybgqZQo*)iIJ8 ztBR)+)e14VQrJ{7np|d+KYF*?@JhnyGU^!wpfb5T!B=Jvz^dXEO5uA}Tk@g#J}%6| zU^F?eyn);u0DseEm?1s<)8vO66LsL(x>L!`0=dIyAt_Q8(`xz&|(d zkf5}Ahw!FH9T>-_$x)c$KBr}FR%9we8ZqW8WnV9ck?wAO9wV%Oj5e<9k&63%rkkgk z#|*bNGTt4cG%5>GgnvMO$o_e005CrUQx~jQeH4Z`Ravf~EL9i-K&%-M27p*AAPfMp zc0d>aVlW^K05KF027p*c2o+lZ#qQF&(V?N(T(sqBFo59e28011hC_S-#Mh&eX?&~y zINC6)FLx)!je*&*yLCKdMb;%o3q6-nFNHJ zN?jG$KMe0=w8xzXpZ;sxiQ6H`qPF?l?Df$#w!4XPw9WtdYTbPs@7*^4@WWr8vhiwd z^G`eO@@Vu7w&)R%h8#T{$H;zU+BW~SHOIO~AB(-`r$6`b%uz0Qw~$I^l;>ol5n6_j zH$8aSYD#YQ`W>r{Zr$nw2|2on0-+=k=%`JvS#9)VX>yw-H_spCNSq}n0!Pi*krdTI0nX|CswT=Iv}`;-=Mh4R41=Oy;GhnY7Y5#0|N z^7ah5NXRc5@{SC-wvg8v@(USqj*uTRbpCQ|Y+~1Io zX2^fQxg%Q}^05rbEn3ltax1yVGvxC^&NSo`8S*J1BSSu!As-a-H~f^`QyKEBLUN}b z$RA`#^_!7<4f(?id7Y5A81hFM@*_fijGyYy8OF*Z9~QVaFT0jU&K9_tfu{;Q(ZEEY zmRZXqM+%&0AX-OM9W{`Bv8Z~sfm{k6RX=Rth`_HJ$Q`gz^*T#zO@XUf#{B}{ZUH(4 z{)b_w2>i5##Q-a+{-*`_jlfe4{F%Vm0z4&fx?vv@c$i_oA@H+?{er;94Erg8e>d#a z0{>>YT_W&O3-AGfzcO%4;Cn1A!zWLJ7go2~q52d1 zZc*LSz8h6n*>^$p%l2KndZUu4%&T5#?itl%MM^?9Xq*l;wCn?r?$E4cDt?n!4>~nXe-v{m6?`*Cc_dA=f zYU9TzV7epLr=+=5xv)V!`Y0?GTJoUHcqbYt>K3}s;3O2n?b1Ed#q1@f6 z&m(etKXrUVj(bvvbgz`!f=ygKi6nimeC|`Qg6X--2AQbYB`eIDxso8co8iF9UFjsa zrm(&ic%)rXt?0vL)!XKj&(;P~WmuENO2KcY+H1m=EK5K=+AFZhu#vEQ(nE*7}PoaM=;qu!q)aFD6@IAktZx<>wxjw*|8`Wvk(< z#myOIxwYby33)Y~Wc^)WviUzrd9lwO%3?7>hT%TAv?;V*6E;F^BhatcGM%ZuK%!xB zcv*!mrNvl%wHSL&S{Av3&4%nx&1Pe_R$_7%Pg~3dGabtsKCI!MEE@GfQIR#=dULn1 z&*j%}_aKPYaDSyD+Oe&dfXvL5UC!y6cX2JXp92^mGmUJ~`L~31zf)9|==T6wiSE;K zHzm?nCF&aJQ<%Rij8>gF0wQ$SV3ix`Ao^!f$L}@z*T4v$x0qUzwPcMS`(8#${j6dZ zTFzUII9cVAb_+sIX$zlA`$oC7w_y>|syd<8emo;d#G3tR;Hx*%*510SjrgiN&wpsX zb0Kn2b+M?3;?llKo|gGlYlS)^Ph~x;uUDSW!EiwQkZvx zpp8JaR+|ng4O2*Z7vkZ|wR=3%q8#`DbI;d%FQSsGft1?ED) z>S-3r_zeli9f&@ZWd_miSz{bqtJf%ainTfO44$WRThygt`>}y>cS0L6i-x+r+4W(O zus6F{?4sq}$F-O=@!qV7OHtxGI5dpkr!7Qm26%af9TyaHJ$hCR43t#Zd8~lcU+b0C zKmcB$*C z+$KR-TlVFt?Sost*$r~@{bopwo^tPv@)QT?>pPK+fgZmJok$)w+znGN8fV^6Ke4-X zDr6+%i8aVgOuLR4z(mLTBEj~M3bsD7oH{8i+m?+S#=CWjD@I^>TlC!&U{!Hbf>(b) z*#}q3C;1_utrm-4HKv_rb=qKC;O$yE7+3p?+yvWTw$s*saA#YoW|Z@E*ap^K5(dlB znDOVY;cNIaOWN~zKiyz=@nv(v0ja!!IgRVzu;Bpt`ZlaY8@$=?H7%-cLUJor6KbgY zQvplw9aSxE@~^eYHpH>@*3FcjmU;|8%xoSI27nk12m?TD5fBD+4XhowXxW_-x&MJB zni7jw{D}4$Q!??)nKS$Qsy~)nsmR@zx|K*~7i7GZdq4VLPbnuA``q?cO7Uya?wcq1 z=-;3&Cq%a+MV~Bs*A8;*s624&B*)IFV;4Dgl|!T7-T-PdRgdpnZg)`0a{9E%T26pR zObZ#iSAepy?=%TBltZuEbq&ncxUwa6WsPM3h^+#`01#UTgaIJ72?zsB5TMo71i`EM zA>sg$eW1vuKnKZjaOzkh$04cXP&wW#he~%iK;VEIm9~niuZpViS8!U6onrTPMIO}h z>U9hrex8w&CFYZjB02raU8}L?NXu3I{S4~tbk$ilrZ9X2Yron+WkXQW@AMn}1-E70 zAY)>jcfpj?S;^Cwg8r+w^mW!5|*=f2*C{?_>KW#z%Qv$JM$h! z3+8d}QoXak`wsiA_#NWVB_8H2+_HO*V7o6Z(-PkaDvigjXma?-oL7~@$1WuAmcvJf znU>bLDW>=4VA`$N+2l9YOBj= ztCixN3H15RP9*>XOu{5$t%tv-zdvogss(AkH&M2&;|tB=(i0Ec$zyvm)v*#?0Wdwg zn)6gBj4I3`(?bO2W=~Q|xKVwzxZ7h*F7FgtPyj8smgXIYTMIIq{wc+?akC}bL8S6$ zL+jIq*LmW5xcmIMZQUT1k=>^GvwPjz=I(PVM32Jh@Aq4~A>qv!`x&1^AsHix52s#! z2Cl4O_D>r|Up0*C^hy#s!}mw0{T#g^T)0soP@PcpbgO@7nxU%bsABA%C9hR6)r$3- z;BsYeu+IsLfvm8C%azSy_cMSjwr#0f4G-`v^=TyM0~qM0%P5Lfpz=GHl#6x@Ej9Dx z0Shf#7*WGgYD8U7YE%ZvA+8}nFUzKRJjX`K#azZ$toM7qvaa`r$6Py+^M2JideB0N z@|{Eb3ZQ+zt=V_B&$f$vH7XlGgKOo720xHi#!zUbxtIlAU`a~NSNn(wk`Dp6^W?&D zz8pRfZFmtb@?TB)?;3J20MEy^Y$eoiZS zz4dt@>8-!c>#Z+<40`Jn(hXe`!a;CO(nCoQ?q`Z&z3t~%EL$5D4*sNy5tk=2LiWctUb*?$pUl4nEKVR#Qo8F3f z)^4i1LvjP>a$!80n!G_Z8LF=%?JD}JtKff2-v4R&Z&z5R@4}+iiz-w46u9NEWKXR=JP}sKRWTl9)TYWfXX{XC7kz5 zt2Z-IYjrP~Bc6|NIo)ABgKU`(jVpEM5pciTTsT|u_?3@GO84Z-d*ho_Uf4|ERHpkj zK6{+(P!JxycE2X2Rooycc_6r4Sp@cFVI`8m3g+Ns?0y5ljW5WKjv`^?JWN7BNu|d2 zK?oQj5&{BJA@GL#vmR7(_owx@mUYYQ6523os;R%LqIV6?GI&B6P@O`mI#ae=D);cPL_k;^lziKOe9p1H zv4z$#m}EWi!f&ZhTtBd}=6B|Z*MJxFce?uzA>uX2 zLw_sXf111hc=vk&#uR_BII5YaMR-c>jR(*KDuupbJUNOq-an2f6hb`lUnCTGLU6gV z9@sw$oAQL7z z=lj>-D*)e}<(myRf0M6<8v|^TkT40)xKwq(JlFnmVlR1>c#h3vn!S+xLyp%{$0|Ag znK~$I*-eqH9>HQc3UW|QGgT?Cs#3ZJ))9=XGk(4}o!$m9DmT^T3iv1A*TFIIv(^mR z!PVFR%H~av>bO^J&2ScpE8vK5{TP+aXFq;clg@suXa%>6I3yc9YL@_JvmYyH!R=ZXuE|x( zz7CA;i=lhMIg;vi+zcl_M^asn50KFrJJtXA02zV2&d@ACI*F>Tj%U_H&x+rWNcyUY z{2KMv(EmZzyE_2mmVW!K4iOJgdjBLQ(;E;((sV6-ZEwxn^PFpK=;kX0WUBkIGz`C< zyi_@Jjs&k{k)jP>ANP+P0$b+H?43L~c(K5~t^p<#n$CG|~=UawJ&SWfrnMeN3+ z$A>8-o62E<5Ivj8vAM-Sz-$Q+xc5e7A@0>TjHf+R+K0q%1@qR7W{~?@Vy7HdwV+-t z>4^urXZ!8I*zKl%&C(6qfn0Ad$FkJ1N8V=_U6+#j__W+5vacc1dkOE&#Iw9qfK^;C zHd$VK=JVQX;=HUf#^)6~Rg8Ae`YN4dybgtQM=*w(H0*GDlcJ}pkWLFd-)@=l?d9PH zn83HS$)IiZEY(({Z7}9J*cdeJ14r{c&+6J`i5VM)^{m~YDNEISOu$kx>vI$Ls+nrI zo|d+T*ez~L3Zo0vjEQWxkT{{=O;MkV9^%O$KgVT}%FlI`Ql(vQ0t;fD80AKgS8lon z*5%dMpmoAj+DcveB;!(YI@*g51b6M4<1X_ScPW^-%h%s5`0IR=jr*w0G;(Bf!`+FV zoJNAI!jC);p`|t+Ja_xn1yuXg*5=xivk2cP2HKOe!IG(#)fcQogzUN=@746~9fIu_ zvT9G>uXHu@&$S*QOkWb!?a2q5hxmOs9^9UMNFg+F$fcq4rYr)Mv2e3Jg$s_`dM0`? zw-|EBS7+H;B3%oJsgu^7vP!D)1+Cv|%O=$p)T535D+aR}qWXq41gYOWnCI^%E5hPNWtXk4AokQ6g3(v-C)gt*o2%A+< zW7yws9m1X)!e&)fm$zn3$$1LHfsDpGTI@W=obI-UeQVwIO?zuajjKZzxb!*|?gr^@ zPp?zq8WZh_b61Y8YDvyf1`!vXB(2giRW+LPZ~o&|TTq{{GY0N=MTaD)*#po0C7Y>7y#mJ0b#(oHfKtg5c=JL z^aw1Zc_&0&!_U#~3fmy`!vjEvK;9f>DYsV6%?GDjE+Le9qP)DEZ{Y>wD7sKQmg*7m zhcgW!T8cZvVQ_Jo;xRY>rJ&*ti4gymkGI`&Q^1-K~9>kLz8z zTVT3d`^WjWaq`CZoczI?i`c9)Y^k@T8#j?N>O)_md>{6GC@Yk48Ow}Wp$uWW^t$xm zEmTuyA8)$U3T6RnOGiutcclAT7ix{#+OO#2#+|H8J~j1*>+tHwE4wW#=cRYwHP6>%%38n%FM< zb#~TSYvJO}i><8-7aV8BRMerUz?F)e#Np7<742>9mtSX3@U>-6@cqi)M^NZXO0Yfe z@*zwYJ`@C1*T6I>J8uu{Ye0Sp4DID@)Tm!B&X)U+5XX8voOzTuGfduerKs3cUmGw~ zW|XxE`^fI<2?S;8Hg+T#M#bg&gpmHI&9>}{D|K%X)6LO}1Xp!4#`7?(CE+3&_WlDAb_{9Ylt5*|nt_mTa|Z zK}UCsEnju^BaIN&WptcZ=t1QQM?kaiBw>RS#%&qSZl>c_#%##}lxxEM#t<;WpU`cqqRg?o0>x3K-{XAHYDeb5+Ht|k!#h;eaB?XN zbyTnBrd1-+R;Dj3y-#yfEd^+$TnDqwxJp=SIZx}F(0ZxoyVQ!J`gImcZ36zF)?1nD zoFsCT{Ao&_)_*!pBXEV>>AXt&pBUsbkG~ZWY{`5^vN&6qg&&U+J=)R|zzT+K#lOwg zMjDv&eU@{b#nM@zoX11a`$Z8+9vzYt5R!a*vm`IHB+;{4qs7pQ4Rd$(T5viKbS#nM zLaSEydKkZp*MYAwXDiF~7b$i_cewzZ^T|TdCAg>~^^4O;|8k{&H8}N)V@RKVp^x>8 zU|FLhg}PVQ0>n-U)_TY_xN1by_YkF&>mhQ|LG%e7ME&Ce=(s{{68gtwBDVweA(WD^yLIhWdSIhM39i{HBQ|)d}5| z${^RNRDD^e%DS3(Bg1wLED+Zn2hJE-09<#FMpMk5IbmfhZ%e7SFh8^%xlF{~K%VXs z$~cJD8x>fc>}Ig)WODnCsy3tvqAS&gsQ9%#Ifc<0swLOP2*3ila5jU{hP@oAPSQ0n zXWaJ3@hU*w_J=TIhK7{SiIAd!X>TUg1_5>3O@18SI&#PObJU{i zTIjk;bdB2=?(ZmVb~e6}Y#E7u@R!Me6}~!Eq|iirW=nV1vI3eKtu3fsD6&382D_le zz>zf41ps`yNF%dd`(E9>BA6X~L5&~Ip}&cPCE5KH?@*&iZW}-|d|l1J_kQ0TUXEW! z0n{A&syTwesoY+8wAu`+W^ih2IvZ1P)L+$$tWe#){4AWx+Fvns(8+Ckie~WUsiujC ztp`hEBht9q+FY?%Fnc5Rso(YGT^zfIfP#Ih9YuU+D6jz(^a&NTYhb<#{j^}p`IU6c zVgFLHRpZPxqM>?R7_Q#shpUv`)3Isk4>dM*4Ga&j$m!%ZHEMH-0J2OFo{( zt-Q##=vpi9lEBj0eE4}4=EEeS`S3ji^z-3E>H;>-hd)O6#_rIbTn?68o6U#iHPL+d z(2!Moa;4I3n9DLBese9vRZT)PneA?xo9%jCUFSNk^ZhBSgqluMo9|A8>6!0l~29}i)&f{zb#wZi;w&Rwri1~zw{ zZgPNP+T3+2RXcv}dNHAyyLJ;axkxd?+||prJ)>85o?=s0MxA$s?qUF2P?|p+t$O88 z%aYh$*On!x#qKO^NSr{d7{{47l4+=nV@V#f#Q zgZdRI_tshf6`?Nj5&w_6_l}dZIP(7=J(Fj4CC#ohvyu=i0V^8Kt_UKmL=qX3G1yih znPd>*F$}_(WftKa2*&AboUt(mpKYAaJ_j7m=WLvPJ|hPlaL$IaLHypI>V9UPSxMM; z_q{)U(rf4Gu1?j})!o(I)z!T)RV*8p-OIca+{4|6F7mxT%(VDIH7L_!w9i*Mhi znL1dI2~fU?QE5uC@TuOQ$<#QCgU`MopUN77t)uM(sS-L<+{xQndn#DH*~a=Vgg&w__wOMpO^|^J@6Sn}&;dBuM7(TqYEjel$!evxC$m-5crSzs2ffy2#t9 z&%$l$leHITwYY!BwMgr*d$65$xY>)#;6?e8k$%GW4#Q&+bg8y?;t!u$#73vz*W0`KztB79JSBQ-udTPyJC_Y-R5gxg zs_5-u_1%m3t%v~a|Jyd}beyoweF#RwHoeQ_OLG9YDg~J1=FF*N5l-h+(%$oO1?@nT z1;KP;YFI4iLL3|JLhyy#28R&L!6_TRMw710aVCp;ohX@5SQ2A?0)oh1!aOea5ZP~b zxG%9unvv0GP->zL4CYAqMcEb>Z?nTOhqi0sQy?(6_6 zyD=pWnrrv8 z^*xL4-NNO7fXnp+g3D$;;G(F-DR}}e`+8hpG8jwiehcgyPmV{*jlC0{sf_Seun1N!Ru-=EABtsd-ZkE*ooDHEwrX*>QCXfFr?qHt9xCeQtLb zu$9pf`zTyZ+znT~Lb%{6K$Yo+tA0M*jfMi8YSoRPiiQYF(+H|wH-c&$BJ7$*P(8a5 zRQ224qpHK0B5*~{4@z-7y7PjYsC+!u9C`iN$immRzr&$Y=Qx094%Oq- z)~+FF#Ddt?QjlnHYO9P(~!XCSO~db82)D!EO;hG7y8!`do5|Ad3aD zOUv?hJnfS;3JP>i4wom;G&xcpqhIL9)HJ;>Jg_L-7xrDivBhNffCr_Y?)3m5jd*tl zL8`(If>h5P1c^G`ASo7bkUgcgNxn-frFv29pDUVSUsT`#y?2!N5@ zqgHf#5rBYW1nv2RK+mT#gRjjgpWcT;&x3_zFzpjma+PA4n8}bhFpLE@4gPQ46~;_TIZs$MDllC%XD2y}e3d4eKkhZ91lq zZaNV?lg_ug@zu8JB)j!eiX@qoo<)pVx06zb^4&5;zD&v}oukgep3cf}zQ_znaw z6C}o}9l^N+D_Zb&t3|cMK7aBR?vmx~Um8Vuruq9sg7}%pa&ZCeUZC?iA}KtVs3u&P z1HkiAfH?p>KLwZrzzb47s?q)K*Y#NlX{C7#1IP;_5rCS$vG4~<02NZv5Z{C+AY)^gwzU-ip948d$~ z?|WXf@|fP<_cZ0+qjH02T5X*fo((|sG|^NgGVJ)(9d{89F8L)jQr;p{PGK^$eaN`A zOIwrVSUK5U6+;(Eb*Ll~Cn`w`)FQ?P?QBiFrqA3e{m9F+mFVIty*#V2Rp;aH1~*=Z zC7+D#vnDme_L-pAKD!XfV;kFNGt-&vvwt|?siG@IZ8O%zSbDc7SeeNE!sVa zqRF0Ni^cQAKy=7)o+pIUCE)a4;go~b=o_*y`xQ8}jSN@4HboRQh%yhY1w;*}T(W|S zR^xomV_nSm(5j4=${Dn5DimVp3o-vrLyROeeqs-gtHI-a{|h{d#YBQa`)GawBA#}+ zWKZy12C=eOB@;pglrHcnh5oWMTD_r%)2qPg{X1?i_e9cS($%zj_77S;>=hJtfC&NW zxdET7j?Sn0o02_;VgqfRM!i@s-8Nk2*(i@^9VHeS;jN=>w9k)%9p!;&-32MmakLJc zKOmfuk3Ey2%Tm-x7MRSP0S=np%ZWsK=2O2;E^ya!%P6n7YFa%Ngm|^pWJP$(MCFzJV7=P z+blu@w(>#aW{LYMnfpXZ1M<4BX|iEXse4C!qd~Z34C62_~q&xlN!JK(+@{bKmL)S{)0~=fT1Lh4q$4V)|C7ZKf(-f@5p( zB64W&4XMXAE5tn&Khz3nufaF{(*6_|_fjZiMhc-j{j&ZP8+$1fG9!iRF#YoW6qoc; zD8#3rUW?eX(0S#*ihmL!iGTiy80eko!=4BouQ2&d;;<27py^li=Y44}Z-uyX@z-Pu z`soV5{94sr4A%`-R^1}<`CP26^>`UO#)uj0E$hw2Rpk;P^)kyZg3()88(O<%3X^wrvG`5`$QuX&7%*r;+XXTL>FgL7k7`0Tt2 z2<8K*edjjish_;kscNO`6uVrJ3pL^$Xxic!M z;lwLaK;}#e*w|s`GyJ0pQ+bTX6MyHt7))i z_5O#H-p3$<={Q9+h?^PcqwlaD{7oO}3%{y=Iri-WKwrk=r+;c+40hVeGrzD8{0aNOeX&TII=EjI6{sc?(eJEFF>mEASQ zgz?l{0oa+zZSxFXvupj+)z@lKmW0wtjRzNk7(yw^Iz0 zK*4K2n6lR5gnoY6zoNGQ=*vLtq97(>S zi`jDtXNSkkwz6U6PU1>TF*yU_yqKKDZ#lUur{TR@6Ai#+UV5JVE+p|yW(iN&e!{4= z^l>63Z|l#TD*!(8PB-&Ef>qergI`m{M|Ul_rPbM=>hQ`MYMEn|>APS}o1Wi>1H=E& zwf$Mf#Iel^aR@+mKS%`AHG|o1d5>VNif;#hkK_LzbU*$dq5I58{|9uxyPxhi_2{mU z|1RCxF^%s1Z}a;j;u`g&$L06kG7Nd*_Nmv$v@_$(Bwn{i{Lh*8?ft~Nxko&O^bt>8 z+dPLxZ03&^Mh3(mW=%o5zawiy`V}4GPQ8%_UfiE{?1m#5|%LXO9SlMjEhSM*$1B z31BY#XJsjUOmhHuM+z_pfOn<aq5Wh#v_4Z1L9 zJJ{E8kLc_d_Z^1#CwK+de>oB()n!fk5@pWCw`z;_j^F~-&OZ%ja6~` zNfe(vnI8_))H9maGa!lej9Tl8lJ$%MhMXR9-t>eHFGqYf$y3OoVhmPE)+#qOX(5Yf zp|44c*`Q)y9fJ1F^ZUeedp|tFs?I*=rEQ9P+Se)AtbcchH$HXi#JPBagx>pj329X zuQWrS(c?zi+!KLI(BgZ5xhEX{V>?J?1j_*>_v5}G;pLc}P#GwheSqe? z+=5=lKj};Tk7oSSOsaAh6KM^k#nlVL<@^7j8u9OON53pB#}`*EGAQpgcPT$@^ z7mDW|yN)7Y&&|TKB^f-wE!-)7zOzTjS6(E>8hfE9dnK_X=7p~)Tp8uTh zKNtAVg?t(WUdx2pei|OXpS}hhKCdx@Afbx#(O;QB&a4(On5E5@d_2%v5(10sLNM>4 z`iw3hH#>khDJEzSFm@~GWbQ|5>zAf^_Gi5=n;3KUeF=Z8gy5t#!yLX}0Jpe(5%`Lj z7YDqz7q(Ght3#2wwlG_Ml9_BwE+Hsx=8{VlWj(vPzoZm)byv`O?CS2NM$GJ&F2^7C zOUD%?gw$TzAOE88nNkcL&9s=9R^(fyR%!Ch1U8nlQ>QStCa;^|Yb_F-YL#aDGJZRI z6GRMsn(hh^?d*fIwO{06?sW*Y@zkEctO*cxLhM~BgnwA;Tul_qheIpNZc16W%gLTQ z;va}wJoDm1IxPa&XBA$}6v6)dc3Sgz`RSc{szxtTPP*5p?~ThCBs>ot0ZL!$(#7vN zig3L>N26%9RcaXJwdW|(cSIuGqJKxVwY|mP8wY4lP<)_1MVsCQQ>DEL@7)>iI(G$? zHnv-SHEsJB-?j&kA8orzpM2*)KJ>X8gi@gy^?%Ps2Wmlurl~6R*g#l zSg~uxn^eNp{B#a7 z;A(ymSeg{%0m94%g%H7@K2Oov_|`~YPm2=RM^5uldF{V2MtQtP`tc4$1?+sWnF4Mm zJIOMu4@Uy!akp)1C5%+748B$&UrSZU78R;ers5i|tjGYJS)!I@{Z+EWJE+ovZK|Z$ zMg@ijxsBsOV~9e-m7$8kFyX*DZ}n=`&ibm_R()098~0!m_kHzbDt=#=$9!KE5BB=1 zm>{;ks-C32s;<;g`{P!B>Zmnwt5bDE8SA%?5Zk|>sxrQ+KE~}Q>9)qLBiNq)I3pJ8 znbzL6n29R6g#S>kWWfq1t zD7z=Kg;i@EDl3rKRC5v1Wv#W>oMwyo?c@z98*IZ?x~V&W{Hr|wMa*8B=z=eO z=`c%U%U^GNSumew($cif%_P|ToE1I+y=g(P)-0G$u*xUcJCh(Gn!d`6zD;k|p^4J@i7E_vH zqbw6QrKyi=H0|1O(bz882kTMwC`@U)t;t>Lo(V(nB=LG};#Z3stP$3yCaST)!&U;q zvxYGn%e9`mOd>0xkPL2;N?6YmSejDf0ZL z=cgHY(q#R4x`Rc_Tn#fTbBpc}Rh~o4YSm1wl<1JGrzU<{;_wDK9@-7Y{LNaCeN<*w zSXNE^NVmVlbSZ%}7wiun-|~1|r%Y#($?RNX;kM*$UB92?-r%)KWABNl*U&HRxZ_Y9 zTZ=UswcVlSW1AHcdu8>@SchTPclxmY$ODS}5R={5W`%^vmn*VxQ~&<9;bwPb7$PjU zWSZ=2rTdnlk>?n3s*U7&AOmH`CE%8OIzNNC@p9!xMVQDTHsvbGW@2;~v{lO8EPQpq zeFGuOKW}f1qGxtBTOX;q@gUJmY1Guj;)MG(gcXd9KYjnRIn)+@Ibe+ zVKqjV-JgT5q1@HKTdL~C&~68M;IHYK^Icu=yA@3NxTBFM9f!Zr%x##|v2 zca9d;nJNBo(aoQ^AE_Ym)}`Pin-y5=2b7T?^R0Yqg$a)e(DZy1-_9}owDy!&uFets zFk35%&DK_g53_n%e53$HwmMFH+GYZ!v+@t1{QrO^zI;FS3%TCdZ&}*duWD)RSG_vI z#>V&tP>u2Vh}Mk0YxEso5GQYgG|Y32*pm1F z$fTYB;l~C7e?R<~G}74krB7c0!z9lYe-vAnv3>bGIZF}J)twY)3Ll00e@QO&lD2Ta z#O4sXB9BNp86qISO4Jk{8X;A&DmGiy5qk zD=hlK^h#m+e4$rs9f=XL7sxr##dNSfI38~~vOZ9+)CZEgg;3Qa^ln4Q!!UC;klZ5# zP-3ZhC6@PsK#dLRTDn0U_Lwh>uKj_(e;7w|XPx>~f5%&Ern%r}Yo?9vg`9*xK z{k17=VSPbO_n^((e*Ejjd2uFrh$DW#5itCIlQQMeaz2}Hk$V@~QtL2~I)ovf>HpO|3#ZidcL>#S#TAkP8O*4L- zBJ>iy9&gB4AwJ_b^v8Ll;`odelF9f@cu~Z--GAP!4~iFxbG6ESi-+IAXY%(@LDe-} zrj+8%39?-0t%`%@hCWlu!GrIV|2hktl6z}Iv6HvqL=Dl)D!Va6DDQz7UB-@Fv1RQ2 zDk5s_l8Lh}#8D|ej$9!ESt6ORa78D@a#E@7yZGtcEq}A42D9PHcmS0Vg()ueGEBG5 z7mo3DhOc)UKEBd7U%V&F7^9KZRu;^(*T&qs`T@-Obqf|L+%uqGru32i(rq*pT9S+P zy>V}5A}<$fY{VQDW25l&W20ELH#QpnHa5NkxW>kJ^6iKbZEU;;hrf4z1bN-Bv9YVm zau`!fJJkt!Hxao{T1wu71Bxc^<(q1M{JejAIq0PA6fHU+-AB$xFDHNfYrl_#sL`xr zf=kx&6XCf?p7$$9JWrG71Hp5KJZFYz9|V}36+G7|3d?a{2D$mD) zXFX}s33-BPA^Erh=-0XE7>{1mz84T!d_w6y8PZ*=vOX0&i~!Z-)4}s^iuRe{`FD9f zE01p<7CX@vn{Os^O>X#_s*1{QwlIH@i;Bx+t9MR!8mUd{0T&<5!Uy%-Y2_#_qcz0L z=5HZ?O2GDFv>$)1gSyEFTKi|*%i{zg0M($Sae{z=V~yq5sUEjRG}q0v6!Ld8{ z_Za=wLmpXqGHUIuG}$Qo4A01<+sRRP9&8n&X8Jye=x_x1{77^-9GpdmbC6G@m8x&@ zLW<@%bytQ0Mkl29JR02drG|P0(c529{jVX==a}aAfbu&sv!fHbvPRv600yr5>59>;gLbz8I1Xf)l^h)`mM zqD{xAtk(3hQb;B%ls@|Mvc>Ok7Oz4m zb0TgnD^8!?pYdVJ`0HRYwpk(W8#ud{P_6@4TU4!RA40s%s+QI#&!^7IkIPvT=6X$N zZSLRTy#ra_haiUKeUxvK_455u(-p6}eaX;#;xzArp3=`{u@U`LwDxyfAXa`}t#vE} zI*DOspvA?ilW`12DE(CIau%=R00?vAc)n8GvJgE2sXSo0q{bN|rZezF-y@$$rc}e+ z(v0>#Hl17i{^-xFT;IkucleTY?ux5y-y9j`u-sg%*dxZGvlQmEi@?ETv2{QhGo;OR zfVA_4JPr({gx)Q&s^8Hemfx|OxJSuk9a&>dF1ic69(U}!xhi$eAYyHGDzTT8k~OYv{WU)d+cFGMb|Tad+>1Z0)-_-W%PS&o>Bhk-QKhce4Xdiv^?pV^8(k9~r+y0CYYVN@nrcBh_ zqIWK_7pg11X@AzI%#hwISrIKh-nXgNYOkTpU#QH0loddCD|3`Qc|C!}O>bdcHolX8 zhddQEAZI}l`7d>%mqf>h8*Jg zQOYCC)}nFiluD1!jau{j*p9s(TNO8C(0Y6rQ#f_r0-Dx%cmmi(t-0L8vK;cBIHCKl zJ{NZD>%sIDuap1O=#JtnoeG#v{>S+KD${oglkYN0UC>X8vqg&U7^M_4ePMs_IST#{ z3s#7`K1-?A@hO>fjWbZ;A|w}4K@1Xa*@Id%LIv2{%^vJSfIh7d&mZsDrf%p_MbQP( z?l-1-6dR(ayeH-7I7$fpwdxt#ui82$y{DQhCEo>gn7h#APS_J>-cL0gM{@346_6pHt7VnQ%MFO%Br;^KY%JTwhrTT3hGe3J;VA(=tuCQJDvBlV*oy z?kF30YnJU@{Vvd4ecX%FW{tL5oEWHV(qkpH(!@|}35TL)5cUqCG|<|a*(T9@A;CY} zo8bz+$OLAZYR)nqj0n}htL-)MxJK1=>!UeE9IB7x=idtto=8ys*&$7IV}_cW_jJEO z;Z1)%&ggg8j?(UsBs<7X`yl7i*zAK`N*B>wsyRSI=^uOh3zPQ(2^YI-lbRBO*&!|^SVy+`N*YugSaNe}w%}IYG1N43tX^*eU*q*sV+v~bZ;?Zf01{Yf;5lY1y! zD&(ImY%JQ>yHyS=l<3dY-Ip`tzE;?4t54oX%E>uI<;vC*_@K@pwAk#qgb%ENSW+U4 zMJ|CJYT`iWGQxEWO3^2O>_Y`J388a2E;7-_cxO1z3nrWDkf4(Q!>%E}P#J|ZE!!<` zf!vd+pfBL*iZ}X*70_Bu*#h|7uTT&c8u@fCmb+=OxKzfvG)fb3@ya6lgeIAMD0U*I z?qn&qs5MG9Legh560+HHSXwj1G=%3_m9VKvWShlS55TLwK@>@Ff5q!O6<}AqqQht- zZEfnG?4s(g?7flhco~aWmb&9*S)0VZ{OYuPUu%V0?R_YU8LIX2oi#pcAB!{c(9V7z zOZ?W`$FfpvA4@}+_OUdEb=1JNkF^n~-mGgRJkSE4<81bvBf)uf-2N`XW4Yv;iZzBT zq;K+lbsLPv=lpd`m#F&Q{{tHYW$y@nRezNB-9ze+_i3cIBctSp7D;?ob4M(pC!fjS zcx@Q$^IIE=8BU#&9}$-|s!j|hUqxQsYwR=>p`H5y2>wB0Yn`!5|GHMfE|ee<3;>oF7*GV^n81EKZh~E5B?W554RYYYPnHQ{<_A{1f>8mAvDL z+R&`6^N)sxrPh)-dA+3>z+y=X#KP|xi)hrJP+1*K{FLv+z-X0?i1yELw@LsX#KAsd zAv%M6|7JLk&qZQ~7!&0}dc@0I+B-BFXziC9&&{_Jr%U4moQmV~(bG$QjEm0$d^W5v ztZ!jUE7=DR?)6p$mGdV0;Ug-?_E$E4?T2gJ+bOI%kJm8SM$tB1VdMJ2$^w4CPrES{ z_e`ig*wf2P5|r?y30t|qw(o}X+X>?&ow}K#%y3~oYu4{jLmd;z6c~?|+B%|1->K(^ zN?YeIV_0WN^`GEdYqgk*n2w8OcMV-?xT07Uzt=P~E;IYS>|YcxJfH<_Ve*xfQZLS5 zfl&q@v{Lm_p$jE&Hq1C?VN!Cn50}jXar*2^!2>ol*NOu0pmsEEM{n4UwOR+mplOwT z+~J~(Bt)h05u5ODhLq#g(P}yD12$--;o0@st8;V+pQ5mL5k(5S@iC`9XM9d6fY&AG z>wt`HsJV2TxhcP$44#h~UnFeG7n2iUDLBs>rdqr-?2<)ma?N#PGTY!43)XL}&S(;C zP^vn`b?IK9w@bBEyxd5BPFsmdf5BJT|B|oiq-mTT0f)zYykCWQzYg(!!`IHYenFfl z2VdpX1Js%&_eKT{HgksOU^MPvCMP>i=8%h+l7!<$E7+ZhcUp0fHCkURcO>IsuL?QS z1QlZcM4tXSJFgV6S&$6n7X*Ede18sC(-s;?ET>JuV@mtdE%iCNqgBka!;o{7JZa2p zNP)6LU!IW8cGj&o>Bju?U{3_&DdrRO?>hSAJJ;(|=-j}^l=N=#k#6*nZuXI0qEHB- zwf}GV&Jyk7>9#_3_I4$Xt;J`NwEbIXJhoXO(^vHeKU=}SvtWg|=TH%zj81;9)@kim zO8$TY6aD36KB~1tx#W-dO^mG~gzhfo+}7+6T2B5%pgM(#3!mycn<-9j>M!{@D*4YO z7~8B6_gqSzzPdlob9=}u#66EV?)m(r57GH`{YlYbxAfM#TEoSnMSC&mY1hJ6h9e}a z4A>gp0nPp+r0`mh&v%ZFyKKK;Mf(O&*({PuS`{tq2=_1GpA}o5L4+W-K2ve~Z3if{ zYdfGPp}vr;cW65xA1&bk<{hR7q4YbFMenE9lRsu?M&A4#9|R;tM~u9dPfr$=5+oAW zvjE1|J)7UH^Y?&&}X(MwCkuEp-v&ciN&$C{+KV>u}@k}8sb)8^MK|vK(q6F%WX5iHd#vW(TYy0 zi zE;%H8?c7I>kwA_u!=Z7#$ozcegvgwgmmBbm{<8A28q?z;iP)O6D-ny_Qd!nmy#Y?j z(SNW-3e?NQV&R6Plx!ZS?|cfz$y;5Nen)-h+v+&GF!naWSQf%tT}yZa?*-0 z%ikZOCG?l&Zzb`Fh`){GUH%qfQvQ}aROs@A=WnrGkH0M^<8Mv8;&08o9knrTO}`xx z6SwBzjvCW=`jy1?=kN0<1N7=~(q`mAD*7)5PMN-*C0(A_%xKs?MjEa+nKI$?Nxxe* z{a=+n-`J=O2-7+O2`S<}ZzyBQop|m32BrN?v1_Sh7x_e~99C4_({dkC%+mCGk%Us* z_9CMaahNy*@{baU1;pIkM6PISj;#PtHX6!#Ijz!N8|>SkmWxAV+yJA;d=@O#nvQK2 zgu3mdkI`0+vd0lf|@*UE<9BF(&qc{^no~7b;Vv=>a z)CYHYvI!gHYQtN=b1C)B*~^kBITTdiO+(Rwg~4htup%(`0;HY@LLRfED`840qVzan z<;MxDWWyw=whE>X+I`!r6nzTZDl=$ok{<_Qg~_-6k15v+{P{tThxWI#0WGJijv<7AK^tsTV9ZuXAj%~8<(6xFPa+?@(adF5+nb4&s;jr(Z+PwrIEw$JXMziPIh;i?PiLNnQx^ znF;4yGEdcjl!)M*Un&0wL;elrZyPh!TrrtXy!mENN<;ms-w@^>#v3jfqj6K5WjP9= zlmA2rTR_#94cE}*bPW?#ejO3q6XbfmT*t}v2Dy%x>y2`qAlIAZI#I6MaRuAn?j!)-s`r>XIpaPh z<32UxeqzRbTGze#wPdNO^e%mM57it1?oI*b0Pyw{U=9H9P66fs@SYT44gl{JfbaYC z&0a8702fIOPWb!NgysP7ffQg403S>N<^b?70!XGj26_(AXSx?<+Y2Fh=R<_E@Lhzv z+L~8PcEVAMrvxq~i}64`2{;o!?mO;G&sY{kOAhRU_RMiXz@zG*py=*aDtvva1LJP&FnAKzYC-`##$ zb6u~j<&$YyMLQ1?-DcEj$&9jQ7m|CoSK4>_OQSNOZbQp^<&CdS%lpUc_KEG4_TQ@8 zKVH@+|CeQH%$9sLC_1)x7O!~*aW7(swTDVIY8Oi!2z-jNl2Kzk+i=sl@|x|pH)cYO zU4vzoz^K3Iqj+G%CCV_^lbszcbe`Vyj}NCE#vB0tH3gUhz(-Pmcj!!lt%GQ``qI?L z2+Y`-M#)xorUk_g^NXq6Hg=|GraOSnb=8j2QT)f#;?3dDM7Ymn|6;dP^};6eE_^$m zAZonW{Zy?r{V6;)w|x@F*5Y3Qws$4g*k*;acf;w%^m8LvVp#nzimb5p`MVr_o08|K9o^}Sj`wZg`7#GRs_sd6Q5JL`vkQU#0HH9+9yx=7Fe^@fx*w&# zsel#*jBQqkyMoXFFD7_=?K0-kGKWWOzT<;yhYG>N>LCW*p$V> zrTM&`95b7ONT)IOg`}HnJg%`-=~VG#Z#*2^vyr?HF0}F7!>oI*66qw5Bp(Hd8&SN* zEWmxK)18dWWx@tldGg6h5+b)@<^;#Y`?1#^Z*80dw*_^*V*4AUtTDcRlP`PWsk&Uy zN0}F$d>(=`|HQvKi(zG5Z$C<87J8k}fF>%k|E^EI^I3fgozL-+!mPaRkHqwHR@@qv zI`LKj{zad+8sU`hMdXV~ta|&gkniVxhF{Pp*ZHD8h0d4snVs)^nJ@NT1>KaJW$)1< z#>Fj1nTnH^$@fDeXet#KKag9^(l0|#)q;5^qg69>tPA6 zZcllNwrzRwjPg*1&bPln-Sk@XulYtidTV2V*=U9O`>CClA0Ef>T+iq>@U zFA&xJ&Iqslf5y(Jq52@fOp1an7Er5j)Z&tznT*VHW;P~F4L|Ce8d4di67ACR^TCSt zf06fQjhM@qaiG0g^p^cxStUtMVKGldB&(X0l3ik*3*HV6P2tcxbg^STT2sBs8qv68 zK|E#sE4B_5PpNx(38no(O69ef@2g^CK0LWUc$6mjp?OTSPTm&+Ed2-ML0(f)CHGtT zya)DCN=xGYBsfy*@Lt8}eLKciy|st$XgsBU8(Rk@J9Wu;N}bkAIy13Wco%D>3|RXN z?EYmJcgrQkUBz~Bw-F;-vLy^P5WU~CWBk?g4gc6WIykFa$5ZP3iODcCaaMR2XAKU? zze1`m;s*JMASux;s}ymS*G1fh9bd3z5}ZmMuz#`!c-=GYe3yhQhW>+(#QrV%YE5Ad z0N+mm<^b@66krYjKTHAU0Pv#}U=9EeqyTgH^D=beq7fT!DjF=?2db;g!{V$Pz~57q zuDM^btn*qGC=e;IeGqYzgViBQxv6iEd2)z6xv8(?PPaC>vtj<>QiJv9BCQl}8?Ka+ zLy3xZ@*1V6vwflUWL^3P_vfa*sr2qwTH78*_@IYqVpOg=n|7#3xQ+&jUlYqz7!>S& zqcj;1g?}p`gLcNMRa%+nroIhG;|~k!RjfFe`SR|!O8F4Oims8)ZY=@yJ3vFT-Crci z{9eI2CZ(9!V9P-125eeP|-yk4&(*ZXvU+J`IR zP<_b#Q69u53Ci_EequLU;FIJ(nIDEWR|S%s56Z%JW2d2Fj)6 zG&zb5>mt#XIm3GNd?YCoN6W@7E25I7hLSs7@yc?S-IL_bO?`*<_S11ODOiHrXVjCqkpG7xl*6pMfOrlS1RzDA()c@K*|aw}X)o z1}2K`FP2h0(Z3Bi<{rwV+_^uc=uS%J>dB&e*is7AH1ibgWvJOU+Kc%ebN`je#AP!n zy1!aFHA|!Yal`d*naKP2$e8Uuff`3+OE~|yJ{f1PDB}II)%vFO5OaVH8`ad~5zAGe zv>uOUay_~qg{>A@Y?7kzSSIo*|0oLG`va2EbzZwjZ6cjlXj_=v1xT*Vc0H%&#pKCw zQSuai1}1++(<5@P#e>jp%A1m6Z0u1>n!eY>@eCen9q&68pD6BqTq!X+df&PDRDbWg z2%jqGeV0#mC@uDv1MWDE^@pob@2GWgfL+Cj{46(UuqOrBCh1d~+O(Tv0SBcSoTkWi zgSoy8h7C5aFW!8ESzXsr*VSd((yuVMCz`G>)Q7x&z%J2aOJ~_yLi9Wp_)elHf`__B zJf)uD8p<=;vPz2}0jyhkZr&woYxEmg6lo~af(JJ**rnR4vm(7*t z0PwREU=9EerT}vQ_<0I2$KBlxu$&tel31TxYTts})xOB~V{&LOvKSpk{T8c!j-#of z9axNTuU#))@}eH`+$_Q0#5{ldSTlA@1d6+$_A}@T;c5^0#rB@+JsNg$J|TM3ReRG~ zmjcvf2hj+BqofAIF6Z;=#(&Ok>_qR@+89?s$aR-?MeEoPiB2kmTcTOz+(F_ev^yIO84qx z+h?`@=QaXMzRpgQJ6mjIS!^%yW^pg4F_#d3v&IUoNq(cR)+FWt@Y@t%4gh~h0p0{XVPoVhJbNv zZ*(Y>L(UyVl3;F}CSYWYImT#TMv-g+v@h=#t^TGxs$ss;L127Qqj3MGxz;1)%iT`+ zx?Z@~Kd>>2EeF!ObD(WLNmR~LMP^C#?le|;Hl#t8Kc!?b2YtQ}p)M6I%hc@{RU6Bk zO8<0nlGP5?u4uJmm|o?#9#p$6hnK8&*o5%4^NF|cE=8Y#GVQ&3%im5q_UbCVBzJ83J`U~M7f z_5T-<*xJ*_Mt?fZ0L~m!@TsWE9~Hafi5Vi4X`S?C0^x}4enuC^mxCkY6;xIR`H(^0 z-wspX5^p%lMuGK0D?RSysx(uLo6;J~k0yK%YuBlVK_|JAnrmiaXcr-HmSKN<_?L%LFSCIzE8`zTp3&lj+e%|oy%v#mA8m>JCXRn9&l zju_@?D^zN&MWy7YI0jG()jL_`?7QMYLG=E9Jyx5t{8%_1?S5K%UKK~TQhv+IXGO)H zcg}9&)LN&dH^<8U9#z#k=R8W(&A?4>LS9Thq@ClI3s=kb7W;Q6yCH6tSxl3A*HP=P ziY->@GuOR}$yLx$%IF%_^$O#uirkY==0s;-?(w~{HzsW&O-Jp3ExY5=_!=J$ranx` zoRdDu%IsdPues-gVAwIvIm0MME*evsx7{(y+ zW^PlPUED;@WV5jf!jR&P1YBsmY#-y^ct^cQh6R+!_a!4;4`ls8)Xb_I{#6w=jjzrl z%7FaEw@U*)@suKLN8j)qO4*^lVJ><<>6@aNrKfSQd0Udr+dL0^or`+mD~W)5p>oF& zPlmeUxE@6%sH z`ib|%PROHts5~(vgk0-2?9LL{-?VqnLJMD8Z+3c`=;^>}k z`zZa4_BoUGQ75L(3bpppsFrD;JYLaQ;jM#c9|3&(6ciLs>C^rkLrG_r08NXUHjR?e zG#1q-wWj%C?1VQ>qwKOx^E82JnzUWs`qo~%SY|%OjCN7Uy>^+fc2R8KE+S{IT`U)C zmoSh|smgvdr;XiZY8P9t$!5d5;BEIz*a@A+*li*#xdJ`sOWn1=FiveCU3+bC@kt?K zlakPp1Z55Ysb-=zF*?u}f}#P6;)!Zqi$pNqp?DIc9# z=j@W3?4O^A|HNGD&S&7_O$s{-3AtWNF7!mF8%gderO(8_AQON2>a@yJN}^*JtnMk* z_55VKoBO>t$O|GG$D|**LUY8N*zC<Eb|X37#)hReH|A}Nj(`!Xe=OdAc7dAbHu{-=Q7-nuIk z(Be%Y5E23h_7EkvTCQPgoVqU*^7R{AJ>0JYqb}~WF*Bmc(=~mc9J^;jmI+=tk=Y%D zS*mXBma}~Ulc#dcl|^lRd#cpjg_?Ejf@5w%0ZKy`uDPdWrlXGX=MLryEM{{14J0~N z@-)hlDzdpkrcG67!@YoCB3(psGlec_)1bb+3el^f2U8*RrZ_0!SKzhDX9JIuIa+63 zOlq5PAWPY8#8+!QO?!N>OM*Rz>gXP(fy%ye-t5DAD37gJsHvIO^Fhm0GIOovGVz-I zne@ejDYk}c(K@Q#%85|9aP`~?MZ#3GEXWWR$^WP6B|hpr_4U31LlN#vFe8>N4VQpY z+O*vRJ)C}rzPkGVVzou!0dLFPT^zpXd>B$3K7ijWOoRg{v2LNl+yLi zT|)E>L9P84y2B-?gd3jfLoIMvDI7v22SX*B*1=^`Ty_#v=bJ&%RHqp@vn?d9(ZD$; z9Tv?2U~UR92Y`ACFoz#wkUye*jRraQa!TMKQuk8(8ll#=Z^OfOwZX7A_ezEF+N6P- zU9cq;7DH(n<^aG2U_YLl!`fBrfz8YfY?#f*JRVnL8}5zDH_RQ9^ZLV^K3vnt?LHzo z&r5SQ2Y~r0z#IS?DZm^6nkm2>07g=PIRGq30p&0oU%>iKN z6krYjqba}~AB)|G$RHh8w0?30){6b$-MNKp4+k$_7P}9s;Ar`0SPlG(IYC@`xDU&R z=U?UdNbr1Ao{!0+YxN-o^Ptx|+YcU(r+#hwkgMgrW*`w!gTpv#@n+e``(8H&Lu+xdGunJBFPH zcQbLdqgAeQcq@->a>r$6Jfp;JP-3XKbv{Q;vv^(Xo5UDjP{MwDH`JXdQAA0ZLv79) z+9tS%=kCI@eLJx`YDRR^jhXD^(tUv{A-Vj(KJDuzK#8HWuTzL_& z!5$G_v0Lo!BiRHJp+Ogd_h@D1V0@iQbeqWwnE^2hYdItCvnpZoU~**aYkF^-d=IF# zaB{zee~}hqVPqNHKn6bRv5CQDL#&i!JfWU%jRxZheZZ>IO?NY%Fo?Z!GoCOvjLAF+ zBb$(1X3CiXg=YIquJ;d2o}|(sU^&5@BL=VF#V>+}ZSfzHSs3ixY+t2zst$qBD;Z^? zp{gtH#&ER>usqlBO;;NCuO-_yJ1;r~GRpj%t*&ScGm>`^E%{^W6VoJrHeZPN21q!b zTiBYlAo;z)-63R?{My{jHo7*&$`rjF!q3LZv#MI~b(;eN8z z$&Fb(&~6bbB0K_GFZ)U--&chTdT3%GQ{(zT))?6qOy>j|4Gs-jE$RimDDZg_w_*$z z{TK%}f-%Z=mMAF;25;u#D<`3gxXs^NV%6vL<-51!>Tg?GBF@c5oSTi9@=x@=<%eA- z4vL}&`FoVVpFr$Ie6^P=<`R4%uo%zoxYK_pVQ8b;CfS7%Oy@af0JL8nnvBl5E? za71#mT<$siE?d3~g8Wbf@pD4X?&mHwS!;A~ffE}_h+?HuO>X0-dxNReW5+P0Iz&@{ zGpVQB$f>lASI(F?GQRceeI3KE@_qfQluxHb9cAs#CZ_vMZppD{WEd5BS;95YqXPp< zTnQ2Gh3))(Ae~hz($Wygq@6IhdwH9{u-Oa_8h2jE{Jgq{OGqg3CD{N*THXg)d7zS){w?jbJQkNt4*NfokD!GrDRQNiJS`}z750&QE!SP7=t z8L>i!>iH9;t_ekT@$)fiQW+{F<%Pr>2;`7elK%xcun&fQ6qfj306H*NMRp)xXzqF; zd696OJ^4#I846`DmTykl4(NaVZwj8GU_Xd-XVT%he)_4;N$wRXP&?C$7o~dLVQ*AA z1;eO~tvUm2gmXQ)QF!uIsH}pthUeAiDH{4;tEv`lPI3ZNA8rga%>K*p0vYe9FW6~C z1q&++7Oz;`+P^-}Zfp-P99l@MF$Oz(qilF&yFphnPZPALKb36ecsn>F-abfuWVl&x zQj<$myf{j{V-x&%jexn`xqeZ7(fB!0J4YSHsLLY@;Dkr{iR4qw!Y8?j+BVm@S5Z$h zp^?lY&|+;Rx|N~HC?IC?O7fU88fG9RlWr_c}@i{cB8 zL>HaKpY~MsPqL`wUz7EHr}i68F+-^z10)G}>35Nb#A91aKN4Z;1={>=1V^m}GlD7# z!qSyykNF;X)^$pZwYwsqsy1iERG zT=TpW2X&EI{79BhmT0#XqqrPtp(w@_hEZnG-=D$l7cD&5+rYh@^<#XUiP-8aJ&Btm zCoj~xpNb2X-{x11UKhQq!lY@rJ&&?(cUxH^Su`!r{$8{d?FY$qvzU4LF6ldrlZ+BM z>oS_y)vu&1ukNUo)e4Kt8qOTmc=>u(x8D?QTws#X8uvRx^k}vny)$;-HHhXy4PXa} zzB)fLQQrNQK=)_G;2lIV?-@|~x;ErSg8VEzd<1(zj->*!xL(Zh;&mqYFXUyGjG4~| zW6M8S&7p+7wpW5dLq;NhT7oTQ*b4Q@vJq5uq{9;1R0T{v|(d z3qLwXP;CObmus)pH(g(Vr7lj1N}i2mlkN?JJ<| zcI32N;w7_u;$^W+gCyIN!F+pZ7cF@@IZAum7I8hPj76XpzV@mI+y7BXi}oZ~6S<_S{ z07=k$@XLr6#wRrnXL#STMZS-*N0j5?Ni`hflyxqRX>s&c@;}tZ^eJ)lj6vp&3^EfV zyq}r|#hyF;euNhMY(6Q_!#f(HLhSyrq%R`{#n#+mENRk7x*3TLIzEcnCg-kacgS0% z2{btaoJCJnDJN1hbWIX9AyskH&L|8s@_-xLg(&B;nLvl;F6B#+Bs%*82+jlKw1lYf z%p#xm6{Jr9LnP6DMJOj^?=yuODyuIE0BtA~g$p=pEFT8JHKJ?IR^!;eOy3h`7xVcm{+`l5UkM4#upEVF(4O{ok@Unbu}MDOuI|!h^xtL44_$X zs8^6rsSpJj(DOIRma3-A+*M}oN|?E+fQU=z%OSKC$f09(;(0U7-lPA>p+OvdG+J_h z`gYfIq8keS?JnJUJ098{W<2SFoWa0Xm^=V*e$!IYnjSIZp-n-BNm?B36XX5vYqTbW zU`MnxL9gr*TD8=S^rU`jYvf86Q){1z!(g<3S8WbYlmfJ+b?Rw_0qO&fWjz} zUHKf-1?8d7De+g)6QecTdxvVtD$rCI)ZLiRK z4=oMP;r>m14wq`IJt$vuO;3iJOH0fNE#YyqA*txj6>p~Z0PC~Za2gb&d{`5=+RMdi zOFTk1c#L5E=_UW2O}fL4r!kQFPvFkTaw^wnjf;%nk}SM};;I~Rtghu5;$PXLUhFba zR{V=vZ%%RDH(Gd-=MVM8qbd6c%0}c1@>H&4{m`b~AShr|nGw#N!B`&JhXJXGoK^q= zvZc7#(TD&EHTKbIY#a&95Vp!7%Gd<6Y3FQ3b7?Ic~{xxNW+GC+!L$TV#4+2F$7#GjBQGlT4+Un#-h|i9Q zS_`wch_}fSocJ|7_* zANaB1Xm02*Fc=3+%&pJ%77>T%_+|kGu|^oI2a;W=tl=TsB$_nf0WRH5yju9Dus!Fd zUB<`INXb6S>fgzxA!gvK%7m`W?ns_f1LK`_RQ*`rrfP94WgkZF7jwM6kG87>3*|Aa zaXvtw&>K{{`=CHx-S+SUxx!FrF4~ON(_JYy*3o(8X1-u9i?#{p-+M2Rxn`=p_>=O|n}~rfVp+ z&&ph(Rp}1IM7e}0rKbBi>!Y&&%#6}HK9nore|+DJCuc8~O$=v1c@OF z-CufS37V8RS9LaDL6Z|aO*Cr7Q~LE>=tm!oJvK?m1ZtspdZU((J>8h0hYZsFUaNN2 zq#*;f)|@ifTruWAc#up(J8L|88+D%<&zEk(uwM!CxSqJCVq&0wfC>vF8mn5DRKyC_ z6Bt@5VQ8uPzQtST-OBi-Qg7j|o#;hAAu3Q7G0~DewI!Se1slDTr7@~oSsN+#)L5zg zO#kj3qu=UZG5~370%b`*;H#d2E=VjiNH(oQr7fJ0M& zIRLCl0p>7X-UITTNV~;Tbdaq60m?7ZKM)js0Ce-V)jyb-&h!Cxl#VKl)4ixd`xK2S zBmCfg9hG+r<<_}uE_x35oFshG*xp95Gn2Q}SRz6D)a_=#;Ix2%_E-kBZfpHJnX7LG}N^Eki{~@7fXRzQGpqtVBDFQ8NEl*eGi0m}833{`8Jhlz-^o z4?kAzx@rps8iS3E!+F0d5Jzh;*EYmanv*%PwLuoA);Yu6#n7=;a|MSP$+bXQ12~?E z!wmb%^9(#yz8vxvYY@K4j1YCPDYGTJS&x% z^X$}ZSK1gBf~O17X&`y3Xc(KEDZ63V@;Y5HJTpjEe+!MyJiyG#cAYpRXM8zU^=!dQ zWtmir;>)anCo|_28r`wirN!DB_=A-DM3rlU2+ZNjPmI+=(Z-!zrGOE|oddd!EXK@P zl?IE(!q{R-@w3TpegJz6hw~mqc9>)1=HxWtrb)s$`7LnrtgVdw)C0Dy$rOa9Zckd+*-1_tNv`GTKB-qV?DI?y{Z; zo^(p|`$p#kTlpZOS;j4AjMH#%$r3zX`yp+#J1M$r4fE1WxIblPXgyp*E8ipZJ*zid zcNBoVKZ6s#QQ;b-%>m$&6krYjm!<%70Jtm#m;=D&DZm^6k`!PL08dW=<^aG>k1y67 z0Io~{<^a%60poVI@!iQYCL3a*TY$uLyEQR4W;WqfBy%D&i(Ahg>4O+@8TB1l-=iBL;LUgGZHc`vs4%bq55G zn$xWc9yOCYFnC0McX05CtnSd@StF0HLoPX79;-l{Gd{9PGhfG%9x9OSYDx0BqZU#t z3G$lSG1+Y|OdrWtJGhb@zQNzG_{)<@3uy`Z7x9PgOSGB48~OVIe-HCFNSx2|eHDLi z;qM;)&H*Otv#_lL{3n$-ys%AzbcE=d}2L{(Vu{x>x#qLCN3-{QaWNy_pb|;%# z^@yE_U7b|DVt0bMRnOR|t*Vov0~h4Xz1iGqw(6wl6uaZhExN@{Em+m9@#D?Gbquv! z%68Xh2M2})g;lv}?9`%Zq{n7NQdqS$(B*K>HPpJgzn3%ljDjiJliu^{+Q6*7dwG^! z8+bN$YKUB--?f2teYeadYKgUhgMC}DV_`7Q$gxv9k)S;jqesSh&UsSh!{GK zl@q$Z?J4SX@N|Yr__G#F>bqc`lIQ zQ9$Y|On%51NJ9-5uQ^7r@rIjFV$H3w_Q^QRHu~gd=eP1uk>9qNLm8qycp35YTrDyw zZ6DZC9nj}y16DVo7OYBW%@kTFYOT(r>TL4+UZfR+__Q{@b<^sYLTS~I6l%j&q>D#k zJ++{{zn=8CR?{=*(^GOJcT-tR83wQ4nn~VnKx=~=sQi=E2D$bp5W>R~Nsn<_67<-m zoyn>0diE4-yQs_)QeL;64QF9mZ2y6TgSLgQZQR*WWh60G*b1g9QQQH%M<-mOH|Ew#5mm#Mn zJYvmZ?NQ4z@XLGfi7cGWl{EhREc|qIEG?g)Bp(McHaU zoxdt4iCG)(UoMl;ix+_$foUD|$JD zQO|||=i<>?8_%Y01i8koqmySZIPYs*=I{sTk`uHhU6+ls_-rB!lzGXetm^Tn1I;Rb zv-y(%p=<@$Un^QP*qVnm_PLm09~c>Il^1a1aiQNeR{lCWSl`RPd8;mci7|B0unYGI zv_ULY)JAc1j^EnUCUAZP1=R(%pR0bDH@#xXGhzQ>%`I!vd2kJnhfuo(w}xlc?6{va zIg@fWSlRLDTz4@n>Mr7^YL^z>JLv7nJNc;6SJfyWu& zqBP$nnee}tZ#JF#2h%ZlkGh=O2;SO8uzKy!_YA9-I}dp5&Q;ZhwsJRcF#p?{u_C`JCjG3>KlA+Y8##3ohf{BS6}MhW07Yq!4gZ*gpy zw`XgmOnbh7G-oMIV`H_ku?7cW-(9DG8;e|+QG@0xYj3@rgAqG9ThL{ViSI0AuXpDV zxs*n(7Zkh3b`#e?>79#x0(#HHH;PLh%#rc>Qur>VY!wxqv38&wHW`NZD%qLh*}`ck z3h46=qczG7yszU~ zUwdupJOlmaI{E|JI}S$x3>_1nuMFoGW7x8ny1c{GWl}R4FYF#5OnlzP;(e*&H`Hf5 z0*%o{uWeRaC22=E(c)1K6O7AdMGrk3&1EHH?}cj&Fg>CaqtU(gVKsafg>w_1^8Njtf)0BNQ8V02 zr!>+%RkSw4idZ3PgP&OdLN!XMZOGP8J(oYUN^JDg=zA=GHdLR^?fY(`*3s-G^?Vp_ zyt?OGUUbjs`F1M0YkI!fMfc2}4;-$==SLi!HSNdDWtR~_amqrT26EO$e}RndI;CAf zB*Hu0e7aA=!dL!&FRTS$`F4V>Op7SPRvd1t1yksvcf@VB?*ey&zV=MJ7GfI5TQixrwA!^p*93hm1XpPJ~NZQe=Do)~ZL|R5^4wzw4V`^x(h!@nzpSWVpaZIeN%&x^?-xL+d(H z;EG9^Yb9I&$n#fNzqa>ZGG@KBeg~hF`3?<^Xbo{U43~1LKk{owE#qc)BiBpolpk+V z^JEb99p0-Pt)+hFtA4VHMo+A{@@}yB&%pNDTyh-8Bwp69xk?w#kry`cRTP)XP4G_obJ1N&4z(gH0B;aPGhu0L(iR9*|4^}e(mf++1*PIT)&}8aB#1y*#g=x6wx-glfnnJ1{{$> zm3&>&tTT9bZTNpimv?9BqO+R|MVHxm&dvT2+Ndhqp-ugNLYuViA1|&iW~k{t0U384 znK<(Fzb##`#)!J1&RN#_l)ybo{U7qr+k12>M8AYiPYZOSCvD3^;+d2m0vFM{#)r4z zA|e)c$VT?3;o^4XVZn(uQ%GQ=N^oBYyCO|&~j^^ zXh*Rw@X?fAaEc4dbk4H&tShDq`2V7B=G~jX$M@!57riSbF=Z9>4o!4XpfNpYxPV?ROQNE@M?>ByQ#FSm+?t0wP6FsiGo?sFVan1p!6uef_;ZXP)P|&%L`r{Qkbb|37){ zeP+&_IdkSrKQnVC5hVd#jj3q`uh-5JX6fHrhlgR7j3!X~wx|d(3e|K-& zU9#?iLtkFoF#L1souVf-rdTRv8pN{@h&O0U-YAEm(M!?%*cBG?uc^2s&CIn89DyzN*Sa5r(zq;Ky^@+(OLRQuy`?%c-^L(uFk?imc_gF7 zghD<6s+TN{X#qYl%>?NDio>Vg>|aXF<${+}lUNpUW=5=#{7{}1JLXI5V7k(R{mq%?u!KAol3JSME) z)}%mV**N17vzzYbLeWnzH4&$yjs+4hz0|w_STZkYr=L$ei#upJ{&f+KA*Vt-xGgy< z`Q`%$cLba?Pw3I63J2qjX9_KT3J^y`qBcKAG0@qjLyli1=iDXAMW19|t?vc#_mcQ~ zQT+XM{Jl)y!I<1%{|ybTLC-{H$4X{tK&yH?6t%n%djL^d%kR!j*>tA&I326v-eRehiWAk0&LFFTGIoFr?ufp>uhh7 zvu4r7ba7H@ev4`7F@1|92I1?>;0MTqc11%O1`Vgoyi9hzD@ zI&>Epsilf{)K=iA749Y|x{QgNW2@1Q7VVS?w-x%}!l3dX(p_;AthmQ~YD()fq;;I? zNzQ#6sIMYz?sN!LehlE$<%$t< zeDp)$x;-`8JN?+pU51@&P36hwk1~Edgc~G^`@tQu@e+GsLV&Dj`r*fQj5L=i*0d6 zzKgqK_!5|GiN1#$#WI@FC*-2Hik^lS#7dE<1L5w&@dbBgLPOeTGdc{S#2l&mMBgVa z>s@y?Xaqb3%zY;t@NpktF_c$bAz&lRJxN3#n|XEg6utqN+M6mj|6R<&>MY>1e`^Tt zQIVevPUQzgXfO3ePb+=QIR8-J)oq#csyaE?l2OxiwlwHz659c@5r>pqkMf6B%>Jnb zzs8?7v;N6n0>^3F0@7>CIz@6HS@nKP!cI)fE_w!zmfu2^Gdl}6;}|VG0soaB5zxOv zA?`|D|GP>Dl(lh0eAO}X=WU$(2P8j%03G{UX+2X}e~Gj!R@Po7u}O9v4p@bCiNHgD zOiEb&{|TRgLiDUY!VmyM60HI+TFp;)zQw7)8^M}zOkj<34q^6ig;pf0VjQc@_w>2@kqiCI z@laJoYK+bd+^vJayIU#<9E(qS6S`N=LPsx>DpTyj>@5Bo>*K}1 zFtnHAs~!!5zfgTuY&SK}R>210QVvfw`CS+Rri9kbKgkw0SI^0TWAMlPX;8GYv0sM9 zNvda+=P069QL;Kv;^gh{rwFtHX*03&QdSuK0`c(b*Dv{WeGTa7^?vzXK^AwvVh(n^BWg)tl4hRMpwTO`R2X zn|u2+w6lUUuJz(t2VMLt(BpB5?~(ipHA&5e)HUh+>#g$Ze5*MC+?D{$0pRuoV2+yZ zKj)r=Mj+#gzajyQE6P-&wu5la)m@`kJ=ol@dH0FwnB8M}2z&h|n(`%fzMLdx4j%iA z2{N?*)KUZID+!J{YUgUq`o4R`&}_ftT@=;3Z1~E#*7cGWc4af|_D1VkHY!A4z`u~rwIm6c+E;Wh2gdt1T%+^kAUp6a6s9)$DHy3{H@zuU`T9mo!R}n{;JD2x;!|GkQG1|b@ zAGr#Zf56k#x>Hri*+%s%;pEZo>em!Qj=ORA^Sj!oIK}e1Orh>~8luJ)F%;_yfe+H=({gq!{jpUbO4ui$M#cf571z5j~ z!2x9kP=FzjWF|MXa;h%BbvDb8U5`mmJN<^b?W0x$=FM-zZK06dle%mLur3BVix9!~(~0Pvjz zU=9G^O#tQq@Vx|J4ggOi0CW5(Z4woBAX{B42PnCW_6ND!y_-$RRyV=ieQvhCjODrD z2IdDNs+X>#z8~gPx(e)g#DU|FiE4M%Y*mk?d+ zj!w*nTqIH@&K3NF@}N6w_7j??Vk>9O@iZeW`DRs7tLh-pwA_87>6|8$Xo*BiKGBw- z@+%78FIn=@tGL-IX^sBr6D`#e<>dk*`9(z7;@j__x4*Jh)291)TOeU@QPZZ6csE_c z_mi%{8~~ma;9H>^By&SW*M~&Pv%NQ3Uq-+;!klvKXYn5!1t;QD2_K%H<+C9P8)SWV~lbKB)S-JVmAWFT|5P zLAh>F*$)$X9W4~v?l@>^I(yM#kMFp$ijgKTHHlWMasxi1R2#**sFd~E*nW0H7MGdz zG(}ebm#K>zD3Gw}^bjmhRgtR{oylxaA^*?dZq3*&BcWPH*m>GZgtEhzW1uO9Dyc4NFP-YOn-kr#Pd zZ7O39tf6C(v^5F^HJ!>%@S=a|;MrHr5g1nT{8oh=R_3VQHp|&tDF|FNa7oS<1G`o} zak)3!%_q8G=FQ%WcbjlVL>B!3liWdjkD1e6?SSkSK{z6u=o%85;yUD%89j~mr-}Kj7l|g7A+Rj6fc3e zzF?_PD$&>qR&!#b2^$SPN;6nJjgx3tl!{Jf;Vj`|3}&(qA+Iu^Zl!|~C{qDt-J70$ z{t;+3MWF=>Ej0=)H45(}no6>}wHFmuYgb#sWvw=|Ub52(9H6yw?DeU4a6+Qg*%~uU z`hDFU!}rJ>15#;1`5y)!2-f{%qGnnY?0sV}O>57PlUpBn$d?Dbb@1cY5_nhB-0hMv zr#tvVq3N9xu-2;QA=njT%B(V`%#H!#*&L_Lc&9tClM$6Zve3~IR;MT{ak1Krp?MV} zR?xC+Dgkd%h%9-mv8(W^!fhzN7HeEBqlIaN)t*JKPRH9aV!xw>84AO^M{6{jp8*e# zW&)i)ckPUC#@z0VZ)SWP*XuH|D6VcI4qpprG>e~sy2`NeWRDfiCdl^|-dKRDh%ETL zbrsRc>`cL{pj(H}@CBXdvpH`QU=b&{oWHDIQ9DyRBT==vX}Ep)tJFCs3T!EV6LIU` zzP{784upm8+AtgNY=lWV?p4+?7`W<-q*wU}bpk8s)pZGAkgu*M1U*CJ`f<^W!}7u9 z{o*=~IfxQ-@3UsNz))~kx6JPz#@yi=&edP|f0Z#1|s<+`ODtldnK}~C>{fJNA z5EYNwfR^?b^6;z;ze(RPL!GO@7+JIKAsk?`h@XCgli+b++C{+c42ZLvuPcJ-L?6WkPHSGzWk=3BVkFE@q9GRMT-VR~-T^ zoKl&~r@Eosejwyfrv}1}1>VHKHV|%V&So-L-3;g8pOevVb9~7#HV=0^;0Zitz@www z0yraS!#Jiy8^$pas2|6M6%{L%e4;J+89=XVSNV&H%@-BZaX&!4|ET)2 z;VdQgAoZg+KwMXEsiLHl<(R}XyKI{E9pdYWDt#;?4nP6F@v)`sk%^dn(ozvSEecdg? zyIKdB^na%!oW1z_Hh=B>CI5;zRv>=upJ;BH^nZfi4D8GNLDK$doXgC48tbJmCG4q0 zJClX_dY7%4>0F8hhHcp-y56KuQB~=4yNC!@koaKwh~JT2`H9|H1NV9y)XdqF=O!J0 zy`)h%bgYT4ck_8QUGE9xE}kHn<_Nps;;^SiOUVMUXY+QOYYT4d` z1Z5_R9#q*UgBk5>p_!Won_NiAv*TP$X3j|-`-0OiPs%$Ntb*f@)#c%d@$-;BkLewA zR~9RtgJ?gJBv*a(5Btlh%Vh`P$VCV8%j-MtAik!h%8&M5QBx)blY#gq~Iert0Hcn&Gu?R|?ZLw51&&IVm)191m%qF)ms5N9gw!yPy zu-;*Q8WHje?>$e|T4&%H0aDHr${|FqTg?I5=Ia06{3USx4zP0u6 z0`gzKr*ZDpuu~NgHZ!`Bi=2hZyZOD^U%d06^Nz7yjn#gQ;Z#hFEW$3k1flc-`eSC&^tgEdfQ zHNb6LoI{ULc!km4Yifn38&GzpoPEjc<)2N?uCJFU-s}Xf*Q0F_Y_l|}o9)3RSNXhY zk4oc#%0{NZdy;~pBR3}zBii%eKy8@Yo z=*t-}>Qu!0My5~@59mgMdD7_uawa>5quKCLqGAmu1F@;Qu|%kWY2ZZ zLN8Cv}r>rdZPCOk$LrzeAnb!rqUAm~Bjw2e#O3endn@vYJJWYx)>OBuIvH03Ona+x*^ zQVzZP)KsJ=l7Pa}>}6D|AB@9N#(mgjQmq~j=kBB!A!WD`;bbAOeo(tP2_C0vI>&`F zNzi%nxk%D-o{wB4$;5PXk#g^hMlSl0+??}LKg5K-11bjkFmP1Wr(}Z3uYwJ9s)A)M zEgl>Cs(DJCHw;@;_*?@%O$hK*4F%^lZ+)5#Yw%mZmkDkKWqI~!xe=jx+}wyL`UonE z)5xdSGq0DFV^W9u6F^v_I=1)+QY=elEW-LV1SLHN!Rw60%Q5gWQ}sFLQf91-^rUXe zO+%^qEMku^Nv0O=b6jvK2bmzTqNduWug>R~13-HMFb9B+1YiyT6B2+qZb}O)=gv^+ zaFpgO`!>6e+n3u)=g47aJ$3d^`xY#gKL(J>Mb(TC>PZHN5q^%fX_sUuDUWpb%}34j ztOfVzI@C9>@;?)@wARtypL3|e281kT(yz;YLPp+cVyV2-)C^MVM&nFADe88%E0cI5 z>Icc&b%tmXk-R4+!ZU~TJUQ#*S1Qky6C@0Ze}g3Nwf(QQ^6aARezgacYsq3qO9{J= zk}I1wClZe(u;2IkN!|Gos?LSN8ch25wF1VMc-`dB5phI{u`S)`OU5>L0r8>=r2RRf zlW^%A(KP}y8?kdlO93Y5h_1^_CDwF~XwT3xSylGqnj^7HO2lFg0G$cI900l!fH?qk zCjfH*=t%(P05CZLm_xdne1L_Q;5?J{O0vPx7FS>N`RIH}3#onDzEA!lDQEO*X{l%z zHd7_6oDK0Zi^6Vf{qEGUZ zX@a5q3;>_ywXsQ%@Z;f)*la4)5H2NRdsjHxD_M_e+BKnv?s72Ww*|)Hr9uwf=fIQs zgg;ZD86MfitfhM88gODG;0N={tZEz|)w85h(~n6`4`hbfSaEX4fx6-9 zPYGs;&ym+qKApmbQoI^$Zi;tlinn2kSA%WzU*lbt(%U$tSA%Vm!fFEBG=*KB#@#H1 zU6H~z7fcd&CfLs?zyv!l4ft$|yoF%;ai2?J!+34IsrB~xs`uOgqR2++NJGp=P`2(- z{O(SEr2AR^75N_KhsL?D42tZMUTI%%yP~(589vMG{xx6wq=tbti*j+PNCsNG|>!;oQlZ5|HDWf67qn}2U^+kA+RV&%ItAWH+P zJ0dRK(zf38Ogxydwda z1HeuRz#IT}P5|ZruuB3k2S>iY4*#0gy9ebMTv&tT-9rXq)t?~co87}>(H=37<$oWQ zEB|UyqPqxnUlR&TjjzaaN9?&%p0CE9yXCnj_S`GaedckG>ch}y^w;By1l0$KE<0`Y5!BdW-s65tB@MYdHgPKn`s*hr6Y?j1nu8-5)5GP+bjj9!AAWbtDY?#E? zLHX~(rC*2@D!0X4`dsGY3{LT6RnKw(s?y*h-^a!IqYn$qv~Ec*bP}1*x_caF|Hukh zXdU?ozdXer%dhAudzs~ecObBfMKk9%OomoJ*Jenn1Nfsx+WpY!He0@0!4=$P$+;ej zXy@on|2#A3D*5Mhx=N+I+Zs!i90w?QsZxd;*U`6YH>T#=ftxY)Gb?V$TCbriTg;z`wHV*6_EAd9ISxC2RrSvkk+1g55bwBrT{ zV*3s#zFrlBTe-yZDU*Yl4r<~AS`=Zuk&{t{cDs7r(Xx{^pk{)IGCSCi@9-c$=-B+z z$5b_O%3hksP&a!Zf0yz10)H~F{67A;5*rLZ%TE`_MeRN0CHAy&+wda*b;{y%r+E>B6E4t9-13nW^OavvL) z#4c+faYh{psCTv=<<1Y-4)%`(fYv;s0^ikNNLz zFL!mdC*$e^-kN56SGzq@)9eq$4o>NvDtZXw-N=&9d-!1FtGBOm zw1iv7lRZaWW68YjL3u$Ey>E>XPxRF{PTa{$#IVdhRZo{HhDzutU%CSGET`IqHQmir z{!LCTpnGE1#4Ku8IDSBTP%Kr>Lh#*_n6ynA+?Th7^nMN4`q)kb63Fw__QWvHX2(4sfRD8OV^Mb?FyEzMwx>i$!LbbPoVi}>wA}eOA`hyz6hn27kQoS zK07Uee6gdl?NBhg`4ovKsNPw~80#}N zgT~m2w1G?4n8!b)dGz_y#*TVy`ZRVLJBFW5!x?R@+u9?}QJkAARb4HL1|~(h(xNCE zg~&2fL(v^fp)dib$EfJnXUS*Bo_zDfGNRF-+TBu!?AzMaVik_(Csps}GJ%^FgNAZ_ zlOb3W4Of}D!c3jYo2B^6BpY97w3xVPH5tT=VQr9KhjCq{r-5J!HPe?^ z;yaSb;d}fTV4J4aBqYLlMAOtj?{1Cus)waKSQO&LGdkys-r(oBkj zpQx!8tGhF0jQ6qkkh8D4rzVl6KVxe;xZLtLqOgXocj5Wkp6~5gy(`OfL3s0wUCfv1 zQ-;{A7WHxwz^?$fPaspyjXpz;D(B#iu4T7zps%CuL?^QK`!%h>qIU)NX2#(#N znO8#)mt!}1wx}V9St|Y-_Hc%4h?bF+D)n>usnZE<*?`r7DrMzg8|E^xkUmO!>3rzt zc-Sm150qLi?#8C={De^FnC$?wx?AEfzz=ISIds0Ih@3uab)56wNpzbBJKGsDX;YM` zOnjJK4)LX*bE!CdSZ!y>3X#aE7!0ANK;%uhu}K~~rE(q;a#rYJMLRRjs)m<9E+3r^ zD5!iABEPm`j(C(V9m0Fz0-*O}vRmTYsrS152(Dpp=+zn^Hbicii-lQQoZ#BY<)K%~`IZt! z<>ItB&&(J&^y}20w->Ue2x}3#{zVC1UW=2V*Ya9z=+yFzxo8?X+h@*QGAw&VplRmB zr(xzap=(0XCN$l+jA>=eaGS=#?X%gGa2sNg`^9l0+4d%$MQE zUBb7_X!xlHxcq`EJf6#~1%D^m9bN&MA^TXPy$piWqZ=uDj>i8NxXB!GN=&ya@TbCg zH8^%uz5r3Lx)T4Wu8e(jG-A&)=W!%I&33TC+sTBCX@=brD6SN5L!}e+p$`t-`B1Fm zn1=?hO4ofFu9^DUq+Wnlg^0F= zs5s;)7h`B!P~NW&gG4!1WVAGa7cw{-PK@(qROU>SHzkT{&Ze5J;RzeFN5=g*Y~P#A z;v2-r&l;^<3Sa#mp?aCzx$5P7tSoa@FW07;@=jStVrlQ=kQ10h%X9?o`R5&+s#6Y8)Uc1n6S{Er#`MpE=T<%s=87x%8C)mM-QbdVo`h=lDgr{sQLxtP)xtr`p!xGf`5?d= zGIV*d(- zxEmnoxI9v>-iVhe?-%q5mx;2~-0DppelwrpBc&gKvf+*hbdAcadW&#;q;NzYcPl^D z+XNobFSmFn3$w-Dj*pk-mkH{=$PXJ2G_vMlo{}e*K8mF-Olu6GQl7 zr&5i1Fo7E2Kk%S+<{EjJTF(Q5vCPD`gO*vAtRo-(6H=oz>v_Brj!m7qDG|j{hEeMb zZ6ebN|5iDSUDdu}sU?y!7(b#`vzulk#^oRy86i%q&p>%Er2U_Ylx+=b0=06?5S(65 zknIWQ@s#I*LIqXX?9BtTUcdH~u=w^1U)DYGl z4&Ju`S@AV*XB(LIop-;)KM<5Rh%-jPy$mE4cU(r%D}eIRuM{A|ad9+?-qJXN zZ(pYDjYkqd*B>XEDVm%STM-YXTN@g`5#_ZBGIT?x{;>nwimv8+lR&i=g+;&1 zdQ|_>b^CVodps-e)f8#1F#3Z+cn8|(<)c5!%h{tp@uj-^D|3Gq(C*XfF3U5%eG8b~ zV+h?}fV9``4xo)l+6{~I8D2-J`y7Jb<~VWf0WO^$0PLL=PPI#N6h2qOZD)UHPoM+Yl6<$3Z3wi zl@HhblDn^C%H4%y*-2en&_w-8(UQ4@Z;Oo8s(FB#ql^w$}4GG%hUB;#b$dYB88O}eygqg z44z6M3Ras~3CzvBHa@}NiAiuW##w08PT;`ck~lCK4-^)4SUmdan5y;Dcoj$@6ZmKx zu+Vf=u)t`d!sz=I7ER)3aea^0H?vclK&V%mGu^@0@6|2^ zk=gzK-CivO-m5jhQA!oh`{Xee@Vs9h;|tFRF8a5_yi5 zC*AwiEAyYQdhK2%>)2L*zMrL%R$QKyu{*ji)DXnpScFwI1hG68VRa2b*%-U4{9+A3 zSzGg_@lR`r%J!NOe^x`3NU{rL_fjoP63T|LuUE3Oe*dPN?Vl#Juc?wySyEAD7Oqxu zgr!o-gjqonmI^2n=IdL|rLys1wy|u*OuSSqKCHHRY()=3Dvm}3s}3NfVlaf-?y|Eg z0zxW_Mg*%iQrDcNHpBO9eGgB&qFhx0>`WP)@Q){L%N!g$nnRu9*~oK$#EB+XMbLh| zoey+Y_=~E8mE}OGcgy%-GC-?h9cz(y`AW!{w*4F&qlJ$Gt8~(z^si8e!`O4#VGXey z8D+m{o?H0{Xe$(hyUdmI@QCws<=1^Xa{xFm0hj~8Cli1<0Gyuy%mLtn1YnMrQ!(_q zkC93cuIp>do7=jx8~mRzf1g{aujvUFdR(MpBBEwRrxO@c%0*|$bEZYhMQ6$5Y38Dj z%i{^=qI2Z&RMj_y!LHC9)tDaJn2;snEpwr_Ow4VFq0ou6Pat|d&z<7l&?x~<8^tM2 zWAilGFe2L*CfPPercj?%p8&cW)F($#^h zTqM`Z=k0W%e(j`UQpVz;3m&{4Ah*=Y^*SQhh~atEp5g5_SIiByX^(dR{6cx6-}rAz zj{o|6VZ+Eug#2mkB^|?8tX|wIqEwn5_-iXq<6O{WT)N!&RYI1o5@)?YWGTIm@6alL zXaC`hdW3EM^bFX?Y(#cfa%;`(+M^dbp2OZ|{uP5Tos$Q!xn|V+4^y;(B-komycCcl&hbgJ{yee@9pa8KJC5mf2ZVM6TjD`rzJj; zlz8CM3>r!YmuXMXSkY3;PAR<r|!kT54G;MPCFVGjzAqa#V`GTpyNHsrjeqL6(U+ ziGd{tyxc!xZm);cPO_mTEjDTj1y zsZ1pc)oV#4c(PrcHY=o7w;?~#mYkt!En~dmJ@~FuAlfit7AxNn*=fprYP_PWuyk$m zr4!7Fb>FluY!D3YXWfY9U)%9_sV;bGxoA3sS}}V2O{n^9hPTH1*4xB;FpKBc`(Gp7 z$`ioR7gukQs4Yj)3|J_P7QRnNWhQQS3*!uzo-EXJ7(GPb;A@!(%WsB&4Olnf7%hBH zvDYGY{|be;TQy*DnxgDbahA~!Hw8M=akugwXWkNyW=R0$32X!4Xe&mKKq!$cPY+~X zT|uOA`|1v+4Fx*d8f4agYZJeEowDVXW!K|0z4Srg-Q#60SXNi2LGocG$bKb3x%U22 zv<`GSN*sKQ*2R^C7#N$c-T6ATQ782}S(9%2PS2D2+(St-UfcesG*MMs+CGKF?oz7W z*rd@tH!iD_ba%?w?^#H?U-!C;vFXO?tVgy)j$`ngK;{*UTJjv$8gr~T&EKY5C;7_- zA0nN5*CbOTxco*0Y8B)L9HWJAtAg~CbpHy4xZ9{8%fC>M^SE#Z3`ZgEc2y$PpT?}G zJiTj)=Sl0&b>J!PTYQd`QR`dS1u#gjmn#)Xf! zen)-eO6cF`%T;_ZKR|%Z!VlFY_wVJH7R27>`PIdOc%!Ze)K=pngvDHjsA7(+uYB! zg#LwoUSW6t$);R9u=H^K$K9_BG&Y$j6x`p{J$?tsil-rnMeerDfze|2yr|h2RLO6> zP4eEnKZB>bpk?^(Q|QDr)&HSbYpyio+>zhACE85&--p218)9s8A@CGjdHGL>WgY5|af}vTQ|LTG`&TH$ zVcN26l(25y$+lN7?}PIsc4z4trZsvd&ohi&>P4{J-CE>@f-RIDkBGB##+j_nA_x7y zmt5sXhK1n$u>DPyan7C=2uD=099I)52qtZN<)WcxGlJ3QFCWrn9Rc-@mcDaQS%>|- zxoB$;u8t0uXz3qR2Mcx%IXcup?w90)a;?dWsA+&kaAINnllOWEk*#~ zNyuIgQtl1T(0O$aKgRY`=LRRoZ@4Xq%Yq`@38v!{usd5$HEuK2wsX2%?FZIcnDs9m zg)j8Y4a@Dp>SE}20sKN)YJ_;Ko{aX|j@X-=uA3dsO zodyo4VwFG;K4}A}8_^Z3)6289S$R7OKuDO*(bx#44u)XTTuz5!W!x)j<%7`AedH8&#F|rS^IfI_* zOo=h=R8m%ITmX|(C8zqRU3#X44V=VCR+2F<7P-1FaXEw|S@pGYqb<&&UZFCsdaA}^ zk7W@nph`;hVE#Wb>FY~NxPte#t}cacKU~2JcBfkRBl2WJ|G0E&fAl`~@5H!(Jjpqj z%Qw;-Tl4Eu@uik7Jr0McgNM^H_va5)qiW|5t*T+6v$wK8aGg0+DOdG_`vAFBD7X)l zTZMr8Ah{)BJD0c!haVfMEWsm|-eO>xcQHPX%7%9_1m#@}tg@6_3&l-eO!}Jf;%z~O zPI}w%4jwl>LEk#PTD)v}|2f_vl-0 z@p@Byg_S=Nt11GX%#%7(~61nY!dhZ<})PULh>!i zQ%gB3sD`E7_V1-UDbwTlgJf_ae;xekpWrw0r*`=R{<82_@oQeR4`r8y_W;`;*!%f2 zZvm}qnyH2rKM zT)l1rJRTsn>9fabu)b&&Lvl>?6&r5p#iKdPMr%$hcJZxb(eVgwc^-b~XE9r2u^wbajZ_-mg_kTl=VeQYFqAt;zPcE({=;H>tC0PBN zf+<)@CM3{Da(H+X3YO~q)Sso-+Pic;$)%t zW_@}k_s}XgFeSr0QdJSc%2F_@|1hW40o8xX*<1aWsHvVx0TS!g6qFr*5RW3FDq(IN zuHN5!bJ5946kEfh6nzMf+0}sWaP>y?oq}^0L!bI4gQbhY`Utu5VdC_-Q}`W7-x%0) zk+1KPtAnShDj(&lmtD70L7mfrMrZ_w?P77&!91qnQQJlbleF!|e0aV!o$oDUhjJM- z8e3hh4bR%Z^MR#nWj?lT=&D3QUjAHRtXpeLM{_;Jsn0cKmbB^QY>(tl7wzSq>tVq? zmXxKgnjnhNa+3Eeu2{J_hzd+0z0qj|LBV@g;0eYUQ1Iz@1Pc#J`jni#oBO!xuDx+H zK(SC|8Pl|LX2tz0ZW2n|^n0;2`tmcuYU+Ai!i#+gzo7gvm(`8v-u^_7)f8;Fk!W*G zZ+W3z6<7PkSiB3qLz?a?>Z#iZ>p?59>z8*|r`~_ddrj)annB)On|hn%y)N~J@?M{M zbMoGhdh_z$n0lM>R)%R}%avqnf5Cl$uxJM$!EzMr*45Z4z|;0lu$4Sphl*2kFx zz@!9V4gj4Az#O-wyw8(-N$Yz)}`@5$WyJ@Bitjw9Ou5xAY3(W{AHd2C1)UHHvS5=ba^4LA zSM71RNRF>_REXC(inq6?bF?`B1D)eG2(?k?_&xZm>_h+y#Of57LGL$}4{}O%jytO` z8gz~tte#NZT@*LLu6&gGnCKkQ6=9EPMDmEX01L(AJNcxFN6icC6pzK>Sn)Vbd@R#% z9A#X^qllq++?7v8@z_T!Q#|(K;3Xl2dc~tc8WoSF-tHC@kIcTk;!%U37-n|AIWfE2 zio7>+x_V&d>nI+vGtnsvBPbq=W@l$^Wk>K^eaVh z4zy%OM7C~uK)6zk<=jxf4T3XXDZ`!N2ba;Nt*>US{u}M~r_~I}s8`{DjhCtYvRH zdBaAwDVj4|4cPXZ(i8of0}>#zCSYO_VqJBu_-&Lh*fH4eQ+`a;_;ty(!E^>KvrnNR zjcThoqVme%Ya4EDZZApm63wyf%%J8fPpWS3Lq63mW`j+;SeuV@@cRPxFIRCv9g%A6 zr+EZVDb9XzoOOJhb@^aG(!bxio(PN)o6mb;M4{0+l0cmRl;ZyNDaN=fh3o0nJ=#@A z@LZ4v>Yw0J9nko$na|8!ysY$M-potQ)DZP9v^8cdE9KqqZ$cV!yfel`ro3n1nu_>;36{1twxti0No5OQL8}RHfo8!zm z(RTA|WiA!x57Zu@n3?mFUN#wGDhd4TZ_JeZn z%VNmy6xCrImSTJ*gJIi8ZNndclbD=d>eL#0BK|vsd5Sib%6s6w09F*tLfeAxtOd^{ z_`*0?eXL@CwH7SzT{Uk|wERnFbvtdtokC&Ac%Y07>MKhz#E?uD^;V9ek+6ez5#$vp zi;I5UBztWb=teh`w`NuBYa2cmn2y8Q{I;!pgq9sFDRi81KaRdpWO@5_-;sJp8CB4* z-@VEE=b?85YtGuf(DGJvn|8C0LlNff*UseF@_~$yp6af20v&4S=%UsK)9mzmrhn|{ZSX{xaL<6K>LRc`MkB3&th-euuiDOS(W#b3LQ7M3O&I> z??u0nw@cvO(xXaMvN({}Var?dI#I#yb$1u@iK}$l#gu_|%;G1iF0m4CGHU{gIvoqw z{*GDB6E>qrLto+is3fKKdH`uZqrAYMIM6BV>!@K|uQLT$dwFKb)L$L3z|?7Ldm$D~ z{8sq$)?FoMi8!msz^G+yyhfTDY_NmxW9GWAp&DBu#_jy9=Bn8`lb5CMOodsHsW2Ok zFGA(CCugdCzo%mx3YNu9JlJ?0Tt{WMjP!ogPjw4Mq(~>x@dg2VUm%z-^Kofj>6ZC!)28#?32B;a4LJ3B@7p}^zk_DES=_2pq=J+ z$>?m39paN2Ts1n!j%q+l?NDQlUq9~MxPIIx0dUhma~_tOt+BxbYd&0&%DEINvvyq$ zcCrSZ0)4ZWSUDXihbu)YG7QVugjpM-eXE=WlnYJPd{3Id0f525+){mMY`W+)yBOp> zoQb;l-VD0Ws!kYPo&!kPH%83(Hm#JYX)zytL=2&CS*fpofBqoiH}-eCW;&m(GM&#x zYiUbz_4z zS2yP?<)r-jDp+5|(pRzcHN1rwVk3jtyyCZVrBWhjo=~*9F$aJx1UOUbU%A4OVSa5g z$nJ?C(N)B>wTa}ceZ}XHv&YNqN51LzBNdzdy1N-n#_UHn#?wt|+4#vEw5TiR7WIG%vry(m3{U!4zLSXqn7SapG-XpONU zGtU=P|9GPXadXjz>FK29G~rfv8Jf4YVOjEpSf9H>wT`B8m3`gWXh3@1%^nzicLL}1 zKP5}EUl1f%buEzw_*h{bc3^OF1UcMC0gaA?eXhVyr&SPGY!; zGShJ{^67349g5F_`#z6Ku|a3g;e+{uK&w_fe+Hx2?yyT{u5t=HI_UA@1woxMPXq=igory^4BT`68G)zr7u3 z1)lBT6B>IgX}=^Y*TAK_pZEa2TnBIu0EM9X1I^KRTRE{%nKqKvzP3D>*OFoywlVZV zgsmdNDQ%UPsFD4J=tb!Xcv|;o_AN%M^rh30j7rh9CZrLyZ@<4)imtPcXl~_JxLF~Y z49?3%cQe}}Axd0FVwpC#4lc|^tD(hPAK*A@QU;`ay5qGpnjcEnay0zEXzg~SyTFtu z`Vp(BrQJGX7#1RHF7s5ncu-^)+;Hqs0WIC0`G}iukkD<)H^)Zn=6ox=^5Zs9NW;l< zKgZ|}c0Hsv$JiVmep=(f?|~G0T#c1@652s$zA;3jCRLqyXUvzwLTLO_K&o*^u#d-O==z|dIM*`tE$FSj2lfv2ccZa{$;o0hj~8J_*1a z0QME&K=x4O$o05~HD~$;&1FC-?3aYfLFoIX4@pD!PeSD&^gDHN4@g4gAoRO+p$8_R zauE7KQC@86PIKQ>b0yOfdsaY**VYZKe5GGVubsiG`wqy1gPLO6Ke2z15>YiUhc6rT zcl6K92Nn^U8K0>HI-evc<1FbT&%xCcGQxl#m$Beu_hD0Iy&6?u6U)N#ZBSIU;gxIzAodWU+tqe}!?<_6r(8nE3xz z4BP(l_Tc`)$~sp)1SIsH!i{bMGrC!k%M+RU;ArF39*Y;>)_S#}XQ$trM6JY7>h#T% zzTLn3X?yG8h{&U5qazH*3QSh96n zC~>+xHveS^GquqPIpgFj_;p|@vp2$+=Vv_4+G}&lca5iCh^IpIim}Gj+gR(1jV7n( z7v{{X)guZz_?7s|_v~Y4yO&JemZU zDxl=A_uAY|YyDSanzY7?X?Ez1M~P6IIrJdm*Tg1!q?)zw!$Wb&d7Qb#x4I2a#ILB? z{uSKTJZ#mLK4qpd7B#t-sPz)FvfwqP?dQ>BYQ|Nn8Doo{24*6U?oiR*$xnNE8n>IR zJjRMR)`G6CQ~1Ob?jnx%vl?tSG3O6CtugR^FMHXCJM^R8ExvyY-><`Wr9f@sZSUx5 z7@ab=#XF{LI<1gf`V=Ip@1|lDM)d1vVu|5kRO@lR8uo=$lbC83pbH?Nlz%L%LTXeO7UlJNyDjppcm{yjgYCa$ ztFL*q_lKM;O)nn%y{BD*-7gB-;S^ci7|eRq@A)YII?HvR);B8iHMPvVHIRWi+2;2~ z4%v3s5loHG(WKyBc->I7R=c6BMGxlmX%C3^{<5kapBid_XI8&SIc;?)h?;~1++L>S1L>X|uB+qAs zO2zKy-9woSp3FtPdvQe1!AfX_AC1t^P2r~*)O!q_8I(Byyf*=u1He%Uz#IVHmjKLR z^e`Bqq{Wx0?0Y{brNnJz85z;`@B{sjj;QgCQ>_+k)cWW)=PbDWj62>qpjTL z9qsnW)v$VR;`4ljUXyTlr!gksyC?N^m1kp&!`%;s_*sT7Y&c~7p{7t9mFISK%<73R zmvy*%MZnMNIyh~_>u5~(I&5pup8_Nw4@iKVC!T*hcM^KIImM+YKhMtDSzpbw8HBz8 z8raHUJ}M!J=$js*Ox8n`iG0)!PO{|}eaqu2Ls*I$8xlPz1f~P-A#)%U&D(yT3xeNT zJ7_Cnl;|C-5!y?$Km4wp_ut`qP)f5WT(oMF&G#zJ{^hQRPuqNEY4-h3A3kxx6Vwd4 zd;>06&Uxlx>|^4;^{(w_FVHh}3f$uNCzThh6MIBv!RtgDUT&D1d~us$k^8|VXU$vC z8ceYqwctZ>;MZ23_s$ek3@+GEm>U{q_p8-w#N~5+cbp=!f7$l# z4=&g;PFuhQzauxpp&@_!@SlHd!NQpAbI-5*?1Cp_kH{>j2=gbzEzN#*kFB2Ee6Tco zHA`1KTcj(}wY-k$7L>ft>>CT}@t~Pl0;KZ&*s!f_p3l z{sq|&>!qcF`*sR^O~7UYKAr+s3HT;I1^1m4_@sc(8}Pd+@Ie99fw}Ji>loIivU|du zcgXpDbACb2C(ZdeIiE7;MRNYYoM+4Vv^ll??tW-ac6b8!j5(!vaz8TXp>qCM&hq~5 zC-&XlJ!{{0xaaJveP_5A?c3viYTtJEGyCSl%zSSH(s`5i zk0m$;mCSm8rlPgFpsWWTrC%Gf9%ziW1=Z}Nw-Il^bORr$Pwxmu9?}z(^q7T>O|LPY zrS~@C{m%0D(fagqK7WFe-ecqCuQ8sbx2AXu%cn!j_v_8}t}K14<`)9z;^XL>#oo$x z>Y*9*73gJelbD|DB&Lu|SfKqe(UzXY901Np0OkO2W&$wB^_5pCjefT;Dj_U!5!Po% zZ7KtuOyw&x`Hg55wuz&AB+Bt)JU9A~qHt){qsH_skHr{e%`S0c`?R4wTrf&>^$~`@@7=3mRF@4gkn8G~A$&XC=>sNa5u=D^%I<*x&|Y4~cBDEP-dx(8`Y z)x08fn<=x{kt4|KT)DAozUX_R&53yh0aIi!m|_bW#PRxjvo&7{u*(8<|ME<%f!R=)$NjQUbc8_VioQ1xE_*5Z%t$bbuxw)Ugb zXi~kn0QjG3^}_FWHrvyn?6~i2Jsb1-|1dx?uu26)_u4QKn}a(ZFm&RKXu2jrrA^a+u(tpGlkj*ye0) zINKXod)5_a4FuTg_vK!SKBRL0FhAI8Qfow?_Kr1m>4#EZ$7woAg-MEk_%=y8EJioi zdVpedOWFezqg#_60A;$h;gp?P6V{Pv>mGDmZ2e4)bJm3OYih#zw{F5~Xr`;Q)L`q8 zU3S~4=bkUNT==m;ubqzJ848bnHyQV))|!Aj47v2hLtU9}$#?(ma5e8jWAm=X>nx1h zbdol&vF@Fe^}hUxyQIzEhx>0T|G*tV3Y8zJR=IcKjDCz$JBmNy>+VDyvHV%NzDqu+ zUv2J4;rkK5h9h2!AUt|rKganx@B(h?z%a9kRXBTU-K)+GmZKK|YCMU4Du)iww(B1(#mt&z`Ftt|nicp>4?5yWdW}-t)He zb#R)mLOgmE982qR^iqe&fui&V3(-lR(QdD35oq66|_QO0URwSrbg>Sm4RYy}Zi_vQUW}^lVkO{-> zmbX)mB&^v-oQBkx?4}YzaI**&Ziwj zCF#&0p!jL`iXW@gwWj5cAwtq5IeJg7M3~#=b0*QdZH}MMM*1+xof~K3VxI}!L!TbZ zmQPQxkMGt)V9q7OJ%G;{9}du1!&wUqJpdBg57( zQF)yV^{-Hfdx~tiAMn#ZR2Xnii((jc=Jzlk-qJfJ9I?HSJh|{{U6p(KS)S)Cve;+$o*Aw7o)>0 zyciws@wDGta=)hBqOTc7Z=v!&L{T{kN%Z@iUc`~k>eZ6qAr`4Gy2qnN_t`g|l=S#X ziI0;W-LY#1c2nA3L;sDc{Oxq<(fx{BIRc8wO*Qgtb1gzU1J;?EYvqHjC{tUH$G&`ZIY)3sPTd*tIh=>*FtBi*`ET`+bVz-ui->`R@N2z zs0p;%Qj^2)s}CYjs#XH^@qy}z6*!;3Rv)O3*|oCg0TnnCj`k+=^WP)OsM;e;&f}Bi zfb;&6q3-)!`-ERwN>{rX?8Ixjx^-7rp6zE+wisafCWEKR(#Q8J>`g3yi4~gv=}eT= z%Y(<~Jlpk08QmW#Ol?eDXt=!KtMB2VMkW^Fs2~)er*Y%(-y=0sO_+m-e@Fo40Px2I zV2;cl{htY_ep+-?M|6*irYxO_V9{R)rFM8ra*^iu?jsZMn&9RBN(L)?c^Qx&l|B_7!ZQmn0Kb{wgjnRi;Pu8~qJ`A^JPN zaB7f$$UV5PZr3k@jmkgqyMOVcr?{Bc=Y!Mv{#*4nAB_TMmvHuDfTDjZCRPsMDZ)kP zbrKx7ztQiwCj4z~GS#FQu~unq&vTV=NfZjLQ0m1KBi%nmA6YGwJVY&Rq4Q`89_9F&o(%-Y&4plVLVIdO1&P_)#Ry|>61De>?I<< z5X;Nco=Hr!GzI_oXL4`R+b?ZGew9PXx$Yx=mRiHH%Fpo$3d{7NqU@uJave;%39vk1 z+AsEc4@5g|s`gOLUvK3-tlS|&op_Z{jhh7o8e9u^x9ZW5u@%f&}ny+WVK)ywo* zCKx;XZ1|SF$8~{f=a;_;jT|(v+zt{gF=R(=n_%`Ik(9&oUTZX2X>z)$m+#PaVQ+t{ zQ4NdL&k!R-pKIisx2Jfi{yBUJk*8j!e+>3)L8k2ZNMWx~nL_9)J|Fgepq5iGwKil}B zFtmbjvmoW2(PbJyGo8k7I3<;K7e?1Qu*z(rL~9!hS0^15{paUNI2^Ix(ZZ1`(jf>& z3u9K`B^Rs@3y4{Lp%A^|j?+G4V(sQ*X&&?*;^eK}<|<{1$E`!sZ0dai>MW?w;bZO7 z%mcCnn;sN>d%b`V&aJ4^EyTTun!p+~E4s!TkMT{x&b!p={lTQ)tUC7|c+xrID@8D@ zUd4xcFW;K${d*+`HHX585PiLGtlH~v2j(rxNfG06G8%7PPGW0JIjQYvt|Hl|S1@wb zYr&luIyPw3A<-1L3~STO>NSJ}WxNEhha5e?=CM0NryzM8Yw~dGQXJJAz(*!G>65G8 zs87E71$`!0uh(apFldoKvKA?i>nEbs(VV?CQB)aY_?#egPISTTY#YB{^`NJ{JjVy* z+|Oszv%MFNU^v>-mk2hti4^TFTz}w6bE?n6=R(yr@-#qF1A$G61_HM}KF4N^hT#Q- z{sNlrmm+*MN1{3W9JrSSdW9dJhC@1)4Z!9Kz#yN%mHFi%!)@Ih97YIkS*`ra@P~Zp z+HP(VN~I+9)of@k7@cM{jxw=%Wm7O=Vqix-un%(`+3{K_r~Xvyfqd{7>(>%83P&VR z>$jSRZy}RmgWH=;7&iEVzP4aLFH3cSUDLg}fBdW^7<5I)SPWFm!6Y#0j3 zQyEb$sO+csic`k^p$`>u&c3O{6TR zJfDm++8cyuV;Isi6K)e6LpxjFtL$of<;(hVwZm7XJZF2@%`FG3u;L0ig|ZE^OyAMT zoD83CJ+MWF-e%CN-X;pXWzMI1tK2;X%Jg}sAd7HBVfC{m59(z0Z}aQfL1l9Y_D_rE z@nIQrs$_Tx)iP*X^>4E2gFU5a3&ZL4xNWO6+3zl*2(R8wIx*_hm{K?*psjNgdRpvv zm%`3c^@}z9DJi~yb@-DU=`h#LK@tD8GQ2Ze*TIS;luoGg!_GVH>L*v zQw;ZaW@_+$+QSg|8uxG-9qAMH#iOw?`B%iT6xcHUG#3~CCP8IBOsx0{vJkG#(}~-n zKFBQ~h>77=eA)b6~sYeUu#lcjtL8WBJ$WBQ32(Qb;D!?BE+@sCo9ksfScn zOwe{VFXnC29Yu;YV9C9p1p4^VlFzNeq3I~vp*ih=dyQ_Y@(yrMT`uYL%Fqa}_3otD zSRLtXxwxD8SZ5n+uM^;5g`}d}DLaD@-%jDq^Dg+JUHQ?n%zwj?yM?OVUURU5x+ejc z1Hiorz#Lj*gu$mMpH}sI&UPKOmHSQzx0a7DL<@0fA9pvt?sYh~={x;c2deSi1ss9L z1mUa%!49;>2p`HINQGe(Y;K;?I7KM5Z!K+yKGCr3h}X1}f>S@?*BWHwK4oKvPzPvQ z>6>y@16V!t_X^vrA8+rdJDac#6`#4KpzPhV#XR<`Kw~`HyMN1g`CvEF_3P{02^Szz z?3ur4wC!&HCZUH;$h)5cWoG;lA#pL&ouO>kf`6vq_;#}9a$V%EoqRMi6iwhhP_(BC zWB6YrXRp?p9pj-~zD(cUp@Y0F23dL)5*eBkduGYAQ|#G+goh4^J+E_ed*}qSTd=%{ zNW5L-rQ|sDHkQp9y_fTc?ZY1>(d8&Y5-gF8vE+Tlg!W_+mKy63>BGFfUX(xFH`TfZ?<=|y{!cpi(>`|zc5me>N?7pL`OqIC?kCP1sQA5g8m_UFqC>i{8T=ra##D~t&0 z6M+_1r&)g;2&i%pKioP?Q}mdnXc2I$@g?*%i}7G92;=Yv<8o)I60qf;%UABg8T-!+ zM^sJ69`48`dx?97l|zVMRbp|=9lXBsJ|wt8rCB+Yz!fTv%E^LB>XlREQYmubVdO9n zIK|3u-koVo(6A;dkng1U+o`X56mt;Jl>p2EpgRGW13*s#Fb9Ci3BVixdJ}*-tSqIE zQ5#D7AYGTHEZf{~=rJm<(9BP5_LJ5&_eKhm&~AYBs@M*jC&WH6AG(zUMAz`c;^7KD zcB3acg8VWgJ$KE4HW&Oo{7kgEl_4~ji#v*_i#wv;nrjFY@c+>E9q>^V>)VI3o84qn zV3Uy02_<335>$#V96G2-Q*2nUA)+F3fE5cd$h8;jDE8iayXv*~t`~c`g5}zK@5b+W z-YI7{L_h!E_xtnH>~o%Zr}vq6W@tzuyb_a0x{_Fwy%a9S!`>jq&kn#TZa^>5r_0yRarwk#hdjZk_!JgN4yQc zog_WPdEB{q60G8qL-|$jqUn;5*2+*E)4{7@?D^%gi=V(@Sdk0oAV#=2WH82ydlD9* zbxY!~4}@cqSE!;7;Dg;Imsz26wr&g;(&s|D=}9P=$6IUaG4wP_=JBNQNSy^+Qd>&( zBABRbYmGI%Cc3E?tcY~ax%Qt%d@7+{-y1223(8vR@QYWGio)CBR!bjzC7Ss++zXzU zq21ZE2&usT(Zb5j7hB`E7%pWS@dq847M}<45JtEgtExmEYZC1x%*`%_!}h}Y_t$kU zS=t9fCCRp8tURG11+sg+Xx^RI6 zD3i!xZ#}gPfxx9~xNi;3xw)rmSFd-AhvhgJdDR{I4DUcy3-9o}S{56OBIhbE)Z@_5 zH+}njN!cW1MtGUltg# zX7Khlyj#{85$K-Gq|`zLM7CDcd&W7eU1D<1zGo=>-dmqJKTWL08>(^JX&AyzeUsl1 zOT{I3i@QBh;zH&X)lGs0LZT{50~0iG>GJaik;;zo^*bb!XePc%ws7kihQOV5yj z(qA2jWM@5S#!Nh_2T|VsOZGfH*&4w*R*`ol-0F<<%Uy>=##OB>JU$xugOL6lgC(ot z(ikP=4U?8$G%oXsdk+hbK%!L@C7Ma0VLTWXu<>;WeqfH%%tpVn=>;^0lv&Qws6Id` z;{Bf)aY}oKk=ga|J=YAoz&8*d3LLiJPlL8?Zn8p|r(2D^SSK+P@@km(I2RYPLN`?; za?4?-zBD`;pBVCzjfIDS^L#ms#5KTE7uQsper$7Z@KW85ur|#*#_GuUqW9QM%15;% zwFyCZ47`&U2ag57qPv8{Oo-dQIH=pI&R(|-^n94lwlOWj3lMl_a|1r4Z7%Y5L)z*4 zOkyph{pJDqYnDFmWn{Iu9&Y=Y;aI(+T<%VowNMr%{ZUDjztNVA^h!6?`GyxOXcF4Et|dMvz$UN<&UGvpD0ea)GDIO&HEf{Lj%VxY&VTOhu3 zWeLj6ApCWs;!Q<-C^;QN2E0msAJR5*?!@yq3r}c5&}r!n+Lq_Hh1E>wYP= zraBDjQ+zppQ;B{Mz`w5^-Ke+2n{LpU8C$$`7YfcPzMQ|MnTZwg&Ye5g*5qEtcF;<4 zGx1(jW^+Ca9IhaoII3&Xn&gHcj^ zIe$~QuUCg_I~%Uf+NTqcYf-31LGhsuPOZpcojRpfN(I8=+#iQ&;zSY*%Sp6GmkWb} zwDY}V;6Sg!uxw|-!rv$?yR-?*p* z(ueiJ6!XTx8C)@?%TdwN`{a-|=5x9L^B8tNq`RD#Fh?aszpK6}*^mft#lZ`6z?y5X z*#YC@LqW2oeHYeWFh#x(+9*A)CK{)X8Be=YCO3ivLkSx{$KS2kv-Z`bV z_GB&a&+IM1E0S=F<9@%H!QjLjcg#LFQH?dFWDxzNePt%UqY*!PuU-yPS5c#cNSc(SKl%Lzr&y0myE~RAh8eiJ=q`HvOkjj zsgPa4ly1bej7(Qwf+mS83CnJJFwOD1f%(PNU-8Cud8L^GD9tqY)z5I&^Puw zxpNSx7O@9+a0QcIs}leMmquwq00`VGr3nE?M_#G#5k`y$)sOO9q<-eF>CezSnK?m^ zXlnc&1kC+1ZKuXR(CBky%=)wDpJ-H^KT$xahS^XF0D)_-bT9>gz_nSL5Ww<*IiQ`V z)OB<7_220H7slsbnMzCbq{UaQG{Sy?1nRc}BU~7e2v5Y$qfzzp)bHz=FUMtFdXs7s zfYw9;c5>&koUEXU`VuZX>oT<;oGXIN3W_&kzosh{k*XWFgu*`uSJd!nz+>)fJ7c=2* z1Kq7P?vB^)PT)_xWn!N5Mb`N)&ZRu(yE@W3k2cWfdbl?L?%OcxxZ@Ex#9YrRktP`T zjf5QTXuvv!`T`n7Yi6QzS4gkaU%h^TtLE_fg?=6e@v-X9jhvW&@Ies$>jTAn= znFS~O3c27lF7un2>&AdN?>95!!*9eGZPz4x$qtHuwogEGM?7TNZ8Iw-0QNSwgaABj zx`HFIO(2gMd^0Wu#C(r+?rT&lJ=Vdpy_Z26_&AZKMnRAZY8?`y?}{BbZxGW*&i8Un zbYlqch1foPAr}4qV0@Q^ki#+GUg*KYPmm;@d-nD9=)!O6{u}`Jn&_Tq&i!V#afg1F zUWSn>`s2>U`YbQp>|S1?$Cg;Gm8Ca7)6XxK)4^EWXIMGX;EF%q&f{~mSkp}4KRsr+ zyl_WY&woq2-ORl@d5XaoB1Kx5HYn0kH*m3rH(0_Mt-q;%ASLa&ise~6Ahc!|TV>SY zG_#LlBLL`Yu;=<5oP(O-M);7&vCF6$z7-~7GC?a^%fn*Dx*pIe##XQE*XOPKsUzTa zu`FN8_`?QaT~6nx&V%qG#6gmxG2iZyu&V{sQGn9@I|p!jn31-bcrjeStoE;zQ5l;} z5Sx(=n3Q0ZqeE-WY0=$1QCjux)Yw>?KBaOuerY4pLE=7bIq4FuU?J}k+$7@@# zd~r`y0Ej7;5CCGTB?N$&W(ffxrdvV)h#8g;0Afo^2mrB_B?N$&X$b)!wzh--5ZhQn z0ElfZAppd7mJq;kh%bBLObRzV%w5im9gkWHRk$mNx6$}Y;+Yy>MZBHHR}*ik@ioNL zHNKX3md4i+??|lcYD(U2-$3eWMJl`=#;HvAM0f)Lb%k%lFMe*qPxwv`STl{o+JeTx zt0)a`hDceadvSOR0PZAJx6*1%-A1c#;pSCyh@uHr^G;A;Sca9$ilT}{MY5u}qD0L` z;LLJ`S=UQ>12)oYZa!{m7_dNN_LZ|u*~#q$OZ9#@krAse3b&BoSh%VD#>36!w;~=+ z)u1SpWUa42>#3P!D2Y{HQ5;H2)mM~+I+?hp{;jV)vi>Y%I$&a%SUw9mDI=@x0a_Dt z;93s1>0*FN6ZH*8Upo}a>`9q_B30^F&_sO;83irvj0uz^Nr^wJm{Y*c8p&vS#j2}ap1hWgR z?oF%z600;(ztZY&w5oH%SRF>IyA)d8hgRI^hiH>A~F3$5-;t9Of4 zny5c$75kYPQzr7Q?pA1ZKCRv(R%xQpqGgm%tGXB&tHbH*?uAzOqt$!GDos?3R^zlP zRTU*tvJy)eiRF6~T3tY^_lZ@Ss3KZT(5lpIYjqT@&MCCIKds&`R%xP=v|3E7(h$Jv zX)K?*tozOH`53fRU5OsEgrUr-(Aw(!^gDWT_eA{!tbum?=HIS zhu788((R6Peq&jGoRU!9s z&!R=I>XWVu&%;-`r{t?TAGoS-y6>R!G>qb5Wl!W(rXgKt1SI3D`lb6Z3Hqn|4=ROR zKO>j!mF^QhAu+>%BYzy0uFoHEFf3e|8o3WgLgne+haf~a%cM_(J# z4TI)*Id*Kg+8v8rQA`z5Ug>BK^m2zVf{p3M^uTy{FuSkxfOKQ{q)vGirM!ao%6UIP z_m=Sfq(bj;Qihd5I#l-{QJN5{CKswfO4=iY>g2*uVF1riZJ6G0P){$n6ARl$ z>5bC3S#B2=Hb1?QlC}UgPbstswOQI6k{-h35I(ED9}#&!0^W~s-e>hV2;QGo=snJU()&^AQS_c8 ztg6v`I6R8|77QgczVx4UDX7vr{sA*5#CSK-cRy!bLsu2=}ps<;^98x{e<+U;lH)_6C>{@ z!uyHN`w4oa2=7-FdXJOx^nSDSX7rxp$g0hu|D5OjWOzSWd%uO3tEKl-(o@n~#KUUw ze)IH{aEBzquZq!uvA{y~hoY^nPl3D!u2hv}&4%Wu++br^EZ{+WQ$^ z?qYhsWqQl>3~U#R_tVl_hR+LS z>-ezxBEv5#8g`k*UltAPmoWUo#IQ>==KOw9-19Kog|7aYHnr^AuaD!&4{4v*%qeK^ z^B(bu`ew02^}k8z-NOK@k5%QFuIch{6k&yzOjm?wqXOy2e>@n-F2OpqIps*da+7{g z*IkS@+KW0~akvZ)Z$^iQ(Y20nxMMh+uoLzlI)&fnIcz$0I-K9BV?M4OcD<@NTpqPg z@=a9%HI6=wrt|5{NDLRo5LQB0W+^V+=5|_DRk~_WIvr0}hKD+@GZiHT-yUpA9PdR? z(uiZ)#<9e_fP`K|hspJ}It)b!!vuz5B0a9obi?pq5@BaF`{~YM8r`F|>A^(&SPMV$ z#}VAV(Qdo$OuLQ7S#3|Z$J4=@OcxAECJ@#_WwbUt2<2Q`1TQYX1($0O`WhR0>=Ne$_1l(izr`(R!MQyUuN9n2Ik9kAO#f!gH!c$sy`8!AjSEWxpk`wBwXNR9ShWa& z@_6uNF#!+0>_t0V>x|3U?IE&$g1etVYo!3}LBE2`POa-znzyoskrndE9zL#4twRXx z9su^3d!^A$alXa%w4vf=Of7?-GR?xeI$nG6!&l+1S6jBOS54WvZUvX7xB)gyc*VQh zU+B}<>-M+Ye(nFyZS0PE!x-+|u~1=2#ri$_4F7#Mc9t)`84=jr!8HwrlQngrGEuy}E-K@4r_3!XvlICO?x*hHo(3-kWd;YB`8^4+O*;?$u7y zI$%EVuze#P)AwlFemM1}b`tl-K1erE)&9c}#dAv#o+{|GwAR@P@LFtkgzNC9jyjh- zl#f^Ie&zBs?;YdaR7((CQts^_Pi?sq2#JlgK6P=2YI7z)%6+L3*N;K z>vY+vcWbU0-q!mqi&%7HElZ?evkZJ{5Bm#UGA~)XLut|v-+`6ZczH?Fy~xIOJ^xIfuo{NQIq2z185-u*PSyHn$SH3b zo^fV9nr|8jv;+%~V7QJEZB5HUlXgD$$mQKfznR9VMDO1RV`DT?i?}{Pmj`j@R*|1U;`wt;0#4Ls@p-OlUo540pZfpT~tJ__xGFM!PTw z`{)wl-bh(l|EK@l2jE&c_Nlxq%8yQ;-mOD%*8)>t@?mHxj@3xEq%z4GDc`)jkOjPj zi3H42-oiMu15k$}NR17?I*vi}u+L`@Gx(7mG;l68zx!e4ZrCzNG*7y%dJ%40Y>Yq%ySfCia`r@yJ69gNoKp zK^WRBG?nJCRlO)AbYTxgFAUrRjEo`$JGV=!o3!#k%qBDp8M4@_HEyajb!+} z{Kmo>I`g%BbgRu}IE6qWz|lmw1(ObS_6Wq@jDU*6uMm0^8zP~DLP3h5OT!*jH+t9vx9oMP*z_Df4f%#JaiR`IUT+VB@gx6`+#WV> zz0>i3cVPAllqY{9{@jHB?cQd0v>()!2=dAZQ#TcUhd)qrMN%M<<;?fR%vBoZGenRR zXp4G#F`d%!P!bsj2Sf;$Wzhp9=aDvvamc?2NfiDnK5Ms82EEaFZije$6R!{Dc@W11 zCs3#gOJG&hJ0whh-(Kqf(O#*u$MN}{2U+pW--@f*=(taHC1YB&@X z{*K?W%pBZ-)CyG2LkWKX;PQ@`LT$p>u1zJw+#dO#(AVuHP~{&&)v*<6Y^yIyUuhf` zhoBF^>hL$VE*^a70QZ{CKo0w@H4CB~HYYaRKatBd}cr@wla>CXlH#OYh+UsnVjqx3`Tp1}yrrY~oOvWQB)47Ck7{qiB zlh?uED6H;V7MggP-WX@txklH@u(OFe8GcK{CIG}KmJk5qR7(f|ahfFr%z?Ibl8$l} z=WH1HMJ?x0Vg?QtG0#gd{40h4jsa=?zQu^!B*slQCi2dmDuG0JE*ckIF#9DGH~Mvh zS`$(&0%xB4A*#7b46W2)R4!Z8OJiMX)E{`-tUS}*Z>HZ(B`BVCSjEChMl9?DAaw?h zAXfq7K0nCF^(~E!2poDTgq~sZ97KS(5|C{EtcKo zQrv55j5@-o6jRv7YkbuSxT($$)A*H1l$H_5d&12)Q4UO+oQk(xptFeeViCf*>Hj4^ z8uRm`XSgBqL+4Ez*)p8jQpVK5=`@!u9no(|k~Lt3t~}E*8B-mwG!cFs&&v*ATq;=Uzalm3k!0H7!E>Q#9-<8aU8r}xH>bAQC;}U zv>#I#u2?c0j(SrV4Ekq^JI@?o#mC#+rA6)stbwAD`VK#67?{4b;efmZRyaSLm%u3M z7c=}^-xUDj5=#gu$bw60Gy{9-ac}qb;@YO(cTMQ<_|3Ga168YT;^G(-+mk2&;}KzB zO~hY$^S7PkM}RJ~0TKY>a!UvRafKxWfVk2U0zh142>~FkwuAr>*H}UTh-)n&0K|2c z5P*$YUQiq-n#EsJ{vJa(CbKMvmIGGx)MS{RwC{=KKKkP=C<$(g=ICN5E`pl|E|j3+ zV+KDYi1^rw9}+ZtY|Re|5b|hSI5``W+SthKk*L+;)cmG6)5D36Z7V*+1gwcoWIasvb!a>% z%d59bmN%go;NZtbI+cM2V37gOF{FxX=c_JAiJkg*-la`1mMrqrA=KqYcSsBRVMVTY z_Qnu&3ezB_R`*9F+vt~Z^t>bVFW?lGyYuzSO0i{Es&xdg-kwUAsk3=Sa5pADl^J9m zlH0MRST!#-tL8qGEV@HNNr8c%XBGI#jyfhfZF^%(L_e`%o~M`4F{2`#w#syCXBMDVT?0^3oA;{nV_ObttjiHn-@Cd=#1tHMdWz=jd_lwnqh>@;kF?CLX zoYn;6_IJ5oqUEGi$JDt6a#n4s8oSaJn2BmpDf2OPUZJQ_aq67zOwl4ODm5ae&My=- zdPh*Iqi8~sURT?gx}Z?hC>l{=4x&kQ7ULupD5fqf6g4{_UD91BTC7E-7V0&5>TnF$ zQI=&ebGgDB{vVCmZ(@?tAMD{LFJoUHq5*Aq9Juy2fTeC_bA6D~zrNY*Xx_Ec)Z185?Xb^rbRy7=(f z$+{)qh8>1C;y3Yv7tDhmBnGB+PoG}ZA-DqTb^M@2P>f{H-q%2z=d8Zk4@!;UTX&Yn z1MD4$B;5=S#u`L+l}JAH!oEqmJ@nHx@nBphEtELn!lK}3;}Io-6R=pAog+53+BcaB zZtbEaPhYxKDwqUAhJry4Hh{HkRwTXsZmDMY_j%^KPV{4Oa-Y)R_6jY0@{sMzf+w*S zK%0~ZUaN-9)_R8jxbu3agDY#b;nfFLR|St7Kj>y~MGYih63LCO`M#EJA36Qq>fj8d z0G)ekpTlc|dJ{xS1dFi_k-bE0jJ>Ht*Wo>rbvOJvt4r{0XKjOIP=~dJY>Nohzf#dP zxZWfnb0=7h&}4TP!&?@f-#Fnl$)ZCyZmDM!FZlL@?hMA*^DpcgVEiJ%c=n*t-GiS^ zno=TIfcR#+iz6TJxlga)c;mzDwafYkqfJ=oW&njXFZ*?WCdPKtu4$m#%kKQSKEPa8 z+&;K@TthI^q$MSST^aiO#m1uFFCWM#ZZ~4`fZ#O~8j``gj0M_cI6LIC=LZJUaCnd@ z#wZ1wN&Lm|p*L0=R#9(U4$_@tYC>)VRIuM6ucJg^nYn zDAL)^8%`Y=bcM70pqs&1xRLEDlK(jMpQGt^=bj&r49+#7d@2505S)jSKpT_@9%a${ z5W}@(-9|q=IF|AH$MS8)1dYZ9$>3s^h=)XIMP~B2VBb>h0_#{Xo{_#>47)tB+kU2^ zWe)4H-2`3Hsz%S4#@PP)-qTYUqAj2HCkOYL!gSu;>01Opn8HMfpf9pCyPdc`$oC-})E#{<(B%n2SeACw4oVk0y{Yz(<|%{=B-pXP3J1Lzcqvm}Gn%&lq>I^pEm z`viZOtYq#4hcLf?!~iT=cXZ{}2b+pE_N}uQGm1^KPT8NqIR3i<3xXR=8JzL^a|?nR z>_4;6P$H;hD|CxE;$L_5{=p;@tG(YjVo~rDwr?p(H-p=mna7IcE6;D8quYAq77{xOt4?HLsZ*rbwuo+{qkqB-5^(F@gcp6mv zWR!w_2tc-449gZjcO>I=&d`O2((k=@ntw>JyK(9Jo6l_yJkx+sBDjJ!->75aJh=Us z6~QbMmA7vD_K4tm6J)v>yv$JEB$Bt+etHz$F57j%QNg9g#$)qyM+N^hZc`$771p!M z#m4YYPCb_K%G|KwF~QxY2$Bp!*54gP=&Xqgjth1)sm^{a=mUL#>?kpO^0c9ko6hal zu=spa=T^PH@9Cz_ZFu>4Co$r4&pBZw1GoKY6HW+DGT~|6BfT=%+c-^$;A7T{cd)mf ztZSWd(i&u?JMTpcXDu&DJ)xETXss&Vk$Q! z0-WdYvPX%Hdu|lqCHE6=>JihitmA-O7u znNYRfc-X1{N(hqWlnBN!{C&jcFR#3@DtO2Q`HlnQX9PDHKj>yKi+=pri!rUt^*NJn z7j5bC_|*3_ktLV0%*@NCwxTOl2<; zq2HeW<-DNIO)aA%-VkyyuOk&OJMD(xr^zF^>(ph`~7Qk$&d~SPc`OAN_vt z`N4svR8u0jlU09=_F?>ti-V7h_fxL>{NezQ(26y>8E~~NTP>2ueX`}%bo-hIZof46 z-q^Tq%@$V$6HTH}BACU{zl&i@vhIzUW!Er@$G!Ub)xie|?JUXQIi|zCBDDNJN8J#7 zX%dN13c3`*@FFoR-{Q@?7_a(=y5B~>XaDrujlqegL~MEdx3>g%TTntvi2xcfUUr_? zeDR}mZwtOQDG*=V@1H?~2>{&;4qy!$C6dQqe(wWx`_Y^4x;Oa56zirdkH0_I+r*0! z0j~+oro_gzQxAKD@p`fN{s#i+en@$pQr%um3&Je9WZ~5cFO(wQI``rO2OoS*A41?oYoIHVN zSMB`u69LXCNl40nUi4(JgDJIiGr)GEmz^P!*Sxk8581|%uOKm6n8f*(yp zC=qPQ64FC#oU-n`H39brS&A=v_NFz#)+V3RBb z!QD%q4|`16S=v){`F#TuKA#wK^)qS(Agy!PMt^)F0Cd-<~^?@AU;9k-Ujxcz}iZv^icNAG^+ zs{aJfpqwzJIzb{x!o}>%7;+`+_PRdyP5^BeG5l%kb?*eY;Xyv=W^fwfo4r;fUmSVY z$8`I~O;7kRm}d&)@Z;b3Fj&z++n_{nF9Qh0KDhnf3#&e76szYA{w&}{xC{WvU`Gl; zRT$rocYW{k;C2%LMk&~orF9cAoVo2WKQUeh&HLnQiC5=7Uj}tXc-8e&zYL&LC(cqL zSjNimJ4PzWx-r?^z7HCVpOeRDe+b?&)rM{cpTmvpiz4~uVHf>Mx5s=P|0Ouh2>mo` z&ac55li8FAerB%VUR}8T!jfsfF*N0(9BHy(=G0+- z1Qqzm;;jwL<$SEO%YyI7>yVIHysQH<=EzG(4i_@+$g@b+30dSw_IKG*Arp=~k|YP5 zzQQG8#-n8<-x0Fdk&8$^BV>sqnYY5VF4`cObb!$a+W4Ai1BA4UU{da(f{MIC3P(@j@y`Zb)*FkOLjr zK(bcIMo0D_=?j^0WF^VZF;2lsz9VIo<250h9NEeS>0u!UJMue{Hwd|bBR?T|wvZb- z@@Q$2)RklD&nT;K)HFQ$kL3WM7isqld?;nIpT9 zd|Sv(9a%y0X(2arWSr#fLT>KJU)W<@EaYTIenoPnkXtzN1Cj>`ImMB0lAI&tR7bu* za=MVy9Qg#vtdP?kc^}DsLe6mHZ6qs%+|rTQ>cadVBPgtVIuaXa0`St;bsjvPYrcMKnVHQSMjcbkUlejzM^=-( zPsrUInId_$kh?pQ>+jiBLhj+nA2^UcOvpKo{EXy$A@_9TyCk<0a;_s^BRNjUy&U-* z$&8Tm9Qg>z8X@;~Bp%-JvK@rn$B}qZ%FBL+-p^P2IuegQdD&NmobO0HcZl^bA@_6S zX(X=~a)Bf9BDI(0V8mDZJ90V6BZOS&$OA|&6f$(AEPm`L?UN+krPN333-4cvn0Pl58$h%jvP$#KSCbp$o?eROZ(~|M|LB5vycZnvJ=Vk zggnHN36jSMxy+Hjap1f}$Yw`=Lvj}(4|U{6Bqs~m;z%ecdfA~uE_dWhBynXH!g-h@ zpCVZzyYqNsb&w@;M<-c4Q;Te+hYtBkM>)RRM0E>PYO_ds!~G`sy@CLVL)| zaz5p&(;W#dRWG}jkgFUC&1Emk2G3V#I1+mDUN#W&Oh@8KhL>#+a#gzg*?lV zIGgTee?xin)!B}GjO2$xp5sW|{^Dg{5b|6{;vOTce+YS=BXLgN%iS{+ekX$X~HID2-@^B%q zbz~*U{e`^Fk;No;5b}CQ;>~63e+YSlBk>xwmmMtRjgI_;B=iChq?;W1Hc6;Bg1p(0 zuaLxX3Xr!r@)?pWJHEQrkq?o4RLI*Lc_+!6g#4!?Zy4lBm7d}aKf(yHWGdtX6<9*}oxoPCJF4#m zeoOd+z>f%j6u6e~CxI^z{w(lu!e0d5L-?z}n+bmtcm?6_0?#FE6?h8a9|Dgegmyhj z2{eQ~)j?oL=nI@n7!$Z7VO-!0!Xkka2@?Xdgh_!-gvA2;5|#+8B}@q{BP46Lu2#6k%H6{e+bQZzHS{cr_vR9TB$+2s;Zrov=nA4uN~B zR$vQZ7lBI%y9&gU@t*1?a5iCgfinqv2;7XYr@(-)mq462@l&rNfw*eM zQ@aZsLAZy&!Gv=J_9NU=U>Cx<0?P^a66h1o6Zj*x0MyOQG^Ez+=%cHfenPq1a>EE7TAgKP=Q5+Edqb>0GA8=lJGEr?-8yL_&VV~1U^T2 zxWI=Aj}Uk#;gJHbCp=2vC4@%{Tupe4z?Fo@3j7D*aRLt{JYL{_geM5xop2>#W(Rem z{BEUAlHbYdWceMZPLbcNI#qr*RHwgZaR4fj`v@k>8X?cKC0LF1X3$T2wxt@;PI4~ ztk(iSyle>pAYQSA01&TQLI4OTW@$Bl0h^g%ER46; zy1F+FB%Eq6PAc*w?nTtfT*~yq!fqi&$oY!C7MNWY*;~@q?DEKrLDE-jEP^}c zMj&-XByb=Fwkg!wR99Ma2_+ufI|Netu80X7W%Dx}1qaJBwKy|78VJ{>;EZ+(1yFke zdJO2Mw{i3})>?yUyM0;IvzoOyr3l^6A|#T&AL9kD{}1~7E7NQm(rhcHSu9GUy!%M0 z*Iw=?!W=wNA;JSWmtG>Lb-by*&eyO-Zn2hgX(w`86Pw39p^2^Ka$3%%qsVDZZ0e~~ z6Z>3hH!sz4E;&WcYGU(Ia%eZlwa&B@&{&kRTGVJ_^St~VobB#Seeq>nabFA8+zC&~?CjB$eNH2pX04X z0U3gKnf&HAY<&>`;y;!U00Im7y3z>%vDOj-K&-Qb01$6kLI7^&m*xc#{Fu3QBqPY? z=`eu$rbL6<4w}AfBi^?51b}$Q5&}THYY71$u*9W9Ctx#Ng$sfYHQ~KqI4_5SNXdE2 z?W1;}!acb1SM30F9saBxpzE%QFqNJ9=~uxIC@tZ9*mY=~rTW^se!V5|W-n|P)Mr6V77i;za|nmvyEjrp2jx+S%mp!hB>BO7^Q&fyMK9#+tsy` z;mDuVHC-o7T{~6Fxv-0Sx~}OuY3kbPTF%8o%x5@^9WPz#1n|h_Uea08(+{ng06G8j zKlSs9_Wbp{9p+IAfRTYy%hig zE9jaKAn7{`eUPrpW9+dJTbHkms?~jnYe9W={fn<2Xlr&|WbYxEtzTcSj|3iRSKx+7 z;IVcEZj1z;XjkB-NZ_e<1#XT6o@rO$mPp{ab_H&Y1lF`CAWaX_*!M0&8qd`Iyi3!( zwuil_YkTg8Q)qi!`ih+H=h=n2e*Qr%=h9l_blby=Xn5jE`tOIeoJ(?%(`^rLNp=1F zqgu|TyvW&p9zC({=f%m#wW!MjEo%DtjyNHPW(xOXaP)wq9E^?24J~R;Ty;u!qG&>k zKCMMvzGzW%;tH3oQ#7eXpVgu+hqS0UamDjy5H->Nw-$AIrA5(}vPtEUGl-hj_j!u4 zb^F=Y5&u?ih*wWb1Iabty;T1!81mj5mJEmH5u zZ$(`FS3qtk#zQT1OWxtx4DCA9!q|sllN>|qL8a^zqN61QNMH7(DvbMeb7V+HfXGA-xgnpegO=7|+r&P7?|Z2w{B ziMlv;(xNT}v}oQuu_OAAT6I1x07Q372msN;5&}TvA~nnP+PNjj&8AOLh+w7;TX6pzds$P7g(4F70W)qDOwY|RT)7I?K$Q~oIexLDJB)~*iUtmuxBuNx}#k5^nfjdJAg0?6U z7}(bP$0OG>wA{AWQLaDm6-!%z!%sw(nRNwbPex`N(cw1BtqJv1BrvpHpZAG^&BDBX z#Gj5_*rZ*7XCi?S?FzWg$yY2Y>j(1L$c51m$cv$R&KgO{;mr%1Lqb-F%QBsDZ68;N z%abMf>%+cYAAVO~jhMGS94!yWu{_LciOl-&4u0QdKJ@DQFrEj zwVW&0B4_)3b5TZIPuu!yQCGsXsOk63MHyz^*q}vSS=XXw-qP9Wg%*`DQ%t#PrA0Bprzl_6f@nuAD#NLm zaurOAVuDXmys<^mPC8zrbXZ+g)1sK*Q4x+`yN@tH?<4TJwgK2j7~t;KX;72tJFjdq zb#tTGu)Sh-Pho)V>IIQX07R1|1b`T92>~EB zu!H~*8(KmDh>a{EU|u_8KIthYpoONc`J+)6EIu9H+d|YylqW^ z^?E`#xfwQ0kWMR|?i6w}eaRS6q6ET)}#zZUJU$IhU*=7p+HD__kr)Mhm*+ z6~T5EB)8LYE|o>jb}Vi|lJ{h(Sz5GB7tm%wQa33(YEhT`+SN7-l3H|UE$VVZi?&&i z)S^;fV#;NX7HzX2sYQ3wuDZO^qU|n7>UxPPJj7OE0U(B2LI8+imJk49xFrODz(FhB zwg>HxIc1RDk9~?tna^aS|*L8_&I2C@fMz4D8uLWKBMNn5Jy@+Er z7DFvJ+wR)vGtLkmG!_bQ23X zrdKl1fS61WWt4~ynG(wA5+5?*ltCdrI2TbelEMe)9ZH5u_~5Kf$siXWoZcxJ8RLVK zCM82fd~lYeWGsk}@%)&8KMd$ZeoQhSGV_#?4NN$7P%?JG$5?*o^g*S|+Daz?#0X0W z05Q@M0@~^o96|s7-}d>JGa#p%$#J2+&k7pZ8Y|Tj0SRnC1b`T22>~ERTS5ScF_sVj zVyqqBxZ|kNoXin)tbE%hmDFy`` zOg7S`@$fZZK2lc}_Seccsc6ug!k{_j2F)cQF4jz`^XNY!^kzW-Y~RgoM_ftSz#vp$ z_M10wFMMY?6-OA%JIm$3`btV%E}B9g_2=&_hl*jiK*C_}TD-;^wvOjJ(Jpy|I}Dqn zb%R)@Dv(dx$=c9b8`^gIo-^QDK^NhgW?!vtYv%F?2et~$l*oB zC+&{=8H>-UDk;XBuw_U_<5DqPLMM?<(j|9wqq(aPrFv6ass(`9%n|}XY;FkwASPQv z0EjItAppb_O9%il)e-_gOtXXl5YsIo0K^PS2mrCAB?N%j$`S%V%(R365L;V90ElfY zAppd-mJk49J4*<_drKZ(%;f6t_SQfEh*_2p0AdGA2x#zC8Q-Gz~k*X#}($|N`72rKCb4+HRj`5eq3ihuE#H*FxVg#5-#5ffvIn* zZCJh|Kx^V-XrkH`4&xcPX3 zA5WT(r}*(SKlB~*`?B)$AMPcQ;MbTq)tW%pn()?|pw^mD)|ddAfzVfDP0B{!I?T~P zNkVG-th~MPPIgSK^@smNFZXQ*+;+x=ljL{vjm#vOZP+td-`<{Wn6LPZE2hR@*2594 zR?7lZ&~$wjnXx<-n0+0Yv3OxP|7Z8MeiKQu?3G~!^arl1FTx+IgZphH!y>ufgj9VO z2{8B97jSB)zG5+K*B5u)t*?IiGnwxrk638ib?1jjhNZ7vnI9t=7RvPl{8J>r3bEc@ zuo;OR% zH6GHeWUY-wC0hnHdci_3T${m^w3RXCO1a4CNtRx?HiIc?Mq|pAc#+d{EG}ESE32i2 z#p_8nj(LMAu34k%#Uf>MF|-36vrcWaqooDK8$<=}Fcp{>W}Ui7&%vY}k11Duw5w*3 zg6q^+A~jl5(#pr8iljx&B86F|Hd<5COyNpvj+faqXi>9BVV0?lV#&|QBSg#98ZBxT zDa z>C9rv)jI8}S^MTHwXA*1RpHXr#gwamTGXt4H>4Z5rk^%z0Mf<9A{`_xYZkw;zD;K< z^_r4&fU&3=YEf(tv1qBZ8dT+{t0WI{JyB^@Qdgyu?SNOyW1EQbSScUWQ@>15)tGKf zSI5I$s6XVV8+GUEH4e_bgaWdH1Bk{Uco+=|`Uea{&&tuz&LsIvg#Zw~FMSV91ZoFxR{?1MQG5z2Ur`wM#5iv^^2*uE4nomeFGn928M z=#d)7YGbHp@G-@40_u13`-l8S1F+BS_XB_Y@yA=^)tC00?qI<7DZk-3b%sz_^*@YX zWvC@#!G{d4{wp?P;d}U1@0*Vg`0=6n_=q1mKzL=0AL2S3_>3PqD6#Mhe&}$-!ms%8 zHU4nW{0%?8H6P#c<9qY*13!K=9~{akX@_Cw7k>O|On&2sPKsFA$`73$v5+$o)qyDq zJ3c>R<^z|B$|RsP782$Lae!Y+Lpx5r5*^tNdJK6sJBM@dN4kl2X2VRkyfHx{I+f%r zCS!qF-fWnuT445gvl3`x8E92I0^`x@EkM|<`%+Ug~_aifws&?J}Ad+EWTR(zs zH5vnW3aqyg_F?1#%VApq^^rBVYZ@$J)Xw5>cms9=YWSdB4aSNi_)tY}ygWlAto+;v z!|-6dGi25z%;OY!R$K81ZMvxGnMh+o4-aLHZEK7VcVwAu|4fg5dBhA0rJA?D#-{Zs z%&<^efS7WnR{BOgEYx}wX6aoThnR9DSmgB5yB?03@sYF}ZFPg~2+_PbV$SqPs}fVL zQ0s2lj1Y|~h0%|YHYcWB>D8iUglJSL%m`5$p_p_eRrOfEt=H?f4PNd*RH`bB64MRw@DN}x-5<&gufZPHPV1xf+j^|u*1PrF{<=?K zr=ZWPCSt=zI-Q3DYzHp@J^oTl2mo=QB?N#t$PxlT9Bc^zAP%vF01(S8Apk_PB?N#t z)Di+fv{*s_h~<_L0OBx92mrCd5&}T{!x92O9Bv5#Adawv0FGOiV@0a$lx)JJQ zmPof>+JJ`6ja2t5P@3#ZBdeGBNrA088d*h&PYaB?f!jr%JUPBX9%4V+C$Vc$~nE2#*(t+gwsD zCq!^%1W%0MNdkw^+Q|_-C4#3$@HBx#DSNs=-07BTSrrMKA#ga^Gb47jz>UeCC2$kM zvjI8A;rqUG@Vk>Q4vvia-d0o@^^t~Ey)YkUwcae7wOm~*B=pY1e zNk2Q8R(Mh;QLX-q0F-9Wq%(dL%(BdGSZZT1v7J#83!j4&yOWSF%=4jGuB%{wpmg1C z-nyYxiM-HbC*eS-M)OV5bi&01D+%S8avT5F4djc?60Nhe+G1X`m!q%wo^(mDJ$9Q%98{A7E{Y|^=_SK8mEbA=ad z5rtu=_QOz^*K7WEApRn+yLx5yQ5Z%|v0;dNUm*;$nb*0SPvAX` zSHpG{-|xcf2q}5;u8!9kI2zA-VN0;GMBRidFVt1|Q)-LC$c30!tydZnH98)Nq`C#t zx%c62cnxD_*YI>7(%_wazfOzg(7T1Xe!~=s^tU6#^-aYMm=Qh)zOk+Ooss#!+cUo- zGGEi4`M)Cb=V{J1dn+WaoT2lZ+17=4>(11>5?}oSsnQIN?}-uamrN}`7ecaKat=Ui zVihEEcv}$LB{We``pQUM;GXZfaC;ZY=j-s+I$loH?u>(YX=YET#*7ClW2#O7C`awZ zE&Rw^Zh{WM!VI?k`r~=^p@f*j=`2P4+z*~sCGiYf_&nT2hf}0G@E4)AScDF|$liVc zFL2*rs%aH63oG|WM=cVv9?Po5c0Y8ax6yufKeXCgbfhh-Iu5%^9Pq}&--^R^E)K(@ zI1FzahXpPUxRdaIISy=FaTgW#LkjX3ha<~T)LueJ%CeZ>dbx+%MzK7zH*WP{HX&Ck z(!G*%?F4~I?{F`6X_Y_;P37)CRxv+4z8RT5d1 zbXgVCS#=7s3YUlEXO+Ao%l^anW_v?UviUUBJK)MFpBTs8|1^#-M{#`o@5k{57srWF z9C33~n>gaJdK1Sd{`ca@y2>-3dmxUv^WkGE5o@^&Rjy5@;+CF{exjKs>Q#u9XLio3 zzKPbv1t4>ikfW+2>gw|IAedL5&!Fk1Fs)xf6V(Ya>KSOss7vstG*<)j`efJ{KVEsp zD`~X#e2&zS&0zEI)bpEM9od-4sq6VBg!%P+fB2&7`Q~uItK*Ca_0aM}>SD$E-C&HIZboErE}=w8LBZI> zZ0X3HeLt+f&HdAI`vzPu%WeUg9IpH=Oxp4+FT^USo?Aess>s&UM63$FV@F?ln3_y+ zcn^j$@$#H-3OL!jw$B4Ih+UQL%* zR)kY2#*^)3c$~E&oCim|3}&$Da*f@6G>^8qk0zGGB6*@7Dl*V3NO)CAG$3dLZeejK zMtdCI!SHm-)TKLxcM_t_f_6t*Eo919Ca}Uk4Su62bkgm}tMH%`+L2Ce+L4j(wMrM= zUQ^U_6 zk;tBkugZ>UOMZ1i(a3$wgD7>vwvd#G+C;k6MyYdDCOV@`barK;qnZhKNA9VIZ5>-{PK-tZNVqN-3Uq12 zq(m|QkN2AHuzk!f9h#2Ux=}b)*5R@%I+(LaaqkU8be?H1I-x8Vv=`J;t3Z44CQEHH zdn07oWQbhUWH3YXq>h6WT8%U-hE6!VF^yoy!b-Xy)3pv&cTjPxB&?zss`qWulIE^O z&x6NqF>o3$HChV51VR*m1oD3|gHndNR2Dv|d#OJu0O=xh=IE!H9-WvTtG%X2bb7F? zKY~#1ZQ|X%ZM-?xVvN?(vqTmP=Mry`i{j0uGEXX}Gq?3VX_yhvKL_6r(&8oATfqU>(1IcuhCx__ZBx-vxio zc>HICXT9b7w1 zfE`L4494OzKVtFlepvD{C0OxLKZ5br3-be!t3T1zwsC$iGXIU{>>Hu1?d@aoe74Q= zqhN}vj8$wuGm@oSOnh~`<~c)yNnNry7Gjq%k;6t}qCOT*#!m?KH(a}M6~l^XiLTfG zk{?%FKe*ljO?+=(#sl?>OE@9!*2hd_d-U{pe;qRsPDQqrDt9UDmHW+HZvnR*xRP*t z{KeF~0fkU~gVm3aOCYFmZV@5w77@I4aI1sDgM6u`i}mCKHzV5R5P9alUc0{he0Y&J z06P=Om&48J7=Y14r8xk@({~w#rY951+ve8l1%*~u)9Q|5l_si^R&kCXqfn1C;Z!bv zS*sToT0M(acM_{KQB|-S%JbmX!bOD^&W z>gqL&^&0g0NrkuPV%^oU96pS)GN1XEI}x2~3Y&VL;NAGj4#=US;XFGQw!Fn`bI*dT zPT?e)PQ0wZPFVU-F+K#wB_SUR$95zfQPqesCD^_e!GX+9CE*k?{*1<*jWN#xiE-eg z@fGjm>ICCCFC-GfUa=4t)gxZiObtJU10H9km!VME#+C75UEa^sD{;!lN|1>OCTH=Q z@(f1#;pHrgdaPaK?SV49K+s){AR8sg4+We1{ZVigHqlbtd-AV{pmq)UkElmhw!CZYtD+ohhguIuvec5 zx8b5ErZd~gZ*e$_zu1yPKgxEA;{l8Uy#af8y>)pU?CrWzmq*|G!P|I{9uF`tcZB5r zCNKAdGcq1H#uN;;g+w zrJcM8zYG7&ANIe+)#!PfA*E9rzqyx(~J6`yd$n1tjd4G5j&c>fuI1PVY3FoLp zgrrfIu*O_irFPJTp0RK%*kUVRVnU0O{wjm3HzeEkD-XfQ^rasm;_LN04@c%7w`cxH zWd2Ee=8s0^pSEZISY-ZLd*+Wv=AXA`{zPQ{MSJE?M&@7AoMi$pSbD|mtK;a8!!xj1 z9G(QT@Jt{tj@x_DsZ;K4$c4I2bNxOZ@@l3^FWgV(8>aLVyxjNT&=^x7TmXY|%>1Zf z8pS0!OSN3n#slL+xR2WoUYPAs)3!dC5e`3#P)wMvx^L^K~KP+Gq*oY@^4xa+JGB$CbMh@sJ8ih-*%} zfw*G25t+p6Q{e%0!*Aw(MqaSc;vrB%wha{3pQp?-kuO}J(ghM{sfn|xa`EitFplRckiYdzRqBN3RDB1Cjf`w(mlbR6 z7c(oA{%SlT#|f4;!i<)~YVnQP$GxVupem zFgg^nKU^HOZOn@~=qHxwwj{1Nn@YuV&mpv>NG?%8N@{g`H-#Nko%Cax)UJwhX;SOq z8!6_CNKu(uONnY}GJFg~Vfhm|(ppaR_L8CK)Z4f)eW$qGPuNV+J$ZFiN%jJydm8Ex zw1u>@`NEDG*;bO}rX(7^P?Yq&q$vGUuaBMe?&f>>rAYp~LD+e4E4K}9zph_F6IF}8 zil={gZXlU1Q=g+G&7Fk0W0+Nulbf$~T)x03JgzE1<6VNr+qMTernj^Qybo5( z%KfT&?E%-K4rsS5qI$%u%QIV|94VWZg0Q|+PUG4c>7Egw6!N`ki_c-4ETYF6}q2c^{8(#n8y(Y$K z1mc8ZUN~PWReNDyD2G*m)G|L--&EG1=8^ai?ddXwx2dTyrf~0m#%o-NVJ5edVtS3j zZ{~ij>e|*!c88mpY^qyZGubX~=8mp{l6aw;vVYvnEnd|_%n;s!aK~3-O{KfO0IF2& z?Sff^>U|KKfr@xc8bIA9KXFHIwQ1AvU^s}8r=OYB5u@tX z&h)xiEH)J{<_C1D{MJM>>j#tvF|HyRKEpgLaOe<*%!YwC`yV9bKCHr^LEp<*lz)5jzeS2Aj1%fZc;2#2H*aboy;znGFySq%1jUyrM?!?{Q-Q9EJE;oAPuH-vU z_s(IbPu=@|`GwwRdY-PTuCA)CuCD2BZrj8?d!lF7Vz#X`b5&z@yQ8^np_!|ivVoW8 zwxKgubuI0ch1JWFL3Wh>w2u~7)}qiVb*=4K*E+babdr79I@B)b84+nc#!F!~!(jM~ zrpdOt-Clg%T;EU{YzID5^1_(SJTPTSCuK-Q$;)ImBf*rZofK7+yl7@K6-+s(lQI`d zUQREyeA=I5J4=24wP$I_i%eblKs)|+3bV0^rk3vG4mK%YXT5uCOWG*3rJ)uIy_iYn z+NLvCOtuvVQ?LehZ(F&(c9#0`7IvA)8Ie|Qq;1?#TYk}s-`ca(ua%uqvb+5CE#2E@ zLtm>rh2mFJXE;kkd8&QL?nxd~SD#nkx@=&r*7j-|dv%Zr4W;7^E%!{x8b4PKPAOF8 znV}q#QmE52l|u^(=L5!`SD|gHx0GhXdzN4Iw$tQKgS9);EMdNnw86HAy^z<+`!J@~ zG_@{VU;0zlKxOy2D|OE1pVu6~ahckWYh|0dZ%S?HS9**GlOat5vO`f{*WA{vuBEB` zM!jkIb!I`B*qv+Ar*f?-{ zkG7hw**zg{+D~G8dmn_oGR~%xY}Z&ml!$Ar6IVLSsC>9BY_Aq(sN@LanO|_r{dqg) z)|nVkS=%9%k2KTj=6_k=n2-6L`d)V{dW{_;ewHcuSYNlAJrAt0-KnV=(|)QQk!jq; zwrWG1rDzDrl@d6fIx;xb!V zQ$Cg)wLIdy(_(ouIXmv1g0Rl;apsILhx7Ve)^JbBoy#m5E3^&Sy(l}@1E;X25KZ@v z^pve6HTM{IiAB-1e1dUL`+~h}Z)9-Lnt~H;<)o`^P1zm|Y`x}=Rpm=9o#i@44Xq>H zkc}RmWUBq{D&NMspzI0b-)}JJ-OyID@s>A1?opd{vgze|Iy|+L$X~mAvL>13lLua~ z8zEJb2lCP5MJIC8pnKO*O_kx}n)hb6`&OOCMw&Vg`@LtEDlJ(i)VyQaRkO^tmMix6 z9Q(JB{d>{=b-t&xuzkMG{&CYKE133||E%KFd~W}Kw|`tm+Gm?Hm9OXRH@siOQlI^2 zlku*xUCMrDl0oP9z>u4*=XWYy*V>BO_ERiBrd$Wsx3}5pW6Je-`SE4!Y-(P2CuPbc zzAT?g7M^AcJK)_L%cmPz17LT8tPSXISGD$e*O^madc3#(%si@{M69JVZGOAGk){$@ z)x;c|TGq2uu6FC3seS8b3|OJI#(o2ywJ+3VdUfSJ^7%!!|2L3&erA(_t+=tWA_H4- zQ)NX4i&jlN;)go7R=&@ma}M6jH~dzjY_T4cmHVlqE%!?|Q|8+3Et!@h<+JQhP1{OV zylyc+>qITxtF~m%yQGa{EN>xu?(x}X<=fSDe4v@j&TA-rhx9L9Z>6@TW}ubfTLxx* zY2Gz(OIEx|=k1krW?(DssI17qR@_-xk%6tatFj`4Wv!C(O{GY*{$lTs%!0Qw&g}-J zJNRTD?&QN=9Ut!I!#y1z?&ZUMd}w91(!)-VWMuh7TQbuAPP6=~o%E!|*KF67btS6< z&3iv&<*Y~TWY%_VD%q>9Y@D-cf%mB||82WP{?~T4k55GQb<79uX&Oe=#e_|F>>(lK_1R9=?$7H|9=@@}2 z)mYP>55No`P|lu3UC!qASU60qu|Lc`u|M3b%lEZ58v{Pr@+g~G(cJi=U9Z&g5ZLC@ zyzKE6Hf-5`w$;yk%0;7X%^VB+!>wlfb9o-*75SfY^OeWjrmY9&w;4*qEPFc!|4YN+ z>>fhvY^4=BFU?k3Ci{~OiMHSx~Y z{A^#8?z3)BxouVpZS?37o!0L@bNztM>({lXuVj960&7VcqLxaDu`yB=M; zH|;abuC*-N8piEwU)lvt`Ri`kRgHzSUB{>L-4-;x^{mNbaHM|VnBHv@?3Zmec#uca zwus%(&9*JyX?EFO@7q|}oE3c=%WGJ`*>H9@`{i%Gl|^X(TREt!8E)ss!%16x-N8#+ znrcUP%WA<4JHc9O#l0LZOa^uz_qQd~I(ze2+_t|xx{g2BR@Br^eX;t8bhlXBFUpPx z-;~-cgsCr8J3VkN&K$le*)O5&gTmA@>aXiB05)x#c5{q@X#cKCm__LYCp6!pl zfS^xfmT2qSZ*85EPR}=au%?ZPEB(5**r?KYdsIocJg{a08%bcz*`Li?cDB&Ck|ea| zN@)G(GwmwT_vY)!9n+oSO4;X4%O*16%R0 z%8CrOuPr@qzv#%0U0(~87ew^uFWbISQ8Reh-Z5t@roL&5tVX?IFr!f%8>>cEqxhzD zoE4SQXgffJ@S3g3>TL#;?X4{zjM&(zVy@uyIcCY+Zo7;sP$^P=PP>H ziV>yh)+oM6vhy?6*|y!lmX^}}eQoC(Gx5KCTVBa}Jyx)GvBytk6>rp>of9yT3lD7H+Y-re$oZX8 zZjs;Bu|u8r;oavf+`J%kgj~MLcAg<>8y@L&BrmYADsgk^U&(}btp-#FV68$!`|Os+ z^3@hn=P=0RN*EWCHXBj*fNI)?cXI8TYr1_|x(Kavd={HsHFfUa+p~BF^)G+Ll>F>& za(!tL%fUr=&8O|v&uBTNi_e$nv_ltL$d#RFU4}sl`>)ZC0&g1K#!5=(Yo#CTyK`*) z@wUEKX6AKetnty5`TV^xR;ug!_h=iEXK1D7*h#BfV|fnCksfV)ZQ~kiN@ZBLiFUPGv;~d7SHX#P4m&)DO(c+Nk#pW;BkmnA)$kFo#d$n<+FbwNpQ= zmgYseE|d6X3eAx<52J1MG=i~S+V%FW{rSR99-Kz);6%Dx6jAl z`D?SA`}qe;{!N6y{$#+_$IUatxTO-tq+2Um2DaU8l@%G-@7uE`hcoW&Y{c)|ea#)& z8p6I4%F}mOj$_*IHfUdpv@Kbi{k4fh_hegGnwRdJ=IjH(+&eRv`zpcAIlyvjLPs$C zwP5bA1hYctV6qRk3!jjO?0N>4;62TSA)-+&-7kA8S zOWaa#xA()_dpx<(-u{R6Kq)JqRwBs2R(!?^D;u157O=loTy4y)ZVKz|S%9`Jv%C0P ze!!A$jU_WCcoAk@`-8Tkl+9Tn=+7%bXRv#1X`*d3Z~@zT-I-hS!(&p}aUfbs*%XM{ zZ0@*KVaH^NJWctUJPE7RjqaLzZI%I~INz`QzS1$-xv8$i zj1B7}@&K;J@kQ?g5A|@|HaPa{Ge}hkE1sG z9m}!AVMEYapRc~eSTT|Dpuhg@qu3PD6YwPK6F_>K2)}x_Y-b1nr?q2o~ zxE@`30^AEcbI#g+t6W=JFw;mInfxFAQb01W6<<|WWMC`4uB^zwR(w-gk-;jprR^-h z+R_fT%=V=9z?xd?7+pofxxKB-K3&bH9r;A8-&TUiz*c-$S&_jJwWU=V6SLvByqtD= z(aX+R7y5p+Iw}q`e)Be;?v)P91s{NAVI7>mhO*?2Ve{9|35jGuTX7b91+Lv~B zdz$^>eXH4wIseU*jh~UT>z-d@vE=#jf^~jQ;27m|>)M~RM09U|%9cIsc^g)ZMt8T@ zG+M;@^>5E^dw_(VF9UZfV(j{~N?2ep0^VnvmuVQ7Tv}@LnX((OV>8q?` zHe~6Ju4W#7olRJJ%TnFXde@}`0j8B?llDeg8u^Xh(`3-hPJzX|YRBVG|7XmXb&mP+ zP9OTzPn}_VmNi0u8_Z}t!s2az#-bTMjc-cxaxs^8EgMQ#blS9f4{F-F+RUnFNuG^x zKWBgYbsg7GZ{5tUrRVu%Q?3{pT3NSVR9g$IRQdvHi@zCe$|eYw6q4Ss%`x*c6~+%gPpzKZUee4yBGo7C-7d)=98`>icx|OTe4cvLdu}gLijB$$5 zBm4B;XAuSwc%H``_KTRaEu794JF@ePdOOowSHYJ3cvi-oWLEF;zbu1C*ul=Tp!~CK zYV~qG4rf)=efn(F&z_snIau~&vwrr33>#j^pU`Gu+Qp7<*k{)5N;bWvxg*pXh5@@# z7*FBYR!WQ?no&!39K6vBbqXZQc>DZA=gis2R#4iqZAnMAcl~&-hE=Y%CGwUd>zB{> z|5N1h($-RXn}p45NQSIDD7y;#zv|yro$6o5KK%bQ4%P^~V%fjww|{0oP3k(M#V(yI zbHbaZ+9gWoCg>j`PPArr_%yyL*^Q;Pl8t`Z@K$A> z@iN)(CTnWew)!&2GHWMmLo3%phuFD<6KHw3ol%Wx-m^?>EU(TQJDX+wQo97LIgxWi z-6;E2)0Gxq-VL?;8V%zvurF(~!^Oob)ux%J+6=QFDAkt_vuo+BO4t$0?$I~bwUl3@ zlDE~i^|8UfJUIK(8u`)B)@Eg?5>7{412?jL8I%3+$%AW}QBRA;T-EOUBB~PP0Gkwt z->0c1H>`Y)ew6*-cZ!$W-;=Wa$l55L55(;m3&}bpO)Wjg9ce;eyDH>_pP$&Xk-I+p zRLcUfR=wlYZbOiq)ky;*1@!}azPCo7#;NwIqRxls7!J?t*&*bc(tLIZvozbSp7*R5 zm-Q0LZwhJu5Z~e1+o{<+Oj;= z?t?Q=t8!6OH-&9#+YjRYI7aLAnnA}8>cS+3j!h1+uPz=w(7N4+LZ!oOIl}t-huc0) zqYJ=u{9dyUS!uUj%<91a`#a1UvbwBDTL#u=P_nst9rts(WOa*tuPeW7o12q$>|0yw z$_LqJD=>ETK>zbK3+q(!t3PvF>3rJTN3$he&+>k3Y_~+acWR!h?lZs3P1=qvvo2_l zmY!X$0$N&|eSOyT&AXxXc{h~llxtTuoB4HK2)TX!mK6q@!rj*frr!pwcsG_0w$$*m zfp=`p=ud1yedCOl`)0OX)>G}?&2Er)6y5B`cb9Bzrmkj=A(dx9oKZ96w!G=7ty!&R z@{{@P?%JBVnnjnW$@az$aFgwPc72tBt>{`=k%6t~R#}mOt!S>S$iP-~udK+xR`jT> z$iP|4@k%6t~Q(2LLt>{}>k%6t~S6PvPt>|A_kpX3@ zspbV6n>vkhkd4?3FMVZCx2Y|iPg!Z7*4sKyrLS#G|I#-+^=3XBmM`Dh%MM2QOOEsK z(zls@VJH2&|D}JQ=@)m>fB0YekC}eyOnvU@XY=1(OO@?jkHtT*Jk`=zGkoTH8xbX4 zKmD&@f6juv{J#fV46YJQ>6iZ%?5|m{*LDh)D#CvqshvCjw9(RKJ4ae7TPmr~*MGNE zxg+(z{jX?#&!V~h|38{Rm1rjQzkbCJ4?N7H`Qv{@^Jf;#m02_oP!Q~Au6f@6^}jaz zJKOB)nVYp#Hk9=yh z($!wveu1UU(nDGQKR51K*_g|buC?7t7v&pM@+%vw7;}$~nETaNw&us}c5ku1bVyrHr=uX4A|8%meun^WTd@68)4o7<%Tn>UuO$Tz1fq&BxfLZ{N|1bLl2RvJ)d^ z(xyh#?7;iU+i$bA!)r@4hUM%i+XrfOIg8jn&_I>5BVZqBR?68P*$3)GISbW3(D;_K zQf42hujMS;?E|&2oMobYph+)hCD}gEq?WU?(cG2~3=gQB8#1tyMN4Hx2DV~gWkm+I zVo+s82DW0>%8CqZ#cY)o8D#r!!!R{xWapd5EDbjBRl1$D++hn&JltRJxc69I%Wkso zW1~E^<-0n*<>r0)o{kUq^5MRY5BKxofsPLk^5LQELpEs3dCu-f)ST((Icso;mIh}T z>pWS5BYSUB01ITYqRyF ztn>POA8km)Zt$#Qt;!UOwd347pWV*Pzb;R(eQ#;M*JB@EI&J2ct7cygw#F+@LQDHh zwg#=9Sq)a)vi!DZ5!Rel)dsSgl>%L91MNa)A4aXM4eWXu;B6q67xwJ+>|R1CKT26f zR6-cu#8|rh{v~Tm2`6h52nX=y&@P0s<}g2cCGxBi#E5hdJvxGz*(lm*3stkelppmH z8T|hnh&PQ4d|4n3rM$Fc`CkcSw$3TpXIX}?xZj%1xXI22TiVUm?CH9FdzNbLI(JlW z`^=H)J4?x)+R;Ycr|GsCB>83iuXf+vo=$O)9kEhYiv950SW$0#3_(`n?HMMknPG>F zi;N-E8vCn`zjN_-b1pjg#>SZW!p4}h^^8%kY#Tn!wN~kAD{OXA!JllB@2CZ8OV8K` zyAH6`&~Y5y87(Z!E7>V^lx_8Fz7>BuuDmEawKZIOoo!{OZXYhBxc1^N*Ir!6*}i6N zT5ZkJ=1r%J*me)_*R$#_3x#hrga9-yfUQ zlfA;Or)Brd2RgD(C3{#`_7!>KxnW_Kz(Vo`uwEU)lbzftrtA*Js1mYd22fE1UR~ zkHMEVvKcM;FMaZFvMXvE$0zKr1ItfYcs*^n$^Mjov(b#oHU7Yw`n5VX$Od?uY-sl> z%XqspwbEv-V!L%NdHbnt z#l4X>i_NmwUO?|z%na87wU*@1NECM=N^6nzHmT26e(o#R>=;Pm+`G1gdmiC_(Gl+G zJlrw-sh=`$r{EhZ!GGx?bqfAJzOoX0*49#A&Zi!96oY*C?RQcf)9gfR*)q1#e$j2C z?szq&Mw_XU<;9TGhtVAB?CmIb_-hQum%sbEjX@&XIiG+5c} z!v5JvT+Ic9I~BQqXa$wZ@w2tkoHhlgdsAgxUk2AhaB-p=OC4_J)9tu}4EGF5HLVFYaVt4mAq zl}%ckayV-&?>Poz?C(W(3~EP>)MMw(aWIp``qwsDJgNY@d?-;Iet@Q5GscI5uvcdNiqOytuLTlniBN&Gg};2aL!l zUzBWJX@VuHB3V<~AwlAM*`-^x#ELv5NbHB~#!&5w#;aNY}Bjo3ho>^>7K&eJuJHB{D8gIHZ?YP(G@1d={4=ea#|7$6bLu zBuGq+mHn#@iLG)8%B4^W$lY9mRAZ$o9y{_RmmqaT;s8}8oZQVNNWG9a)KwCBl1q>V zAkABp7`x3SNXsK}6stmNaS0NA3@uQU2D$`k8WM-PDx^U!K{^I06yI50f@Cw^YD9Uc z3W<|io^VL#B}1Z)?UW21nD-UkOlVS zp$-Ys14yB$$R$V*BT@XSqMOSlNG~O&p)NuC0EwofDx|qxg7gUz^`=U)A^whpL;4{Z z-Efy6@!PBz(g>Fz&4LsP-8?QqvS$q>N zR7Vy-S_&zYr%^6Jnve`>v`dgyOG=y!^O6Z^9i&j`sM5Iv$$ocHBOfW~9)Ft>hAySy0u+`bS_dTbQ4{I zbPH0b=Igiw>3*cpj<4$yq(_r!TF)g&Zza>TzDtn4M+(j11};JBYw3xHWkZ)B&4m=o z(?%{qS{!MFQB`Jc>=LA{k$M)TOFf^-j({i~KXcL~x% zNFhyIxCF_5Cv4|^qpIk(bP3XTNPUf}q^(?nRBNZPcpZ{q+MKs^cYguyIoy^^j0#Y-CTn7JyIwtySoHw29nji>V%hFf>dX%Tx@;!a0${7 zq>)BdS+J)|kXAwpg>EmGAZ?5k+RS#BAnlD5%G2I1L7IwW<-9sg`?v(@Fr+?3X0p;2t&0>g^AMLHZI5Kx zP#w~tEySeGbfim=?n4SS_$ZekJ)M+} zb_vq!NOKrfW#%z1LHZFXB>Y&HAk|x)i&g44mmoDGg+}anmmm#63R!T1OOWP8vVYYH zKhY&fuWeUeL%)itap@Alb7866NW9mmt|R*c#1hR29+%E;cikXAw(T$C<$3DWjRp;fuWB}fM+({!mzknBN6iG*L~5+r+EM53Bs z?h+*X{bwSiD_nwPzcNg;<5#)_$$lM_I4oDW1j!~gC)%g0U4rxz(lDc{9KXgTNQ3PB z8yk#kU4k?e$y&SWkgjtHQd?5G-X%y2B6T&YO4AK4L0TNi3SPBzqf3xhNJe*)OOVz? z>SXNM)pu@cUeXbPQ4` zzW2KX>H1`J54Z&B8KjVz54r^DGo%4VRSAE{B}jiEg^KX7OOWg#YKa>Bh)a+LAcg22 zbqP`{lGWnsG(F}Lq=k^|U$ykOOOTdBvXW3OJ>e3hHIeFy(vvPhnurwA^ps1GHbV+! z{ArgUO+#ums><(6s6}~f@CuS6P$SP1Z^DF#hEdB%583i0&1aAlbZt#5wp?mmsZ$ zG}5T*u)O9Hqyvybd3xO?NOn^`v3GB{1nKys^rlOYPDctQ^DUPkorN^es4C%ay9DV5 zq)?vTaS77BNQ)R%h4ijVkbX>t^qxzQYI@tE!Kf;v_g#Y2fE3PmAGidm7gA`%K6D9E z8`7*sRndLq5~SsktTI$fAG-u;B9e;Izg>c~7t&ls=@XYAor)A1`%hhhbUji(qpE~| z<`SfbkXnk;=Pp5d9Vt|)FI#kb3p8MZHl~NME@G$=^);$GEPuKLX)01Uz<;>}=}@H5VEpY8q~nofR2AI}mmpn-R9lq(aS755NTI0I z*qZz%AEXqw!f z@Zh6-?;w4G6iypWEh2OGn{Av3 zsfSCD>?cGC$!2|bBplN0Nof|BAW^>K7zUGz@+`=v8O?(f+9zJQnM;sHBXutxyFM;K z8kY>oept~F5~L-OLN)K_5~O93nu^icFS9yAg0wPHIJYn>Do=PeKWc3xTUA}?c&KnL zL7Irvttbt23DPD=Aq(u+wjCis+CG`4SzUs(J5ngVv$+Im5>hxUv%3W8V5HC!o5LkY zharXH%S#FJEP!-0Qs}M?b_vpjNTJs=#3e}gBF$aQOt}Q>>Y0}&G!z{LP#N>?5CX_5~OiRA;*Wi1Zhd6&_vtMDLX=fv@}vU!1fD)4hhog zNTL5!atYElNcOL~_>Oc5(!NNc&1`iE(qTxg#gN)uf^{ zLS~L}3DRFkp_>b!2Gxdf>dDKtIny98-8Qm6~TrY=EhM+!w{GnXJ8f;6NU-R3Sq zx(F$>zFW8i={lrPe7AH7(mlyEZRHZAr;$SIyR}P@UPlTwcpH}>eTHQJsxxz2mmvL( zWc|8oX*-u7^|C%@jBb0EAPqqZIlhBSkj5a{HC}aeJGumEDWs65om_&nb}~&ny98+~ zq);+Jp?qkiv<2HgKQB><|LOO4MYlMe6mZBN=TswPjLy-IHa(5 zQ(c0zB2p+%2e|~veu=zr0tPrD;D2_U4k?X$ObOcg3m0jc#q@$5SdAisoNT(o$ zqH>8#kftMr4)UcgK{_8v#l5@CB}n%pg@j-35~PQaLO0|Jmmt}T!V}&9D_w&0TvEEq zB}n#W+eCS~+9gOIBZa2t8kZn_ixd)mtxJ%8LNcf>1=qO*>1U*HadW*(kp4=h=?0e| zb+y_XOXiI(LF$VXs?<#`L7FQm-Ru&iu}Gm+xy2<&DI+=Ocyk zbgxU0E=i{8K9?X}os{l(3DQkT=>eA@-H8<1%m-b9^i(pWhg^d6Vp4k8B}lI)rAJ(X z^j=bW)FnutAccfK<`Se|k|90r5~RPA(i1L0vUgY}iprBNLF%5Ao^lCN-=y@kOOR$w zO3%0iiJKp>_&)0rq?Y{EuNHKbP3XON$Dk*Agz*=UUmu6 z+DYkOEJ}JHC5~SUd((5ik+B+$|;S!`tN$E|OARUsF-f{`j z(MjoTmmr;#l-_X((wRx=U6&x8pOoHn3DRXr>3x?VU5gZ|`3EjRx-S{hhb}>SG%0=L z5~OF6(#I}AdO0cm+a*YECZ$hYg7iUB`qU*zpCzTwT!QpXQu^E_NIxf~FI?_7d3Iw^hc5~Kx@ zLdpEWB}fyJA^qqQq}7trPcA`PCn^2x5~Pij(l0JS+A1mi>Jp?KlhSW4LE?oDu@wC7 z5~O{T(jP8Cnv#_MbP3X7N$D?_ARU{O{&oq{DM@LDOOVb^O8>Y7>B6K`W4+=0P9UT! zl2R9!AYG3X%2TaNkRC{eROb?;$CFaMOOT#RN)0YSdL=0}x&-O%q}1dRq>qwPSC=4t zk(9c*1j*jJnkaP5ELjOOSdcrJgQ9YDr2wcqT6e*&}G?OiI06 zf;22C^>ztTTT<%d5~TT)QeT%KjZaGbT!OT8QtIy#q!p7AZ;{Ni0MZ&ssl_Eo>m?-~ z(36LhJ*vmvGL|R>gIt2NO;VcGB}h9Xg-f^DT!LgT)k;XSy9CKzo0O2|a0!yVz$zil z=@O)4lG0$8Alds}5+Mz73DWeW#MOG9nUL&Z%!!cZatYGaNr^`y=P=>=B>|spJx*Z@~O@5~MmC)Q`hBJUS}ROh`SG(kPc84MYlK81|Okj*uV?PD*23f;2oS&Fd1RQAx?3 ztk@A9qy>_ay@#bkg0yH-vS-?MNRXCEO7;lX4hhmqNa3F1f-XT?11a2fTgW9yn;?ZT z)`eYyv~4oFMO=ckGg27q;jv%&0nQ!(R!&AY-X%!;BZULJs7sIzN=CPsOOOsrMz^?2 zkd9A=w1i8LPC*Le<4d{(>0G364(6d|c@|`kH@glg+%a9+B}fk?)3l6BkRC@0L%Pel z1nK3Zw46(j-a`s!gymg=^l36p6I_DyAEZz+S8xf^_ei18t>_Y@Kaj#rnU!3E)YHc6 zV{|LK1gRfVD4DCc1ZfabxG%M;OOQq&g&beaB}nrlh0I*tB}j`Rg?w7WB}hvng@mu^ z5~K-9X)Tu^t&&XB+Acwwh!pm2qDzoAPe!+nOOUof3VXM%OOSR#3TMvsT!OSWQn+)o zzDtnyM+%j51D7Bjf)w^{Lzf_(kPK-fmmr;m6sq~gEJp^K zkwQDZnM;uVg%pbK<}N|{5Gma7-@+wGzaWJi-_j*We;|eXEnB$+soPwskhXRSQqQEc zjZ2XFAcf4_)+I=DAcc7t+qnd36jI27?OlSj3{oh*JGcaCMWk?8c615Sx=7&w@8lAs z?U6!`@9Yw!osmKo?BWuneUL)Kx~of&rXq#*X*ZW39g&oFcL~x7NTGcyy9DV}q)=4$ za0$}INoh})Al-yycj2pVJnrQZq^FU>0d98*()μd{FT>EB6dAD19~ffVZ9zAi!f zH5t-=E|eEXtV@tSLmE|-j&li8zqxHOq9`5j5~T4+rJ{6#OOUoohIFD!kd8#MzGij8PjU&; z%O#3+BB}j`Rg%i*PEAj?Mxl53KL<*UCg-ejSSrw1cl`cUVf)sN6DwiOQ zLkg$-t6hS$I#TF~UE>m@Es#P*xYi{|laNAYUgr{|GmxyRSLgWkE{EK2~vxlSK=ArZkHe}gfz4m-90Wr+5jmW zmU~@-v=34!PxrY5=_DkpR@HlVze|vALkd~&fJ>0xL<*Vtpi7YcNJ^dT!J(nDdhOmE{W${kX~{L(qN=eTV8ev(&EXG{^b&+^^+mJ;u562kwPhW)g?%$ zC8K-IB}msJg?jh8OORephV+I@kiJ3+rQl7MAa%1IbUZ9?xddtMr1Z8+kd{CSP0u?n zLD~>06yJAUf^R>1L$xaDYE@2~yo? zTZD@6u}hFfAcY>rzg>c~7*a_1CoVynh!hh3sY{S{MGAeo&s>6Z7*gmUf9?{bvygfg zTe>e?f^->D=&pU~5~O>OLaXv0mms}@6!PgSmmqzC6bjwfE;gOefs=LAF zkwT&S#U)6OAca=tSC=4tkc{p(mmvL#)V)}qes>8{)j(2ql(lbcbfvDcQZZ1LkIVm-}1Zl{8wy@G!9a49fAgzuxcTwu$5~Mwl z!m;b=5~MSbLXUM8mmuAT6!xx{OOQT83dgRuOOU$HZ;P;ZeO!Vx9x0TkzAi!91SvEa z{ak{y7gDGQ{au1I9VxU=16+c18&W7wEiOTNEh!Cj3DU1fp_&hJ2@;Pqji<6%U4pbQ zQm4&@0;IkR+G6fv7R==mq=k^eu^Z|Vr1guy&!7$uh=EsxYxlz1Ono|%wVMe0+OT3v#)DN^sE)aDYT zU6I0>bCgSv_CXp@3~97Wkd8qL9hxyNLAn$v41Le*5~N3v!YSY0LfnxBke){ht;&2Z zLHYzK^u%~?T%MVbx-4vqkWUM^1gQ^Fh;BibAeEBRLM}mC3aO=-nG3rFX+5O=MQIV2 zAnk+{O2IgnARUMly0zn7f^=$9TGS;-*CNebOw(d6L3#lx9N@)Wg7ghiNH}lm%MVNT z&c1(=(vmJg>b;09Lc_Y0OOS>jh3J-c3DVePNXxhcX(^PQ2M(XHVUq^*-7t?3e^eUUNJGZkBINk`EOOTdC>ROa`a|zN4NMYn* zcb6cola$IXLD~{2BzzB-Ankw@8jL+%g0ve_Lowldxddr1q>u&eEk_2Xk|FKq5~K@|Lc;fV3DQ+a;n*GE5~Q1uLOvbn5~SOaLc==A zB}k7U*(h>#y_@V3r0MP4p!$K{^R3oDnW{3DW6Ep*&sY5~PceLOxyY5~OR8LSui0OOS3y3N`pj zmmocX6x#8tT!QpMQo7nDNUtM>Ds_!ZkUl^P7lGHh1nC!~knrnVg4DF6EkZ@O-X%x_ zkme{JyBl1BG#^s9zPr&SNXsJ~Rt)JTmmocf6zbj0EBbel_%W=9Hpce_iF#vp|>-Qg0X6_7&ZywfE}8zP0a;4YUS?T8fe>28-GO-7o% znB(`j1nJCVn(lQ8(p5+ypYC%B(%ndN7Nfi0B}lI$g~RfIOOU=s3N`pammvL$6f*N6 zmmqap+7_XVKkO2ufk@%n{1KNRjY&$6x&&zg(y*FBOZS*dkaj|{YrN{_@Nt(QU4j%& z)K9ns>1L!^iy=Mf5~OF4LJfY(B}ktkg#-MwOOSp;3fP6wU}Qxddrtq;LXy*(FHpA%%AQ zUoJt~9BFhhO|Q5F=~$%D!+6ytNGBqN9>!}fK{^*HRL<93f^-*BI0wJs5~TZ)Li_Zl zOOT#N3YGIMmmqzB6f*N|mmqzG6iUH6Eq-`pP9pC8W@&``RT)W0B@7hV+d~khVe^Ta>hO~@JkoHPS%en;V zK%`K7mvafyu}IyEd$+txkj_AAD@qewg7gqlxZqmBB}lI!g{Egkmms~JlvZ*H(g#T4 z*sbgmq|cGY6cfIROORStOG&G`1ZjDs?!}N+a|zO#NTZ9=>MlW=j198x&9Z0HiC^^n2=-pD0LTOx%*x3No*c0>xN z{7qbfv^!E5KHbzMNXHAEZjwuo_CgBH;bfN}O-V{qT!M5IQs@^2Q}IeTx*jYe%>Qsb+0kgogDRn9uQ7%E68!6P_qg{eD3MrI=V_br?2vRt9$GQY*0#dJHnvQb`(z;3Mc$Xk;gJfeX z)%ETKmmpn@WUXDbbfQa;enYZcua-`73DQavZDD7sYUyN`Af1k6P%WL}5~Q<{HY-Y} zx&-MLq;ObHa|zNvNNvTCPIn1X>pHdw!^~&61ZhR25yg!M#rZBlntwf8 zghuQFmmsZ(6f*Ndmmuwi6e_|+E8@mSm$?M# zc_bAx^KzFUeS;J_G*`F;={F>U>e_OpOOXCT3LU?zT!QpBQvYH|SGxqM&-%6q*9h0R z1ZgBvxG=reB}ikCLgl>9B}j`Pg*Nkgmmn>RG;cBCH@F08Po%y@=|-0z9f~xpDBa`| zq$`j@KHcmRqS>kwz9ny2B+% zJ0XP}ztbg1dn1LtyUQg==OBdxe78%GE=@}JxCH4sq;M^DuS<|_NlN#*1nDlMp~Wn? z-z7+IBMmD`54Z&BAEZ#F9&`y(&4#uJovepkg47i$7o^Y@JnIsqDanwYa|zN>Na2j|yi1UdK?(_f!6is%BZVw@(IrS1B84-; zOD;jW8Yz^5mtBH%3sN|C|8fb^vq)k5=@pkCeVRBOOXCS3VZjxOOU#5Y>RM3 z{ew%81|bb7?%j_rK^lz|YRgY9L0TLsB>ZQWAT5IwP8+|t1Zg#-FoyA~OOQ59hV+|D zkhVbz`SiO>kak5fs6N1dxCCjBWJrIy1nB^z&<**^B}fM&g)I2nB}m61g~Kw#B}f+` zjV>nqAD19Kj}#h=8aq(=`~pa?AT3%9sf$aHX4%9RA>p+yL7Ek5p<+mNEvKF&oR+k{PBF$5bZZ?-7t&OCjG`mZX_C#tcN^`gb>0G2aiqf1e zLAnzul!C!7L3$f0)RrMGLHYRjQn*nx!X-#2Accg_;}WFPkV0mbT!M5CQs`lfbP3W` zNvYK(NOvHG!_wvwq(_j36*F^`OOQT83TK4TE0MG9dq+^i6X=4GGAf1X73f+P(LAn$vbg~w53DPY{ z;Z(M;OOWnI3a5=lT!QpCQb_nXmms}>bXhT<#=8V*?=5W+627QQkPbx(XO}-Q;t!)wdDid9TG#gULr*&L{R6+_BVO^IXEsoSu z+`IK$f;2HHt?v?~9g#x9H*g8kq@=W=OOQ@L3JKrHB}f+|g~PJ3OOWnG3RP+ommoci z6l%++E=Mtn*NTCti-X%y2A%)tqgG-Q>K?*f^N0%V2 zjTD-mom_&nB~ln1-q|HcJ13=GT!OSWQs{>4>Jp^=kwVGb%_T@vk-}lw-6cpzBZcx* zb_vo+NFfXMa0$`{NFfXMbP3W`Na3*TRYk;3KV-Y!A<5Ghp7eO!X{ z1yab&eO-d|EmBDMel9`!6DefD{w_gk*wz-I_#WUAq*;(c!Vh!_QcF^r7Ji4g7eEkemW!6it&kwQf{(IrTOkV2t5$t6fb zkwO_i*(FFLkwVAs6qg_^h7?NXsV+fU87bt`X)ZyUn3PU;3DPD=Aq&oM3DP!5q0pV_ z5~MOxxY{_&B}hjig?u{OB}gYAg*J1#OOQ@WO6Rx)X*yD93(j>3(iKRdsGR2#q&t$* z`7S|v0x3jyflH8HK?>#RLYE-DhZM^AMJ_@50x6t1FLnviZ%AS9E^!G`*X?Z)4$Gx3 zLF$#1E^`S|3sNX5m%9XMC{ifnSGWXeEK*4Ll`cVA8tLldHNsUcLHaTo-PJBZ`YjpV zH7-Hwx`QpkVY${NNd1sP7F_2Nq#;S^dY2%zA%zCx2A3c$ffSnP8(o657E-t*xydC+ zJ0pd?yV)g3`y|tJi%XD>L<-05R+k{1i4+=)+gyTl15&6`x4Q)CUZilPc!x`nUQUK| zr%RCDPlj}tOOSp>3dQ$smmt;cXp4{q_qYV9A5thP_qqgW2vR5o_qhaVzNB=&OOTdB zsx8)*2V8E>G!1cgiDa>cCtldF}f#Rg47Ku6yK*@g4BX^N-?CTU4nEa zQb_nSEpsN1nGOEuEjLH?h>Tm zlWBUxB}jiFbt#7Qrc03iLNchXEpNF5smso`2*>VimmoDEg@nK35~N;8p&ftMB}nri zg&cp+B}gNa()%t!YC{S+{((!7#vp}!`p_jv^CE@4`^Y6oiy(#C^07;h7DH+-=J>x| zf;0gsoTxu>3DSni=stA`(xynEVg1Y{NYjv-i)s4YB}m60H5H{VT!M6RQu@**NM|91 z^WA@3f^;R)1;yyTatYFkyVxR>%&%R7v^r9AF{E!?g0ulrI7@x&5~Qt@A${i(q+O8= zs;kuZEd# z3Q`!@pWzau&ym6el7C!+RJW@wmMHFBjg^az`AtB?$;bG+UqNH-wa9#l&WE8_;IJp@fk-}-Cn@f-$ zMG9>}vrCYkN9s}ByY4PQdJD;*ItzNZ1nC2$a02S-5~PojLc=g^JwpO8Xk_HhZ)Z%CoG^mPeR&2FiX`nd$DJ}LEg2~yXjG{7ZDJ&;0XwzveT7gEpS zVHxNWq&bibs*l|ummm#A3i&jvOOQq+rP*A9)QS``b9R>?&5smH<{U0TS{x}9l{sC4 zv|Lge>=L9^kh&MMV2DeQ)=Ns{T*v$;q%Dy`(=(S#koG`2rWoB&mmvLt6k3(JU4rxv zQaAw(a|u%K-E9%}Zn#U3W=9G`0V7<3G#`>dby1neB}j`Pg~L*E3DS~CJ&GZXbP3Wb zNTC$8x&&$6q}1jTq|K4K6{8#F5~LlF!WH#smmp0|hBU?{NJk>gQjBh1mmr;v6e_}4 zmmr;o6tZAGmmpn%6!va@mmuAU6iVg-EXJF$B}l`PAuZ|>q_Idniyu&cxCH4#q|nl>>k_0dkwVk6o=cE^N=oay z1Zf76L3L5tz$Hlad)Xo+d_$KY^+xJd3~3{mAPqt4QT-N^Jx=X<_q+GV3Q2G;^Bqir2)GYPaQEZ_ zlOO_=A&L0UvU7z=5CH~|g!iZP9wtEq*gz7=pOPC)f(Y>ZtK<%oAOfNx3GO|4z$A!( zuYZ+1VG=|@86?5=bY3tCBA^SB;Oh7Xm;@29{MXBQ!z74+Lr4Ptd<8x*2_k^x4R~Pw zQ}TsL5CLM41h?4W2a_NIoLCfPW(LC~h=5*5qW$wdg}@|;fM1Yw=T9jVCP4(?z6B3((P0=& zf(T%MB)FFu4wE1P_#p|daYn!-h=7NX1Xq0{VG=}u0VKh-yC|3h5#RtxaHDxNOo9k_ z2T5>^^Ak*h2=Ip_xbZFqCP4&5LlWF8h=oZI0U3WiQyff!2q^jMJ;lQ$h=4{&f~!vn zFbN`H2$D$tEZ{_#1QD?M*UNo|Ne}^tkOcP%l3)@<0HV|1q-2-`5r79ta1ms z2_m2hlHlG`E=+<5=z=7;0?va;5CJ2Q1Q*%o!z74+IY@#_DipvZh=A3<-cun=f(SVI z>pd00B!~b+XYc^mT8d#3L;xuy!F6IKFbN`n1CroIgi@FU5ugZ3aK%yvlOO_~|5Yl7 zNe}^Ef0Zg=5=1}}B*C?o?=T4>pazoQqUcJP1QE~Y6(qs+1+_2w-xT z0SK<(A?{D98zw;n#6gn4pHdG@f(Yn?B)H4Hh2uO#d z9B~0ENFw6EF!PKpB$Y2B1lp1QGD$uhJAu zf(S5#B)AZA8YV#mSU?ioDD@L2K?KNNe}^kkOViKB!Ni~0SS-< z_nt^$5=1}-B*6`3WH1RLpyaP-B8N#30dVB*2_iuHuM!(ff(Up7NpLTd9VS5p=tB}*XU_qXAOdV5>C2x_#|e`l z0&so616-`a1(P5G=phMic;$vk5CJ@p1UGE(z$AzO8AyWbGkIYWM1U$JCH{Hid@u%bFud0AHG8YPh4){CxG=u;@=!AgX7ouk%J^~7G2-+dY;2-Y}_=A6DTEIrY@(cip z42zY@9Rjrf9V5_;|M?=*c<3`4xenI<{x;O=8bxI6=H`Y45j;Qi13VQ13iu1^w0A#m zByN3n<3yt}o2{A=s% ztYl&3>frn0p9{1q~y>qsrhB| zkssH>zt@zM6%|z`71!5h?bTEk)vBmt$Ujq7G&Fsr@=y(3QOQ8vK=QVMn!19%ti7(D zo-~WOg{_6v8+JzrD-%X7&&zg~frsJFJuX08 zgoKn3;~6e7A_@gLl_uFe22nOU33_o>L1qpnGz?5)QgU)C1RQEY3PLhsN>`GJ*_NL9 zzNU`m#^%1^&VjM!v8l14>$%yPw%OL0v8lmtr+dr4=I8Twj}MMAR~9lZH%<=ge_j1P zDLuG8J=;4@{^0NLl@t&V{yjdzJJa9qQ)o=cY;t7$(B}`IqI^fZN^9~r(yJS)7E?18 zzLe)?pJ!K<)!a0c)Yg@kq}A8dym+82uP3F0`@-U-hP1R6>MIXREmf@tn)><#&W`r- zx*o1fl1k6s7#Z2vNWQRicwzUz!Oiut-8&~^F1eQ$4pQ{&j8y0tsL06pl0-yk1O%Ak z2$x7CntQxe<~zQe*wCC+e{;V!Sapa7e&I1a0X02kkVK6`O4Zbq)zn<@X$ z%lVMr*0!PM;giA9?zqvxp8k#DiP4C**6HD;o1XRa&GgBwot4I|^}Vf~n(N>D2WMx4 zmsghuXY=i0AxQ~A4aq)Uep5|tWg#gkO%szXtv|~XJ$;gWLdzrbtMUs9YckUELQ@N~ zzNCJizbO9xJ-58JzWQ4c^!zBOXvPZ9bfh2a+G{AvN@*!-Dk;6ydZef?rD!axqy6}u zvxTdowX>|v^Otv68K2*M@BUKQ+T6z3nAgJD#DmWm3Gcxzd?ZX&93)&6c_mq7Dk74n zSOm}T$Bo5ab=#!X(&iaX;~X6Jyz4BR@WqcqbRSV``FCG z*wTbk%H*}Ro%ua^MzcYBLZ@cT>nsc?zjUA|s_ex64-wJHVt;_$G6_rt1nEhp? zyzca4X;EoTbw$};Vr5#btO|yuj*@|joV@*m#}6K(T5GClE73Zr%jz0vk-e3E>}>tg z#=zR$%+><1Gh)H8-;`?|v}I|jP? zTi3d}2S)ni#wJo`e>OITH;uQn5A5V_{#u{EUQhe=dwG3lbFq5sWb`u>x#xA68$#|G8=N!)9bQ|V!nJUNrj%j zy12NytfHd6r1Ye&#^9l@;UgJmO?7#Mhmv6br=qT9aN9shM^ar|^Nr3^o#!u&-JCcd zJvX(rGqYw?ec|qIq-Fd5jf1ptUU!1I<5#AHHI_}niMMr5e^Bpju6HzJZe%h zvD-wqiKsa_Em7_W-=)86iYQ3Wf0s@ITZ~nZOUO-(-=12SSB$s0uP1P+ajK=K#dCh( z$5_kE?B>tO-pRhEskynBi<#-&>FtZP`Stwml`p-!8w<lr{oh$Qzqx5WU04eY zP3%lg@(uM5Z}boR)DjVyJQ0)_7aHf~8J`?C9{2fcL0nB%Ue2~xZfaq6TK1=goZ^b= zo6?&7^t8(IlEVsVC3yv98CxAK6?IvuCt&+mfB5Y7yC)iY3Qq_nm>p`FXdH@O1snU?#>H}e_xAGg_2_s{_14wa$?wy?tJTH*gXOZ7xDP?W zN&O!`gf~YAdbd|pluk#7hbI(A#>5Q7#)JKTTETi^lHae?-29Bl-0a-ql9G!0>%ywS zn%c6@<>7TT@(iIi8b62;QmUOQi zEuWhxTH83jbr(0Vb~bW!dM%*tVv3KBPKhl-jgO3rB87xRghPc-NJd~tO02HMcN>wC zjD&&HlATrfE}H-gC#N|F%X1HGVO9w-UT#q?dn9sH1jhc>p7W`$pJ4y*IoLkh+CDly zIF!}sRS&h~to)OfcrFtJoPaB;~{NDvUQDQ|0wkz-Qglak#gB4#8+ zr)OZZy2EV4O3%e-jEsd!D#6SxAmYI*>ImEyzvDzmv31cq-QL&%J^xUDV^_;q;??2J z;mk_Bq(czi-uL1tY|npaw3W%EZ9I$3n*Car?eFk2nvn6OsrR=JcF4Hcs@n{q7s>8|dkojoTdr1EKM#v$>(6 zp3{|;?cW8R^NUC6ox6*RmmNQcD*xC24;}0uTTFa_R%Ft`RGPN?g`_RPl{UaO8*AC7y z<{s}}@zbN?;E=JRQemT`67!G}BIq*VU{euNVcyaZrX?c6;iVTA6|+OawdJ|T!^BKa z4hZuJh`AHdxX}sF2?_K5zy1Gu%RpQ6XnzM({(Iw7Q!U5ib1iejtHWqSsAdIfr=2K)MZhWZDmdmC*Yi53OX6XAP|_e2<>{-2ninSqUgnUh-tmz7ss zgopmF1mA1+k7vED{nM?}?cN8YBR{%+^bN=T9+;kNou5C8SRHEnxw4!8qhN01X6x7T z*B@CU^XvO3SL2t5hc}lO%RlzduY-I$s)M|;d;(%Zo60|YtVsD36%#a97!s8wNB^rZ;g3>6ee70ld?j4atyUYk1_8JlPtSvoqoiQBt~8Mqjk27`rGZ6Ei9+7as>72P1=kE1m$4I1`_^2m=!; zG68D;WK&z$S#wkKcxP|_@XXO%=iu1Xui=iyfzcl^?QK87_%{cPfAYIVo3=Nze*N0m zJ=t0tKiXNnTwJeM+gtPY3C{Qb{MpMpp)okn+pBeadct?MHYy^?YoKSWJ|QAKJEtzS z&MPl3E$eI9)pA~0NnTMyX?a#@R#koF+4+U3oQkCCLooiglX?WkzuE{?YTDY$Po5d* zDru^dD@a;d+Pb(|y)(9Rv3hA@XJKad+FIMg@tvx>JwkGFY*Ommx3CD1Zc|a2JvXPMRNfaS{`4 zo@?s((Kgo6<~=;p3dZ}LGrgljZH)_`$A8WYv`$Y7IvdYhN3}v+C5;H8DNv9ZSsHzyYtM=L#xH}B2gn%kq$-l4%pqE$sEL?^*U z!Xnfq!^Offq$Q$8C#0Z2(IgRn!NA6OU&xl7{jM=R>m4Quel{A>`}|zI5(r{~eBAdg zyIOvB&-KnV_qC2TkB;wkO#K`fnD{*OW9-MkkJ;(zv)qOAf~BRMjn(Cm^0%*wC%`SaQ3 zYu}10zQru0xA?SYn*p`x<2f;yN5AXYO}Cx4=4 zW#(q#!fESaK@XPv3uPyFcWYs={}X%v-i-UzOE>e|qUg8KsZfz{WH1m&6|k`I^k@*t zaWDuF322dT-6p#u#KHyuOw@Pi1@D1FpEWus6Str!8z&(z3qL)HxCnQ1YyU-eck5hx z<4D+SSI5@$*v!`S^l1P1K+D2-^Us5=#pRsy`Mj&&zm}Gd4!^b^99{kf!@twx^TorX zrRr^ee{lZi9oXLE9~@Gi92=1mQ;}a=5<4?E*ElyiGCsDkzO}jiYjV}42Cv~ZhyWGId6?U} zzI3&+qH}uZV9LnG<|b(C&=_Zea4ha=@<9ji232FCt_7b#+s5DPhP;jtFFwn7aX=!P7u&}9VDG(9J zP;OD-3b5XNDQ0({S=fY&i{UN@9hXA>?uPpCM zOz;f(*yH8@$v5Ovac)jwMO|f4ebLv{>hyxlitqJ7!6810-f=!jAEF|?qY^(Q#zn=& zC!~Z0dHW;>`hSWs2IsdQ$(HLLFE=x@AmAd#BEaiG$Ri+N?SL#SA=>)0?R@-4Q^(j~=MTTt z_QB!ana%d0!HKq}$)T9F(Kayt?OI&jSUMh9IrzPJ+`fKzc6D{VyO9$A^ZMxcv^@EP zZ}3R}!dgMFx97+9tiVrkQIo~-38At6U4vr@{-=#KS(#}IX;mfLg{7&b`PmIeg+)a* zrQcGoO6zOV>nb0iK9ZMuAo&1C@{x-ALq$oFCu&McgnH_-7EhmPkjrSkV|BGRvN7|p zwY73JHD$T`)Xw^atAnMO+ncv0uT1&G&?%%e9^S_U7cEIpkQA`d32sy28c@>^KS6&? zOstJgAVkM}myw^DnS_pqiSaHI;KIhuF5tn*F32S;D8a+^rlqU3C$y<~@r{ z5*mX-;wvIP`IH0%wZ=uJOhzQd#6|d*_=HD9_!kz&)uiU-)EDQK#1xk1eFEeE-0!&= z2Om-!N=mY?$5Mo}1WP zzjl86!oft)@vSu%w!v*Mp}E^G7#(`>U(zzMq3X z$A@@LG>3R5r+a+}NKUGVD@+QDO8Fd}knA59_%$;tH}9YRKdUe&zoMkRHletAlFGm{5z zjXkV{Ubwz_=WhP?wbVN^H)CWJG(}uwcDajk!>r2`y>pzlrQj^m#)z>n1R<*EJwtZ)zZDnHnTJ?pE z%WHcV2}{R!Z{4{~?9A*RE3+_(-Q(opp`&ME1n%%NiSu)@^3b#Ma*1+r^Dr>tqajiw z6Hp_P6VMW(qkuiaZ45HZTX>iVL?k4r7$_tg^BX%`TTAnM$NOt*%M0h5=NJ1or)T@~ zCr3-aFD@2Gr#rh^`?@>EhWmf?wT%t7c27=?&Q4FvG`CDlPPTVdf6GlP%+4wJUQ||) zn^sYtU0+_5Taw=JrM9N*YxehQ|3v=;|HKfl(9eNk@c|z_ed9mId`bxY5b-(5E8Zu` z%jKP|nT3n7qmjc)Yf~_-|H|r(v!lDcgRP6Z-Me=V)(@U4NGt1W$SA9+N-9Y|Q!{+> z?3u2lrlypFp-fwuvK%WDJrgsFiZB;g&P;%)0PlSPAs&G3KDQ9J0Iz_MFewrm86qMk z5g8#V3KALt3Mwf!H4PCmF(Mh+ZBilv^p(|(i*Lk69WcLnG1oIXyVum& z^kX7=WovzDePgrfbmwFvZ|(48ey676^y={H{AB2G?d0s_cW8G(a9Cn|ihp=fl=sJw z@Yw0>PeH+T5y`{xDPwU7;h(>Ltp)SH!Fg$UmE{HBKb7az9Xk${FhDlDfaMvb8XN@zBX0oc)=+RdBU*a1rOb zZ{X%`^2_RE?LIm;i%C6kva8VT;4U z!^X&JhAzr4LBYfOp6~uE2?<^&k2lLdrds-&gLj*I+m~mDo4RLbf5sn=^>@xRb*{9G z4g6gGl|6NGdAh#)8;t+IZEt-aIlinftL>a$Jt`hLo|Kp(;aC2 z2M<7E;$vriMZ?bvrat+=_>Zrpqc`wsu6c64tFN)6rO9`9{d{0xc4&4yYN=_mIeLDw zb75_FVRt>P_2}r}XyJHb|M>Lxy z9vI~}+&efHG?JRpkdv{Ko>P-mSh{er7WpMRySSn#7|j2b)K=z|WbR_ADoRS@>qsN1 z$*HS9(2~5RuJTNS@R5$Z!c#*vS;=QsMy?O^l<6PJC`f5aGbyM((^b{yexf3)A+IU^ zOhJ`j`aV0`T^2fKWjg-5ikzHxL^T3+QO#cx zBhV7ikl%VsOHKBkn34coD)U`h+TPw-|FC{=wz#$wbbNJlv3DANczpc(^d$aj`B!Ii zU*<^PY{&RuVdHRf(?CZVI47Fw?5`V`7$5%8R-IFvo>x=XoRgECQC8NGp7pIhKX>r^ z_p0xiX|wfdB}FOmYu?E*fpOu#LgE8HM8qC_ij7SC7`p#CFgn`X_rllwx$XP+2<|2} z=5JnMzHzYdu(Kw!b#ZdGFs3zec;#TE!|+T`Tj{AGyOxHWjJg)fqX*A~1s*)rd8%db zL_%FkL5`bQl$rg$G`#@BJqAJLd!iB&q8yJU`1$yF?mXq=5XZ%&FvmgxQ^dqCF~M;H z9qTm#4h6++TsKMt46sXZSvy$W`E}#Hw7tHzbR4jGbA5Tf5wZLG;An3?d1e1*zhgRU zsY5SCTDs|!16DyZyX#*Ey&Do&n~Fztx7E|$otY&l$-j!xNNxK zdu4rY$;9`vP~XUqg%&|uFX&yP`^n3m`m@N$oS9*RCiZfH%m(-;}_LNmX;CQEZAB7SuePvQU zP0>`KuV=!C_@&U`Apg*h-djFlDJe0Li3iD1v9ZYsQ5P{LwlB?1ULadKIat_Pn_`KgBz$p10g zI@>n*ZLY7YX|AiZqiMXOX{fTPt)R5BuBo~qC%vL{cx*hQD5vmSWl2TdOk;CnNqv2F z+2+Tqvk=eEN&XSOkuisRo?(Hpfr&R8+p&J$zM+fDQO?%Xl$LI`rpD$7q+~|MPMFvp z?`<62UcDejMs#pAcLw7_W-$r2yN{F}KHvoNBig!JlHzP^Vj{9nH1*YW)nw@DIrN_~ z(6KQ{vM@8#%PBtK0@t^m35baas>vvbiQ*w)TDf|lQX)IqW0PUNb$Uq%CW{E)zcezx zMSwrGsIQlQ`I#`?zU&Pv$E%5hLoz{$l$Xu#$1^4Y=4>0wL%bWUyc9JoHzG}t-X zl#!F$-8nZlRF|6B+|-x$HKVFNb$Yh8CVOzWw4$)=duvB`Ugh`fy3(QQ`ts7k)cT8y z)6c%US6BNnK?^4*Nh?W_(IG*>yW9Srzf)p;4wr9Hxw+uEy>oX!CL$ywcX)ft($e@g z77nh35rK*MOA-VTQKmaG^3t-bS{irMr4>{~SeTfVRqiWlKi1)9WMtr%zITU_QC?VF zn(q!fi-I7x?t=#$61-gccNppTcs2-552`F!X^cS z07`^68)sMBTVYGfJM+5->+@S1*Owsq0 zs!O{&2fNGbs#``!W`?E)n=4Z@D!2{u*+X8LjRW1h^SO3$S8Ous6<2-1PDmCu-}0zL|C`T2vJFC zZy6;d#9dx3ES;^z9PO{K|DHeHIlkJwIyg8!o4+|cJ>Q#a>}qT5o}2}{@`=phobJ*7 znV#N;f|io@y1B{b!N#iYnWDa$ru6*p8D-_|MIDu86?I^|UsB&toL*4=Whx~uI3g%< z37ikbc?YaV#3Xx1C+vL;i~s25dF|~J9&M=eOz)A5oQevv*+V5|Iqh4j%H+4->uYFe zKLzt2_PXX4R<4g+Jl@%x*jQRvF-W~}dhKi@VQqJx7tH^gS@5$_Vxr@6P~IjZM8w5M z!;%vxAt9y3eTI#w|5%h9os^1Gh~0{ug$_rMi;tI)m7Rglm0Ey@SB%r~F1^QF8veW8 zt-Vv-VMiT3-T}wWKl&R-<|0nJdpi1u=OzaxVt-Gqf~$W8XE*6--MhOBS^b-bmz6Vn z2fr`phgKFBSB}oSKl*ik^Yrxcj%bYi)Y+1o5mB3bL>EQd}3U5<;S|}>N+t0 zmsYUwAsbx($~i5}-@6R0srz1(S(#ac^-@7j`k{f1_G3*AF#jQ|N%dY!T|@s7k(>-I znTZAILo>7IFWpp~9h@AEtnHq^5HWOmyxQB_?zPy_(K+3DF)=VZF)}<8el$5fF>;!Feg37e?brU|&hpXX*Pk~>)2F|| z{NKS<(dgO5>0w-VazbN&1GxV06%qK!tF=Bl`cq0s;m1Tz@4+J9Pl-8O_0{vcK52Ph z=RfA;q^~5Gm6bQ-hkdL0lD-$7QC_2nZJ>*QZ?2*){ZRIaG|GF!Codjqt30B4Ed_@9 z} zL860?tM*Ksn1+y)mh9FOJO+lloMM>y{8naU>^!V2?vxz&?{kqdFbKV-pY#pb z90AwQad^~3d>pvt!=2oRf1I z^)`YdNQvY59ZT`p4SI*2zuH&*S}rtKIR{ z#p{ZNwVJMmq(oo86yG0(fl>Zx5wV}9Lq878@sg(y?JeJY2*4vNZ;X={d*%d6K4~53pXWEbZP`_6)HkiNjh{)R7B!iWKTs1 z2ndN0d5~$5Y0&6iP}~!}!(_|HWoJyv#V)|?dYe^(TY?r4VGz12@fsBz)O$lG=e$46 zPqno6`|ON%_Kwe8^^LSmHBZK@O*by@X5U<;=5;P@Z|v`6^`9IaT}}TwUO!y8C~7)e zJm2d|N^I}T^7i&^&J7LlY)SnT8yy#17aHxC;2l4fmFW4kaJ`{!;g|3C{H!l!1sS>L zNnrk`I6LJ__1DUbhC^RD19?@vr-sN#R#IvTic(sJRL0L9yFJvD*OhrnW}zo(_L9lN z?GBryg{zeb*#AB8uyoS1d29LF*p*A_or#Ns5)GBY!#l{dq-f|E=$Nt`7$juW_i;%v zsSqgC?ol98al8;0F?xx_#BX|wfsyqttN6eD9|5>2M1tY|oAZ{L_O7t0*13IhNZ>W^3wc*@A>Dk#kmbh1!bAl z=~c)6HD6>OVHoNo;y63}+y6U*i@@3{nlvWQL0Xq#bnJ6u|{w;Bffz1;44n4re zWh%g9X@SSUA|h~~UF1EApxAA327sFfs{bo9&CTuJV?zP{Cqr{{gI!}OC;c-sgEzyC z(Yw2w%V1cL*SWp>YyL26|G2E?kNz(sB)P9VFgWx>sPDh+e=;C=XeQMs{)6v$iGOZJ zu77Ujuh^`#yf4}5%U^#MM_89VW}kJ;Jn>l$4TDvy)XrcbC-DeyXiY z>#S~Is3t=ur9j0Z1#WgRwsN4F^7af4Z;pxVXfN>j z2yO-qN%;9SHtDO6&*vn+v@M@6wFSRwinr&3vc6~JHe}bG`B#?&cpXI7SJ#whVd?9k zQ6~O9&lasTCiT&%Fy`|=svO!;epMcN(3CTUpIbmQG2%5?X2~13Y z^?#$am8%0En*+WGsWn`%Qom35# z^q>6q`K2x0?My5k%mE!!D>beE@A&`ht=Vf7JSv=r0+`6s%7Q4!IEdJ{)!A7LM2QJ+ z6CtS~py)n9=ima^Ip}PO?(uRElb9j$3-SrEd9ZN2fAvTGKR)<({EzGJjrhC`F8+Y+ ze`j*#X7A@!VaMXm?!rOl-brqC=gHyC-qGb{>CeNZAMND@K0dx-Nj-@Xy^W<&(Xqja zpC)pHlEUM%zWf{i{4@Ti&)3%+o{5M*N!d{51HN0>)Y_%(Vr zxc{kXYNGYSV%u|_KsPlogv8jQxo$P_Fh3>_jwJ#?}IbYlM_m3~P z&ud%O_OE~3TrB?nU;I<(8x-Oj>D^uzp7^N&oZtQo@|qba^7irc&&`6y|A4fD!qif) z?CjKk#{bWCrKR7iiYiaStK}6G^{gZ%6_K83;G@0PP*Hq9;h_R9f4+J4?8!gv|Dl_! z@dI#rNdMN6iBaC^)k_EKw-PFj7H+S#-nrWU)Bf)vAYn-};o~Fz+x~?K2q@K2kqx!P zNKsk;SO1yVP&nD?EO-Q7yOP~!_COQj1qVT%f7|~?V`Jxs(cv(^!`|+hsgALpsn2IK zb3X@Keos&C{1gApE$r(vYac19sZXu@zU!NwnOV8u>*cu;UsjQySKp8mQB&3M{p8d9Eo>YC6M1bSqL{WHRyzfv^ zMfq4+Vq$$&&bR8IkWY)>3Nq3gmcP|rq}1k>)RpcxRHcD)%A=<;ih9b=ENxtni3!n6 zo;=ogBt_|@rlX@o{L0CZ+)3BW!^P6p^?`~mJumk?={NSTUK$H)|MUF%&d%<9T5OyT zSP&5@Xps>}?lMZqYANZ7VPKPCps0)7CMCw#6u`lU^1nh7^Z=i+9l;%dTU>fOnZ%KS-tNiC$i|6*mZA2mt(D!Jna#z`w9TEB zrJJvtmo)=mYP1$w1uH(kURwwY>qzhmO%8}?PR~jMyMd(X+|S{0KP$cc;(f!$>kMV#C3#gDaQ#;uT>nB<)Of0_r{ZE| z{FdOo2e>k1_R`jY!^oOl*~u8plv&?>>-fUf)%Cuei=)Zw=TF_-5%CZe!IffFY+MwS z+tB(iHwk?SyZt@=jl+o_gMw1ddfKO2I%2M;26qp#yTF`I-}d(5!P@fgjFO(K%cJxC zvzqqNq1o>l8B^U}{vSU^_lEd3g7g0XaQ{bA(rj?_XU`b#hN!3r|IBY-{C9P>?Uh!V z1MYuFZAg#*>~~XMT2Ya`S50j94W2SQIqaoO*N`a#Hfz z;3fzTE@=NJ1)(V$xa;@M9Tz4pAvO_Cyt^XwbZ>9*GdA`83|s>fJ5%2M?S6;DJ)IqK z;4a^eiLt2W$??{~w$`?RoS6%7|I60y_Ey&S^6H`EtBbY%f$7q*<;BI--|^kKh2LsI zd_H{$Z3+wY^!zX#n=+f51n!1S27isjr&reIecNrwNH5MP@r;V~Jl|a3xUMLv`kwo{ zzUag&8B0M*S6Ar)u9~uxhK#0(gOxi41|B}OvyA*xxkuz~dUkg9%&LkH*rjaj!1<4b zgVt+s|F6y)2XkI6GZPbYdrDiU!~=a0D`BHA+fNI#xyo zD;iO8CPqO#1X5&F5*KSrD|Z1NVL?{#`wu5Xw2$pgQ&U|XA%opb!{Gk^)ytdBp5C6x z!P%$}UVi@3m&?1`dj&sMmbTK{4#BZ+exqi3ZmOrcwq$Kmt_% z+p|7KgZrP7f(ob8hFhE3#*2L#(rSx#{L*r=YUYE%_1~5Fq|awb;0W-gq%b!Try&)~>cM-P!K*@B*?$G`M(VEO@BMw6a*#dN_nsh8RRh8pxF7sDx{}7rM9qH*g zRTlAScDz0;HpyqAJkBq1Grz8`rY@&`(bLcOpYcB+yW(41X>rAYcWPDoVU#2G3wcRB z0~IN43oyTp@>WSxh4Ph_x+c*ZOTsrgx>C;8FWeN3jI8J$+uJ!ZO6!a0I5}C{y1AS4 z3dz56^sq<4m-=`7<0Zr-7Q`XLrNK5}!y!V`5Mb8gqoNWQW@ZtgzI%u79*;4pAS<^E z8MyxKaSPRvKth7R|FG9#mCxu zIx04{v>uNXw4V?@SJ0DUQGLv;YGeZD|4iR#XfnuK+P!}-u43Z|)_+9-O;=Yi|ATew z|MFj4%z6S8;4U!g+ZuudsM?IEcO@+G$zI~qu?g}3cX`SA`8l0&a6M=_83p+rD8+@) zrdoamUMvUi42-q3_^tPLo^|%lOs5=-%tpn58IGjI#hs0-f{nAYlf9Mw%$6TblLvbz zHw`n{X{D1l`}@DZ{O9CkUqxVWcz9!Z^atOVut2ZLqVNDH{%sj6h={JNTu-X~mS6w9 zxN0E)+y$Ic@U7xHDW|jojDJ_w4uc;l>FVOS8a$AB{6xbL-CRl2Q2CYNBYh1b3u#Sl zSt*5Atn%hA)=%Z>v>nYXUOPCMifX;Jb^-TCiCevPdd?>)D=^9{KA z+0{EcJ~-*M-Z9XaaC&{U6|ox?x%OjZyrrrAAh&B3od5QAz2 zJX<+By}AJNADuO|Jvkv^!Jg4+;UOu3Nx@NbA47*S)BlbCK7OfAO|QyYswi2FjQuzN z7hd_bx;XtfHS5U38N*geMpEUmgQA+2vJ@(q|D}Y*|CA*5&hIQ-Ih@VR?92g&msX}W zuipzfIM`S+^2ojMaCA1;!A8Zz!=}*!$3IL`d^8Lkgj+ZSgoNrE&+d}a5>cShbKYgU zcOQwJ9{m1?_q_!j1FxVnJGTcH53is-I9M8~%+PII&-{L;Urg+!HBt+*IR3GeA<)mlkl`Q0@6@3MpL*;cv;Z<$z*S+$} z%q(y{6CKa>iM|3d4WhlYlGGE$H#(0E^mHUK2^2J*KGt<+HFn{!GxD%7F>}6WZe;A? zA!O?y;qe~)22w)c=^JN!qJ!I`P? zrjHSGlQT_Ao6GZuo9p>|$4iS#hbK$BCx`Q6$GaO>3me7D`z!Ndai2TO{Q?FWCL2q= zqhf+nLcs0Mb8(;jA_ro9;^IehzU8LoE-WskoXtn2msaF8}FK-}+LvCfs#VTw5^2Kx0=k#Vqj^+%emapFk z+q!_eB#gx+gp@e=@1bDgLf?O(A(0@+Ba&f}>tR#lfZumXT7vg+?Le_V}rurmlvL##2dSXKNdqXLinJw(4(;EMI}+-ylo z-N7W#D?u}R3JhHITlbLgaPcs~`5!7d4IU{q0roReVuZ(NC=|DdtR=XGS&eNlM0oGg z@z4R!-QGL1gZtk}#UzCJ1vzL$ds=>VOm*FK3=VYmb*ztoQQ+Xn^j2%{_<8d}%k1RH z>3;Xh!sQ0^{pS!^{=bfnwq|ZtcP9@v=T4gT_pWn76OzLGKIC}E`bK_@j17)X4*FR9 z&;GZnl<>H|(a?gLZ_tL<+`NK|)wP?I@|@b*yqku??S45St1q_uFAl^?0d zN~5DFJuuW)zWw;&Q*9{%Z9_es$9Ak1uGZG>?8YW`CZ;bejod|@Ubwi}i5b0hdHu%J z!9fY-7A`goxc?IgnG~N85t*2rg!+~t4JIM@@f$G}1p&tkW-)d#W;TI)491MS;QEIs z+dCmJ{&AELrlWJBzdzd?Fg4rN-vaIfZ1wAG?U+mSji2c5iE3^eocl4+JaC$^u=Z=hl79uPn+f&&l;JPcJX2PE}S>wN%xU(^GgwM1w{} ziK?WirmJVA|42_=UH_l`UkbJ_T~tjy%*`D&UK-gr=)AUebG3c)%E9CLn^$@cuWmD= zQ(u+uu=o$GL6C0i~H}rF6bm+(I)c-@*TR>IOyF)0ClJ4%52I-at=?>`-M7lv*K*4t(dHk<;t@rzIA;X-UGsDc@ z*L_{Td#dzkZ*_ftH$QK3YxQz>zoTXT=k@K?*=_CW<@(nBuU~0>{R0)V)ujswTFQY8omE8V0)ZB64b?YLpW1JxqcAX)mkqX6URY>g?>|^&aZ~ z)st3m2iYhjQWyk?AjY#-s4(ytNRLEF9}#1bVi6O55#nJ%^PjwI%CwB z-T)eB7guaXUXTi6x96l{WPCXPe-DU^IvgM90s2SA;_%ql!SN57S$B&M{-@oe!;S01 zpXb2lHn{)m&neLV&X2Z1{;^>bmNd!k7pA)`Tv9^ngN zU_HWv$HKwIN0yUUV1PWu0ghl2Vg^epdR`s|T53icTeH^;Y`_FhX2;D($IDN}^Tvaf zdt_kp>%g-AQ0Lrq?@ai_{L=St!?8;%XaD^FrN8%XD-Vx0*0#Za8wJ~!msh{!0Z6Y9kt6*!XU}@;6${D#? zOX}+znCZKk+n5QsSlC*+s##h(@xC_%!ic*mD+w~O1hBup!$QENMnr!sf{v^9j2Mdu z0|)ru;P9VdQN1wZBglH35~1?3Q6=&iKF?7?t6dAG^R0^yg30?dj>+^(i#}-gteRmR%MM@^8_} z;W07&(J6h2iIIz$AOV^Z8<-dm^q+#tvi72!yrZ(})x_fR($b3h>xS}Hp#K$>oyrp(WeG`9TVY*!U2PEp1ue4TOhCYn3(HANj7q^sPy3XW zjEtUMpM`>zk^d#VJ1rX%hXW54l?x>U$6#-7*uY3+tbgR}WOx5W=dXpo<3GTESe{K@ zT7r zSQ#4w>VMhU9SJjo`@t10?UiN4QFVn?Vf8sV#lNbXid%lywVwwRx7N2d8_L2Vc?;+Q z;YwE4RtQ|NU9=_Nf$`saA!S_?H8Cx315?virq-Sy|6=n()YQb;#>HCIlacqWmm#Qs zdU`&`5Lc3+KzfD+iwH+U0E0q{i-L)akAtH?jD?7!`s9fPArmD?z`(t*)x)A;VPJa0 z!iLG^qyL_i?KuTIBNKjq@6zb-pPrSGd2sy?8SMr6&-uyhyOqi1zVF@l6KSPOJGVQ( zw$=}aH*;HyhF14>Z|;9BpPa2-U3A@S-mL`$1`h>Cr-yb&#dOs}6AdXfnN#1(BzV}+{F?>Ei`V3~v=pHJN%`_Q9lM8#n>#-f$9qSvY@Hu`sdG++dq51e%~)#pZ=PO zjv361hztyf@b!!8FNlap%*u#LiH-mCAt`$@J8&khsxY^_G`FNBGOwZ_sJJ2L|AYTw zEGRA|E=L3r7nhNf)>M@j($st^1I7bNBEkv+K=P8*e{arCVPRwDU}J3VsQ>;wpA{Gn zxELDP8MwLQAfq6|Jb@*mLqkBPVrFMzWuvC02H9pl`Zs(WoJ?%bU=a}!5J-v8u#jL8 zQHb#$;XlF0hJbwBf1l6g`r%2}*T}w}**-tua_>z4GO;j`Jv}`S_J8+E_eAOL&d%!o zkDG(T2PZANCszmO*X?T?muDadTptz~91;Ic1gYrKK_LRdvArTvu2WDWdaOSW`<4OTc`) z@AH%W>$bW-8)s+RT?rXcIpP1i{)dH!W-UY|XT^WZ%uJe0N%Z~DR=HdHzrX*3&9?f= z>(Yj50D(Bq&#n8^UZ;p6B`P2&A@kq)Pgzq!T}e|)T^3tR7fW7208hl-%F0XA`Mv2o zTXIViI}-;e|G>-9(aXir*vHL-Taxm>@jpC(}V!d<*^^woC|FgRH`{Md~J1{!CCB+|9|6|KT zlM+e;Q?gnjvg1+{KP1+F2*^nCD{KxbZYwBhUXLrRDgD?USKQJD_(#D1^|`RUF~7CQ zO-5Y{#^g7V6`>WU!$Z2I2P3e5kk$sBx~?DSk&IW$~6 zd|a3A$4ix^!&M@F6k{$c#f$NQX;2XBv*WEFXG{Gjd(isl;l%HQtM#$&t;^GZpqOU==+ua)xTr7x=6|(WL8-n~ zby8{~p{b^p)#hJOVsH~W_AexH2popx5 z5WKQ7p_HbM9ErS^j;^ATfR?fGOK(%)|FUzju?6}MmAQknm4mmLgB}Yb$bad1!J&#u zFd!nKBMA|qe+%wg+31`ZoapZC9bXyk9RE5!HvesNVQFf9aqIf%+s^vl-p1|u?)v7z z!NvWrqn{^dGgnu;XZKft|B@G({&)YE2KtAE#RevT`A=g;Xks=X!KB9fekiJp|Cm!6 zlwVL@S5W_{qV!X7bwh1KQ(IY1{-?r%_7-6?F)c*2c@i(h7`xZC}_~+dFVuntOUVdwRM!F}b<8+evafef*SIngkQ|5gZ;a zmmx}^oqdV>atdzf_|J#sfq&%g;TgU#u)I7v z6g50NJv%h}EogCOY<@U9KJ$ELzGr#@@GmyEOSZRx3VeE#yL!BTxOaYcSKTo%vwd~9 z-f#i@KiN}58wOwOaG3xG1Nj z7^?pif9h!1KTX}b0R9IrQ4JXZGzEFtw?ay)0y@Oz@rc{>>;jwY?p2WoaTO6i@s-rqXB-M_!z+rAn4adEQ0yFUE;$NtUncF*a?&)d_rn6!-C zkPoT;vFW+Neo21*88LCmv5iU5!5{oX^P&S|Bb$o~(m&=FCzmG{m*vG5G*q_V)Rk71 zoYmBy))dx!0{kB&4KYyv!;$;Te{hvidZ#KYrv&s5cW*RxHAXj28c{JeJzGvk2OfO` zLoP+R=Z+3;?mRl${I)jMR{Zdf5Mc;m-$7t;;6w>v2yk$patS}@#zi2-!qKFHA*OuA zg-6dt1<+_u9jQp|5pf9~!`nXM2ljYAD&*%()C1l9gWczYgERf3<8#AZBjY=_qksjq z(AD{S;>%S3-pbA1(azezx8wDToxRJwvW4S=lat#Y$17)h7n>u?zd`aTBc-k_G&VdY zXfQA(87x;8wP!}B5B?D6OvO$ZLrFR8~=OU-iH1Uos{b*J%j7 zlf|%CmKA@i;;tqmudE@hhaqnX@95@fWX`Q>X>4j?;I89p>}Y9YVdNs_|-FMqDKUafJ*X|43!uGo0l06`2X-oX|SI?CMJO)(Pe=}d`kI>n+x_8>q|ybv{xVk zN=Ac7Le9-YL&MF(hs*JtmwY;Cdrnn6)kaCX;BzE zQ59)TWob!FQfCQl9g(+h2~^~0-%6Pn`dIrI>RTDfnYaM|qm>1piXR(b|H zFMf95|DkZDCi}WHumr9Djer2;RLBh2fZ65Yuj85h!<{q9+1JaP$KwF?aCCFEy*a;g zaJo9X_xtzZ;mpO_&Yz7ReW#n*(HWVa|Cj&wPf1FSOfHSeOiS@?N(~Cg2&qe}XlgC( zXpYaT%>nv1$Ul8K5Ks7Mg~D0_gb8*4$R!$Iwc`$I8~?y}pw=@W0p*aw9zaJ<1{r+>*zM*HX`H!C+MIR2Nv z4sZ5O&I8N$#kawQnVE0j`!8m?2EO-v*&JKg-Q2wWxqiHUJic~%dB3-F`E&OAc= z_3Yy0;@4?^LU4Gne_N8TZ)9XwL~vr?hs5y3m8{s9l=#HZx%kKr%^j_oZI#94IaP&g zMI|{~WxxXo>R***pKCw2{H!mMMbQHKx309TC_0R#sFJdxvM!OTG?f3Lpr)WGuSx4; zN@MEjV`gKlS*Bnr=`e? ziGT>zf5^FPXrA+v(NY2kfDsd`Cldn$7b7&kQf@()jc$JXWNGy(r( zn>vb&sMb3X2~klQ7zJ@SX&G^CWi?W)N7xp+!uYz%Lbh}!CLXW74D9X94Q(x<^hUf46 zpV@?#ij|g`jnCT^?d2O@NzkJJG{5Ya$fO{kn#L~=j>Jbr z&V@!~#fN1ECIu`cW~6R)RJ7)|t>qOQ*VpD(Ro9djuV0;30uj8Z<6~XnZG(s?qM(MR zmJT5*hAJ+Sj-;f5GJ&?7Jhq62l8T}bvAW@VYYP(_x91kF*4CURPF5mr?#>QYkHTF^QyP1%C0LKT7EY-*4GrYS zETOLayQjk1qL`w(LRxs@1}0{DR^|*+)+RPDEG+eXSSdZN$jN;47$m$6+1;IRq)-qb zFi7b5Lb&LncrbAInom%$a6$bCjt~Sw(Vp;I(s0w+^1Xac&8g4)(unO97cGM~-76k8 zE#c zZttz;v^I_WI^F~1JK%qv+u4o@4i8G19j%Ip3JeAQ@09Fp5O?_y`VF`NCsRTK)AB3J z_gZoe1M({iLH#2+=W}goTYFh#+s9Tg{%L5gMi!S4Ll+cMhZPio5t3Ar7qXSpdZr?- zri&w{_Er~H)Y8P7M$bmyjKaXm(a=QCiwk6atu1ZEIkoM*9NAqxND)yb&>*lxkC4%^ zwZv%Pa0vKNu&{ZF@L}*^;Z;cxEU0*?C~Tke8@ym(V&-Ecqj>E~!wjrHHdwH>T9){T zi4jM=qr-zk0|UN;BmF&{=^>Ldi`iekOh!*HOhwIq-TAe*^K)Zk|ERsKXQugNb^S-< z>HgWt{oVcQ$@TH|ZB$@uWO_zWKu~OjZ)k9UZ|H1&YDz*?s&5cD{#%khe9USs&-~yU zT~Se4P*?}XztLrF_mMGiXVo1YO|^}=Aph$jrKu(i60KHZpcDl7A5OX|ayF`hAm2yi zu4HH`Y+`O=APw|SBT)Y`@dV^cJAOL{XAkeU595DgN(>k@WJD|(dZ7QZks^V7pFGo3 zVp1|pc(_MTI9?<1Z~+1e8|`y4=2vud9(0_v3@_ijW`h72BA{R`ZVwH7865f^dE7NP ze9<*Ox-y({zA`(xcE7o{HPzqq<7RnvV}IlNYGQ15uIT6L?$O=B=Yz|WpTD=ZS{iS* zo1;Ot&o3gY%0JrI&p&yjF+DD+B0V}H#c!o8B`ft~NOo#^S$j)yb!=2p@=jw;aYcJs zR13%fwKwKvG_-trC!>LAC?g_hFRGv_q>OB)si0)xXlZ4wEhuc@X=sG!VdU{%$Hv%4 z(8|ijN!Zfd*_=kh-qF#POu*dB$<56~5)tOf6IobP3Jveo4 zd}MZNd3$Acx^H3m$4%GW!TQ+o)&7qk1FIW9|Lh)I9WI{yzMMF{yIjA%$O=rK9jgb< z0FaXtv9PkR)4yin<;8l=!be6uGCBtMC*$4Y>pk5=Q^S+TQ?tw0W8Ws`PriSjSeahF zKi(Q%zXd6n(;jfaJJ?;kxVSo;z5zj)#r0X>fcJ}zY>!Aut&EC^Nhk;oiOvj5iHuDJ z^rsL0wP_g{A!!{og=MLQ&85Co6(#j4HTjM0(T%Oe^>M%e(Au2RA`C}$&|ZeVEcZ3Xtfxt$leuB)Aqy*>>b z+|x%eI{c5&5aC#1A47;R2vF#sVLu_DMuNd%eu4$h@QRC$8=0Ts749p@s~5B^T-;pj zIGl9s{7+fPm^mPe;|I&%mU=qZx+kFgyN%h|(c8)C=`W{KoqgXIzMgMw{r>V}eQWL4 z*xKPn@7CopxCjFL$zIp({zX6Ve`FQ;g+%0hfa;&|HPNwA%?X+5N$m+ig&^D5YbQ^kpf75=txOE zvsafC#4%735|S5lH}rb%&H9|e$i&##nZ`xW*7m)nHIt8n508P9p`pDg5wLubAizGx z!bFF|!hJ!_smZ{MkMbBzTuYY#2OAT?h=!JyTK_eL6&sz+ONuva&aZj7*qGih@v_i( zQ&LiUPWAUD1OC(E$msZo>CwI3!HI8U5xRA7vguLLiFpzQp z_210=#^U&r+S1&Fs`gJ6Wp!0`pX!qu^Xr3kgf+w^BvFOs9|8Zns+5YZuB)|;tD?M$ z%3C1)8z`B0TYFlXdjs;buCuw7zNLh%yQKxcjhnuykD(*Gi5)5t+7kp>LRcgObZGqx z@=OyIUk8l{9pNb~t_mUoCJzNQ53emh(7yryz}yY-vGHS8ZobzX4*dLV{G3iKW5Zno zzI(v@5im0z+|vangM(vPCwIHszhXiCtMlviaq->S=HAA@(Pr+&I&i-2-8CQH-v0Qx zdR?=A{pTdGG&3{UH!CnXz4OzjvfikS(1nz^h-JV$%}T0H&P?0yDB7#Y$;$`nr?uMR z&C-m@@|ydt-x+l+ZBYHER#x}1mNxKzOT817K_G!MS5(th1^hc1aQ;_QR>0L$6MLy| z>qT#9?Mh~9?qH#>cx1_vd>5_TcwH`{~)`_1dqj)c(|r zX#cdR#Gr)1z=+V<^w8MJu(7G6_`pQJvI?QHyq31ShL(_ss+jf@FD_~u!2j|v^muJz z=%}gZPn14e&oOzu{zOT#OiB>gopLpU$qaaUlLqtt`$C&tERi zf9qZ79#8xR@=tf$_2pd$t6M*|)`vDP0RQl?WAFIqT6OoIliQQ%g3QeEsHCyD;CSHw zFOE#hN>0!C9{1rxd`e>Ad_wH!gO;|u(vG8|n%YXh{|f_g`9{FMjIC?0X{akaz7&0{ zsVyZap$n@htEhk|rL3zguPyUVQBzY_SqF+iHL_7Pvxe&ZvVi|?ZmK8YZD9-eSJL1b zVC=4EZ%Ri@s0M^xU20VoX)2W$Dyp)461)l`5|ZrNB7$s;0_fgukA2XPh#0x_SZPe3 zlfCj}VdUjv=3^(bW2fZcbY2|)3jF_(Uq|K#{bsudd(sCdS0=tLWKPa?_uWkm0PxFZ z$vV)#c8>Ny{`d6fRc=$^Rb^i9-FEfa&zn!bf<6ppL;(IvWKUvre?laf|AdCcCue_v z*8dhpmNI``#zdxP0|Z7M82eWi6;#$XHl7xpmKGM=H8vMEU+bz1OAAN~Nr;I_%ZP}I zzf%^M_LP>B0>?k5(1ZTPOe1CB|D$==+vyw zxdH#A|L5rF!fQsbAX@LsN6hW1}a_BXiwH>wUA68(TjX*N?9cj!uuNde43y zUV!@N)cw}=>X#XS1DpFD7~>ZbR#)W@ra%et{>jN7iz6da;xbAyJF??bGBRVTsxsQ! zKIWAI|2sI{f%*SsYiWCREy%|gB-ORn)Jo~TdoLvnHS{X{11--1B);66o~-$ zFaKPT7x*8rh{5I0obv?-l>q}e{c}?zN;Vo^4z3qo9JE}|si@qE7#A1D28M=LR{rC^ z1OD^K@Y3Yc0^oo5PlNgI_U7Kv!9iZ<&dE6l0c2mV>u&!1*|@t1_Ky!u z4-N?E4hoBjNsRQ(nr#k`N)L^0j7R|dJka*8@a>H?xtZk|>i@2vF=tqqNw#hvt= zl%W0}TRB%fLpys1QxSMV7|^5zmJAUS2Lc02K#TkgpO+X14}|~*504!V5t9#rfs6MQ z9ne3hm|4O2mxhp?ofeG$*~y>7asXyZcklAim!+}8@h@NIdU_5=hrY~@3_$%~ou}Wv z0{_$T1ONNJ|M2IJ{oSp#^UFWO`?rVP{kP}0Q`Z~6f&;^%!1(uba&YYDdjF8{#O$`@ zusEmzAT2wtz&ExfF(@aerKvc$ys@gZxG=Y_z9YP}rQAQcwj`;p`eU`Mk&HX2{|m~9 zN{cH?+NKaZK}rMwv?3%C(qSe-hruSmWyd9W0&Fgic>(_qg&LNO_66+=emq_V zT9AK4pe17=U}5L_-nYCv{BZs696lcInf>!^_^ z|EvFFg5e%BI|9(Z0sD>_iJOo0y$jr5|3gA_K;+%Ta8K`tkkFvLspaLs?AXk0p#Mdt z+)UhD13p;i@7=@Ev#pKG5h(v`;&$QqY-eNfvj2MLH>pVTw6M5@7{88` zgxJ*Nyr9_B06+l`DQRg*Z!5`fEKI5@%?&T^XfF%@Sl*P;7}(TWo7rk5tRX37p`VZEI;2O$}8+Jt18sdEkH4Hx@Lv@HTaob+py9w)AwBvi7kww^0Nf2&n%7 z1(6)_F@%Z;5gq59=u2D(uI6JDf~Q1c3VdMxi;sf|&&@#njEtWW%0EY9C#OTEVdCP( zr03vyO2PVuiiByqdvSSSWDGj~JDnLH=-lg`U7DN@&Wyhr>^ht4oTx4<9{91o*O)gB z{7%~&tFs4(C#R=hu2*j_XO4I79{AT~QE4I7K?yPb)gQvMW)_RHVnYG{vN}E$r{1gHMLrQ^7fQ0^- z1?d0Jm`Io~9^;abP(DV$riS4F*0E>2RIjKp$eEdO*eLmc{`G|KB?lZc1qW3ByYC+9 zUhn_Dcr^*Uu9<;h>HDLd(EWecH8ax-Fs9XI00q5tf4IFpcf5bLw>p1z)x3Xnck{Du z`~G(Q=A>luQ)NhoZ=~;dZDMGcU&_a{sMz?7nB30T;@(fqO@+y~iKTfT0Ujs&Q^BWF z;Qen5Y;P@Tu4&J2|J0Npnxd;FC?=()t1qM>qUhnMBCG@YAJ`~rODJi*)gp8j6g4yk z{9hq!CsTVZ30o&S3o`>@HFq}~H)n^x{x1m%Sg6#GM-LB&4F`jOiHe8!63Vxu`OCMX z#zUlg!N3d5ZieKHXk>iQ?dJphV_GH-Qs$TTApgMhC1Rz&`)k+W)#yy;@af3Vm$`38 zlOrJi(D(gY&)3PdjqRh()n9vmF4hlcf9$Uno?jmRKHR%I{am+kaXEXr9gzAt*gxGb zv@8nnZ{vc(n$ja;qrQj7X9Z^_&SjWanH<|+T3+b>5->B^HySuM-SzF)_pw7L z|9b1oz~uJ*kCEN=!<)5@pL@r<(|4DDS~r`APj-Lb*PT6_|0V}JC)>jOLVObg{Rd(r zQ?eFfW51`z`^F|srl$JN0`BX_=F;M$0^on&E^f#L{M(A6s^&zHg3B)e`VX?35rT&g zs;yqG&fnfBM$cCSR|M@F0;Adq=U}a_F z;f2^+e#4pP4a|mESM?@s0WF<%CwF6gRW|DtUYf>cG zRH*)6P+kyQQB;>#THJ7zQC41G*O-%^*;rXq_XekbV|EdJ9C>k&|9JZh9~Dzm zRE(XbX^DW85S0i?T(Nc%0iFc^wY?e1Gr<3+WTayCGBv_N_Iydl&ckWy0D0=}!o*D- z9^t!vv>Fr@{^Lvcz(~~C^x){+X#ep-Pd}Ld?92`vYy-jT?oWPsU3E=EWB%yg!v5;< z!Fk>O{qOD{-~t%j5tkNGl9rYN_*R9X;c=nq8BwLdk*PT!vV#hv(^3kHc0X6O9Rfbk zY7un)d)wYvc3<0EnOocV31mqwrPMS9Bm@P;rBxLH(F*vV6jhZ~HMKz}AfdNF|8X%k z_A&$hUmrnEEm2!XGb1-PNh>>hcUu{IV>b^+Qwg=lj9T=7Wz46*10cvSDCpFvPf@Y) zaBxWJ@j(9{A}VewdNOWI40cAE*Uwqlk+>;%>0nVF^j`#SR`Su_uf4uUiz_2zeN(ds zeaqj6HcvtR4}_r(PbMe&Cyw@i1M}aX$>II;%e~9H-n-xXzi;k$FYfLZ00aN@S6QmB zZ)aq7R#bAle?VbeWODXGc4TZ^eM)*pYU+G_a9&<|aZYi`QBGcMZG2@>Wl2hVV{_T3 z7~p@-FKlSKG?5XN(Gc?%R}xiF5R>&~{Q*H}^I&^Dr?qq|ny~ z{9idMS8o{?2Mc>QW@G#Jh7K(5@TA22oUeE&QSku(=#elJHa3bn8$JdS2@DZ70Wl*k zA1w+GF9?7b(7plwcScIk^Mx1c|Ka9k;b3JW1Nj$l1jO8e;xDNG^#1LCGd@2(kvTCJ z+x-R10s;T#Z~o(QAJjjO_t(xrLG<+K_i^pXpUvxymim(Lz@YIqkbm)yObi%^iU9e~ z)b#1)c;D#+5C`#%-7l>8d>m2;ihi}lg&&LkTC1AV%gT#uJI;%m@;X{`bWkL;HDqK( zfd2Eh{-dNTuc0fYDC?=Ms3R;Qui{N-YHQ|Y?yjywCi4Q+uoSpiIr$WMK!8V)iJM){ zg9M(JNCrom{1GOupr)8G(LesNtRf301}h#S3di%uyp*Jr94}yBQou8iBQaC(VDr7E zc?S9)!1KLg+!_Abz4m>mXMcELXn%0}a&TpQVsiM){mOFB40QhU<9g_Jt7;th|H^Y` zkMDv0|Ec-g`T6nBpQSY|jdQ=FCkOk+E3<(;i~E}GKT0<#*+s@tmX{XzT%{QXO43%r%mwZ{~2!!n@ZPyNpP-H%`A zJyXByHm@&#FZ~)03(FXf^!1Mmu8;Ev{>MuH&=2v+@u|7V{;3Ip(EdMx0r^KI-pu;h#EadFZIUMZ@(l z(s8o}`Ug9Ow9Q)!17lY*ZdF%FYhw*pQ;`4GaJEKz2Co0GlyF!C7&sVk@JM`5PziZK z7a)>nazvzX!2dx(_maZ)B|RI39vLkwIVB&38wVFRCno|IFN+H$56|fLU%>tr3Hay0 z|JOe^{(b0TV&dzMh0d?D-G8Q+rVp==?snGx+-w|eY(ncl)0YQFe@_2g*WR7}KA))^ zs__316cg3ekrEXhnAjQ|n~?G$JZ~(!aG>UM`^U1rl9Hm_@|@P#=AxR4;^L3RX`eq= zw$)abS2wm7L?z}Gf$i7SFpyCem-BH_6cc(UrERICCaJC{t4rb_ETW?U{I9}ht`^?% zvQ|b;76z{B61Kqp>EY?*swe%{z}Q(I0~JdQ?J3=3R0M1YGUZ?Y+Y`|G<_Uq~BX~G` z2qpuw8PI<@KDg`$I$4zAdf{j?GU@g~W%&u6Hl= zg8ol?cL%$d6_@MhK>ys^KfM3lSY7k&ycYPs&UaRS2L%oK#e5j`_w!9kNc8vZi%N(~ z%J>eF;6VS2i=GLo`uMm1$HsbaQ(k^!b$NaL`pxE_=%$XgkDm^ICRFQ~ywlbamDRCR zk%2J~l@q|Tl~>X0eP8 zxMMMJaF96?;aRyN@{Etihpl}Z9GnQ4S(xk23OXK|Tv-JE50G@7nvP0byWU^h0{$P+ z|7-hfnXest(4MHV#&L8Xg`_ULMf#zu1HSksJ+z$cXwF zp8y_~^d;E;Z*b6IxZrS!;mB~wUlDSX(@=4uuv5{qB9M{)%|GxlqP-z!;&}u7Pm7yh zLFnguRNv&6nYoaep{0e%`NizrHD?J`9X&Y_VId7STTLqu zPmq6b8&(Gv*sOY#iW!WJZ08Lx+b+KtoE3!-YpoN6)}bLd(Gk`u{v; zrl5gi*bG^g6Gn3N*e|Gd0a1g)EUqkz!ZEWq_ z{Q&&G-u?CC-R+(2^S{@>>A(4}t=`kMEdSu>qKq)#YX9iqfKPs5fOnZ3S)LXaoSv1O z9T1nAUXWYV(w1G^&{$HH8(p2#7*q49HLbF(qaopQQ%!sA|F!*w65`?ln#xWp+B$Md z;wnl8ib4WfMplCFEe%8rT}(`koRswKZGit-)zaNg#S83yhyRZM7le;7UIYFQ3k()I zHvThq6e3I}Qh3k~4uOq|nwp!MoDK$fzZvi-7y%NRACs1g37d?a^*P*w|M}+2x3SKF z`M&Adi>}G>skxP(Ba0KiCcb~4p1z#BU*FmJwtsyC`aceyt?$iUodN&X=*`~t?fupF zjnnJOz|08$@YcZOi1N_T$VT7j7?A&M2u;mys|oba{=fErN?~(ta6?Y(ujr=Q(&nAB zmi(Z)mVy>FNewwG869s)X;mW~ML{VEQ2(@6Rg`p<5>`?Z2K7%deN#&Z*50?d-vBkPf;OH!Tu+QfkmT4LBd1gg+YFTjSl*sz9E9g#N>EP zO-0U&#LD*K74z$tv`E~XysS?_{p%Uq3l^?NObjbK1H)f>{w$77cJ=qnoX&k8Ivn}F zv^ah`wUXSOGPMl&f4i$!<@Z;I;9y@L*t}>vY`#0X`MrHry}o|EeGwHs7##W~H8CFa zKP>?^u+UIY>5oW=iO2~INC}9GuREyB$@$n(SXA^296p6Lf9F3D_0^4Sg@sp5wU^qG zp#BT(|0y5@=09-q%5vJO4syC$I(ou7!YYb#?y5Q_1|IrmLe~1$K2FvylIE`OExpW? zRNfkU8+dy-&;ZI23JeA=9W@g!5)KSGDFG4WH8KtoF$n<`1|k-K{1KC}(85wc^)G5V zY)ZCQyvVd%^eBJxe~%cbzWf;L`#Sz(bi8wRY+!n9c;-jf(9+rL^ycz%@6y!e(!mbk zBmZ95T-%-A{k`6|cXjdO>!0&KyZ2|C7rkefH_-kcEx|Ftae0wZVX;Ml$*ECE*%4_~ zDe)hIveV;on@Y>e+yD0e4ag}ir~&=|+9O&&SC_PYt`BK$&25sk5EcfRa(Ix31TYU;*b5|&o)%{?s*oQzbQtxbH?_20RAxypE&J8QWb>YD=o z3j^r?L`Oh?hx3RM1qB_6=m|0%=zq%f3>Fq1hVV7ee`)Fd=HJL@(P^Kv;_~r3KA{B8 z4?eaxFihOzyiD-ZZw+}iOeFMO*piqzc1*xQlBUQkv-SX|LfUROd{PEJQo zUqwY!NkdIo&DPjUS=z(L$jZdgLDJ;Co|%}mrH`kxqqC5goq@flDWVANfAw#A2q`Q+ zCkHwTCgu|ibY83{K>t9bf#;;B=OJe0rDlAJ_L`P~36F+{haQEBf&Kj(3Kv%*F5rBa zi0)h&8kqF;znmVKO^4!7{_B6%_kF2z`$zG?0r0<`p5%b0U;BkuXO}1Y9fv=Dom~E3 z*ZVE(=Tb#KRYR7zbLn$sP(YDx-=gYe9G!?8yib2{*+g> zR@a{vg884Q>|-e@48gZ@q7Y>nQAv0e1!Xx&QfXODIb~y2EolEYQ(IF{`uBDghDI-( ztf0j|PA?B0dr<#iGVpQs1pXH|M5zBA`4Jo|4ho(I0Ri%V{XgZw+!*rWwHY0qEg7hP z>#+j+gE2c7Ei*Tr7Y{jy9heNTGw#J*91kpk`fv2v%-~Sh{PyD5#MIK4HQ$Ndz>Ayf zt+|$&{lEQhzF%x!>>nNc`q_K;Ywzat4%B|GHwNdkQ-S~CLw0UJdQd`3U2V1i{`@f~GOM7jrpQ}CO&XlYq~*j# zl+Bfa70y;rLK90@#za9=313W9!^*&m2K0ZiF@5f6Zth^F&*lD})6vexjoH-A&JFnA z2q9=BGU%iTZ*j06!^5&;;i0JEVWDDZ!-Dxg=zl{(#Yf3t!$4#Gnx2``q$IihG#=m6G`B|7?MmopBR=)L&jLiE^LiPXj!HKz%=>@3% zvAp=#|GKwR`3txJ0srIU&#S|m`;nv5A8n_1s~a`jTX$D6BN={v>AqpzenDLkQ2uF5 zY-;TH(3p%wX#G1dB)_;Kv#OxvsJ5mGw1C*GFRpEEyD$G#Th#IC^i%Ccv#c8Me?$HM zstV$Qfbag$|IbWTQ9}!w|2FY9a+73HlrnO$)irF3lT31XL}bt>F3U}e?gVt~OPoE(?!6N1(K<43nK>?0`R5E@FHX?KyPF{8< zZZtk-N_I{vPS4$yrOwXb?<>dMi@h`dtN)&VUznTj>pS?H|N8dx&))sY{MqV{1K|H1 zJiI!dIKKJ0*1NlX)^e5B-IW&;8uT&fOJ@aO-}E(s{9{sGQbNi!Xm^#IJ^L~5V|n|= zVN6A9LUCJOaB1GJvg(4C?49Vu=E9bShHEz+(EnH4P8Nh-M3g=8MGeqBTpeunP;KP2 zp!^GSZyOIIQ+;cF9aCdhLrY6%S8o^Vx7y}5p6{Ivj5VFDFo;-qpQ1m8Lxp2RL&Ska zevGfeje?4YLjZZiOMs4ri2sP3i;IkpRU^CO4?#;hJOS&)s+=jK=~JI^>ytf9a%XQtu%c^S%2ns^b5beZe(|`E~`(q0eBeO#zXWi4^ zd-`XNj+ah=_OW)_xx2Y_xVEGJse^!LT=_4RFPMMy?KbZS^|WkqUsR%Tyq zU3X$aUrh9J5^zMv#edE(EN^Qm4=>0mugobds0iz*Xs@q`Z!ZM-x3-MWEp1{tB6m?sGCpi}%6ZOvUL}$;}t{*)UeWyLMW5X*MOLG$=-)5FOV;2@uPRI6kK!1RPhyJgZ z^ZR>CKkp9q55J%N-T@G#v+cgXkP-m>OKlE_3ki&`0VaT~%GCJqwCIBP4}l4vePfH7 zvWoLsN&*``R)p5(l{AD^wU<n||))tf0aub#nHJ5cz77^5SQ&pAJ zQC3tHG*Z)4c(17g`u|vJ8ta)#+9();{JXcAxUzskP!#MC2Wk254vi)qmyw@{b5LbmgDws^Yv=lGhOvv~>r`M^A4n z6AK#?IRguQXLB1bJ5NUt1o1I&bYwC9&;Rfa1{PfuTMPlBL5Pcw_JmLy2N(3eU+kU(`p?eHeCPP({8-k? z@bc34$?30CiCLw;ZdZ@?yVke&w|BR;LH_H@+TO3f_3xgG^^Ln? zF=-745?N;geH&{RPds;ZRe3ErDLn~QL2WfP6*Y4k8*eKEJuf{|YbR}C2Up;KwX{(1 zHg?mK6*m0K|KNsu`~>L{DaJbrTs(AS2-0H~Lc}NNu>*4 z99~xbmvDIO%&auTtZ;nKS$G)7|LPytGgJN1!2LcpIh8RwGQR-)FA1GJ-)AP14*nGP z{#NSmV{q&2DO3t`UYi_K8UjIuPbK9Gm8k)jnWOSt83TO#{{@?Ha z`9B?WMAQUqjf7McwKc6ZLH)zr(8reHt&_8jg`T;YlAEcAjUecZ?B?cRYC?qs3-ZsT zObk%|#Tybl(#Mpv$e5^1#LtM}Iid4EQYioEH6PQP*Q|JaG}Nr@SiEl)CEvn8U?9*h zRPgP>@`~}*8^$+GoX|CJ5Iyj}mJ1@}xioPY3Y53vOR|41pMd$CQBId%)wN_knJ~ zEATxEzBR!28?cfB{DeSg^B!)6KoB4){t$?18FWbyY>)aGbm{7U>m0zkhgs|R|L(U52rbY@ zAV9J!|J`r(?%%qx&VTQq8oZZ@W9a?pJs+0PAyuakNTmu41RLT|{;-4qIW2-fc<~`% zT@P4--iiRRSbJE90AY;+pN1s#5jJ3}A8yr&|Mwaev;UULz&m}|@}K{Ez6S74A0CnW zzvpXG`u7NH;eU?^oBa1`h5uVxQv7$naJ_#^ntl+-S~GNu)WJTBAcHP_^!az|EZBh$ z@1V5e-;y~v!XDPit2`_jK!%PW5VlC@W4Y2E*1duhf_LTp1-kAM?C*zXE&%IB(V?FP z9Q?!m@F7RO5Qttj^nM|*|DHLw>fiIF{%_lMz_UEOE~>}Fk^y9!@!@_?AR7O@`yUWk z@Lr%_PawnKH6C7qtBP-A@mE8V|?h+y5Pt z$p8DS&JX`>5ytexUVRSJ@b%#xAp9S)t^>ZRVtLO=ZqDTSXoh|3w6RPP&lS@S~3T*v#YoANcSo>;6Ok4%fF3Xjm>KK`VI~jfBA(u`-vHIX! z%sxezI~a7xuc#b-P~D*+@O?Qd-I+Y}7b-L7(oxvQJt{>_q4}ekjOL@4iY|DZOFNdU zz7jM6rEH5!OA#w-UkT5u>r{7_>F>}wT3Un}SPu;>=y?9C=f#UlzkY0@?#pIoq`RQC zQ9h21jLw*$f_`w!DBSP)#ig11v<;WCEH?mh>Wu_1u3UNo`AE48&o8J8n{_Pxm5fy% z+S0{dY*eHXzYNFZ7_zE&eby~8SJoNW0g9d%8$fbOXbe>nI(#VclBmZzM ztxSdC4Yf1_FX zFa!3f&2v0di%uTX9;Hz~ujk9CdIb})?I5Q<-Gp)D*l0?xz=D73a1?)vwL(@)9X}fH~N8p$hIAgTn6mZHV)VoXVNK?sjqt0yEMT1{oZJgd(_puG4^g#y7~}#qg9}jL1$ZFzF^g@AM;)t3>^N|j~UuR0OENA;-b%T z_Dd!7L)$$KZ?NW1V=MinaxRrcRpt&yamD{ZspKZI=rqQXwX~#PU;&SBODcyf%E+Zb zhGFq`u=Ry}+-kRv`)x3Yq zfX5Y`Z)MQAW~Q{wU549=QBb+1(fSYT`FjeM`$I+O(U!?{H~~nn8;q29;|MQE{O*6vr3>vTOYjEt$MTNy>Y=$E=0#l2{X=OLHM%rUW68TpSRY%}eD zQT?}>ifm9cm%6ocXcJm=X$?koR!JV;zNd`!8!^whwc)fLGmkUya2kgB#^`A}iyGAt z<4@D~gNd^CGg=J)DXI-i6^({>8Nn(q#)s4UK%5z{=0P*BelGR!_8YZmTN~vDQd|j# z?&;2}jUVy&;;KMQWAI6N)1X>R3IU**|I{1o_OU1Fr zqLJ*b{eI? zDn>5tf7g^&7^mEDDp}RUK?cT+cOEX~^=uwal`AVZoJu1~v~+SaM@}sJeP-<*PMZ-m zTsoW%)FJvC*}ZqmkBCr2hy(50O0>bg+GWp@yU6Ubcab?a?xO4+cQ?##w$G*Q8D{J2|3w>Im;c?s zT+x4W%i*NZuHg<{9_LUTJn!IYAcZC_%4TAONgH687~FxdOo{%6iABu_n58|G2|)rU z2{7j_6W!%H#7rLOPNDyrIJB;`LuK4J+^#TF45O?Jb~(cQjyPAwPX$EJ2RBHV5CxFk zR>S;;2 z+X$0%F`H>C%ui8z+6nV-6w_XqvfpIadQ_NEQ9c|bOsj-!nc>3BDxS@Z5Th zA<7t_GFh;BESMCk@PjfTN(K`fPLcTH(F##siin=+*hy=@^3!BtW}eJuW{BpRLyYG9 z$k{yZ+mWfXRw0kMxy;lmL@$WUyPL9^7bT+V;YN>ZouERrM3||_Rm$8&+oi3#$V$vO zPFdO~%!}V;Gq|5aPn$+DAB&#W-sps=EN$l$$2^4Hp302%n1_cOW}3&`>}Fb@=`q`p|FzZ} zkNFb2JZ0v2%)8c}g&x!UNmFZy$7EVPD?H|V8yl-U=EO}?YmLY71*x{R!D9|TWthz# zQ}BRcvOFfMlgk{d9~^|U(#@gXF*Y$wZ5Wp!uVke zw?*_7O^~)~FIOf6W}-yGv@B(^5SQ~rri+&)Lg>dlCuj8jOfQJ$e5Z`&GxVa=it}0z z(HUBr%jREbrN}h4GUsrE%|0x0O!bU&&eO}nq(w2CC7x?~Oo+}?me|~Jn6ddhy@vQs zp*>@a$1Z_k|NKx=W$HMWX?revuG3G_uTwM4tc-Pj6V1tEjpjJ#io@$y+ev2q3V?}2 zTiJ6t;QTF`v-Ko8|A{?idzxBF&h1>SV+$X{!osX;t1>63xN|RVvr?$x7lsKt4~on+Z*4k(Elb0goDp@e z^N7^?*INtf#8!4X2y+bg0o?j$PJJgsY<|UYmQEi>0!eslfy>m?TEcRw)o|ts!JM@Am?>RF%ot+E9lxuC69?Za=o?fk+ zP9KlS=S54Nm_E)mk!kM9+zSScqt*+f??d<4ket#PYaLAFAWN)1Bfp$ko;qr;%8g zxOP@Gw~tvaz&o!-J6Ypkt@Xs$C!m`B&BOo-+?Q-yip zHNz}&rU~;+Yg22fGfS8+hpWs9TIS4+%|twB^wVAtz2qzrA6D>IkPw(=9-IYVp4iz3c23ugammT= z%NZ@LW9~?;Y|U+$$q_B>W8&hN$&QvsWBhR(Eq(0nqC*V+ST&esIJs*4bchKG(+OuC z!{im_dQ>fZ;u5ub+MP^?n2<2lqhyN51DTm_O~U+w zM-bYJ=VG=Bv)eMWV&00AJU`at`S~#)=W2aP%-6YEU&ai)k)2!C#+=9%EgNHgmN9Ko zPy3Zcn_|ug^Q7JBWif+Zl(cKgYhc)(KNlK%cEB_@f?E+pgW6Jmx)nF6bsq3T^w?jB*b!IY)U^>^!uEUH4KWGdgym zFk9>j@@(vqT(xG$evr#Q^I{L?^3Q_U&vN-^VeA*ucPv@rja$wOv0q7B`;f!bKTBed z`DJvbo4K<*_M|ZQ>q4@|g=kgmSz%^iwc=GRM47RdMDts?VNyM7V*kix^Rd|Fd2){F zZ?UfnlRc)t$8HxUdrYsyz9md{JYS97B}{fa{}KC+Fxm0^XY5{Kvg7$$?7lo433qUl zs}}wh`@S%rd1vzw-H81(PtMV~6MI5rW+FhiR)GY^cPbB$*Q)!Co><=*kvU_>E8wF* zEZWLm@e_TC0oI&7mlJ(Sft=Y7Uv0x3Z3^A+ZU%6l;L9(yvh@`3RghZQdW!q1ORdGW z^}BozK5*C@adUgAj|oh&NjYa&c}8@=V3LF?D;?xG@?N!-)lWh!Ho>-stgQ@XBU z8i3(i{a;Yc&3%o)roMqvtDRSChOe#1guN^=!}o}9BxkB=^L;8Z8+sa<5x%3sZ0u{8k-ndWx$>!Dp7vc1WWYaj?Add) z?}o?}vwMj#J~y6yIMbVzOMGK}@$uaETd$~|ExvKSpfHuUD09p=-d99w^=+a|h$j0g z2s602VW#>j$LAc27kmSthp1$ImC2$PeS?H~+uPG-QKrXyX_@sN^W;ENYoo`!>z$;s zXs^ed8DnJLV+Q@ov_1X6lWA=CbszbLiOdh#E39u+JbSFA*Y^+|^oHKd>;t2&KtpFz8`&`3sV+Pepyc$_pI++{ABn!dw%`l z%Y$dnDfEKf`~ArbWR``r)=l3vUxLVd?=c~|?#rJL2xIOv(AKXycYI|<=5Kp9=)`pt z=J`V^Q!LJn>mkgV@rEfEw_cb!O|-3IaTVgSgjsB5s>U4Eki)4lsJ2@R)Kf3^UMUme@6Su*V#?Hb3Pt57?D@1egq@ zrwMo(#-kjfr@`c$1ry?u5_wD?#FG(~nI2a-k)y8oMQy!P+>E%^i8*ulcX8|Rv@3;% zG*ubgBgJhNCQl>7oQ%s#P1v$+?lv{rS(KFWA}?Y7k5x(@_8p9 z+z7=TfeikJk?s4%xRYYxwtl9qt6;bndA+fi5%+i81z{@SDHZo?M%?wd-$iq4Zw`j& zPFy^m8Kuy0IEiKA$mbTsm1_!B^6VKBm*AEOvNyu^?3v_NlUloB6xX`t6maVXxu?I| zeQyD`UNEPh@i|V>(*(P5^>Z=zksz;nzk}g_Au8t%73M$7-0hAK=3C3$j~$5C zs^pFq=CqZm;*Q~qWAfSDQq>(Ntq;drk=%NxxT!y|dLo$hHJUt=m>oyaaeYUOsZfjwl@XnQG+`(>FZOgVk)a@m` zI9Npc-oqW?J|!|C%Zzfz3RBZ#YPzG{X~GorZ0_Mc3OH1PJ2 zH{9E9PqC-xvudH^#Q2AZOs6-M+3CjkhbQM8(^&r~k(uPlgvjq7lS?MvKPfrq$tBT0 zRoWV7+Y0)pi=JoRHf;s{vvTQ4^3Rc44PP`C=Jn5)TJhbqUpw8r{uh#S?n(0cmx!M1 zJ%2uC;IZs;Qvv^S(Q_)*_@|)%C1J{2%|-khb7?N(e#6bQaD=v!~F^87G@GsIL0b%ReNr@HTtt3eT^{hANXqt^IpMrk&S|1x{OL z(0ckxQ>(52V`*!+ZN07kkkl$XUuEL+wDljCTJL)@b^LApXHs~c*241|wYjbTg4CMg z-D2HvJNU1Q=5t@O!WZ+K^r8*X?1 z{dfxs_mJLh`3C+rTT@|%l`{V6;cqLoQfnK{z5PA%a;#>r!hQThq*kZPMy9X-DXBHz z+v(kK`}&_1=7lJkNy1dMn)~_ZL$Pe;3+cu8vkWuXe_WV0C$pKKrPjJ9vYAWL`miI)EOJNq z{}JYUZ{~(*gg*{%W#Cy7!iq=vmNUwql8M>1580L`2+-=WR-+IhMFSBLQcOLU?8SQBn z{p>L-Di}S#cuee5hB@ysFL`m8MHfBh$9Ig(6_1&2YyIUhZERaNJZ6q%ZhFk%w~U@U z9@A;8VSIVC@88*di{E1gTr)DoJ!YHT|AalJo40~wQ8|zK+dGkE(LKyy7L2uLqQ5+u znI(;X{>gJ#WN_8z#RdhyV04mfPkn*!h534zu_wV}7Tb)S>@nrjjZA)zDPd&_c}x|% z(<|aJiw2ll#XY9UK*N*-L+Hiur%lvV0K@S-0w+F==Zb-o!d!^YW=_ex$j+G+0~bZ6 z8*U7=)_s9LbIIHn_*-OdSv~avw}m+##oQ5wZyr=n{eYW4=gh4i2;^rA@i(eHnMQ$N z{+yYlQ6NQRKDK(A2MP-_(6-e)5E5og6jMZ)K2f!bN$Yq%EC29jQJ}cAmHpJUb)dA= z^5cZ1Hg^r&Bg}i&!mfdPg=rPVR2JrS+j`eP6=B}6t#=JnleV&3?;5BsGFz>lZh`xA z`KMc;uGGrTqCEnQgvrjLJp+wJ&r7zgUV)~$^z;gJ&84S*pqJE2wLKjk7$nS0+l%3W zp~BR$nuiC52{Ss187WNbDCSvV_DAU%E6fYFr^5r|a#=V$5XoiX=)kmGJ)IGFUi4hE zdKL$k%k@?)y6QWKE@iu6Qfs%-B7F)46*3e&9wfuYMS~ zmCLIK19ybUUKzg(I0e{;U#6N_a5xZKfcw?l?$wV6d?J%-SI^^tJR)OqTWd}M1KWZY+l#K%t(Z;Z4nbz=N{Y5f!Hv1Df8)jsx)C|~??(etP$bJfWgze;LV zw08vs;@1mPx2cZKwLtOsw+lqj(`sW(PfNz{FOW0S-V=YW0PiJkKdmy3Q#t;k_$TzN zGB*R2jz6wc0qXh5a)a*3%Qa3)OQDOin#l z;?q;v^MB!vT0Q?~eCJf|=}PG0T10=vcNYtHdJJ+@{A0pAYc0GU|2RI~6K&y*_&y?Y z7CUoo{onW}gjt$tn49rKVKYW~kzryICaUHghDk`6BFtmBXHz}N3G;;+jrpZaO2Xn) zj%;5Xa$oYtK^& zMGNt`pS0utOhOf5e)X8IAhj#DqS=^u#$uWu_-QE6i*= z7T#WUtGc6F!x($X=JXBzz(E%*C3^t!GhI!XaT!;%ueNYs|pHZRm;ieUHav;3@+nn`A$|Qr~ zabJkO4@KXxLM7fWGDTM^69QASFh{~)p3Jp`0*Q4C=k!l%Vxz)5ce4FcII)Q^*&|pa zu^IFrL&87YR)~rwwkVu|etm-f$IKS|O{-Q?%Vd%el}v1xOLM8j_G0r9uNG0+#16ug zu)Qdo*im}1`z6y>*~G5!PYTvmhic=$Au6BPLzr$jaTunjFqd)OF-$MfQ|d>&iGVeU zYxNPP14hR%{0u9Fem!NF3W-k&^LZ3AM3}57W|%Op)m$NQq%e!3WJU|~Aehb4dWFO> z!ZbdNk;Zxxq6&%Q3(FlW{vR_p+=_`)M5Z-rJu9^;CeD=+`~&xjFHPg!Ma9JVBE!G* zVJ1Y?!0=e~OLyoy>@GUR)ks{HYn1CIZpk$k4H91!%`xLiYGqNQ#5ZJ=A3aF)_1{F9 zaV?mE<`=&q!k4<}P-2V3w+hFA*@1TWUx>OSUJ{uv5ojz^Afa306=B{zsQd7z~tblauc6O%p2lJScrQhl^Kv&Kx!37 zTgpsGEEvie;m;@53UMz^!$Ow1?TJr`H?DnbWZq7ET9}y= z@C(z~{HDP>iQ}cGy}IM~rC>s|Cvl>*m7V>9!I@I4z#OfGx8Q=aLzr52FA<``!L_+; zE)m?4!{)2bUBPWKrWG-BwXITMc)adLgfnx?S1PzeyfNXbITDkt`IIo%>)@UB-+2V@4*n!Q{AQ`~;l07LxqMhDctK>!W*V8w!As(e zH00-6$lBOj2d{|cWA7`2{1dz;%ooVmEEA&pgEwYNe5uVF4ZWDd-C)?a4SX>z2 zE7?prVKQ(>G!R*fWhxip+_J!9LSU+j=8kPtCPYm==4)gdu7$hG-~*y({7#~F_%FU& z5v(V*@R=rILSP!?>S=nghsdnOlYOogqCUZ)(t6Eb4AU<-QhJ)5Nd^VS7U2vz3g&XH z%;3|(DMdIV)fi)HjSfC1_WX%m{yvK!Yn(RU2Cl{PawvOo(;mB#jesYFAFmYy)ev1VdA3nY!+rsl*|rc21hXm zg?S*V))Dd8e=wXi--6A@#2Zc(W$iQ5su$Opa2~LsfpKZbZ z!lYW}D`8T|>7+7ClD+|xQ8I-tVh*b2Z-N7*txH@O$uv$nAKWZV(%VMn*Wfl` znt1j!Px>vmTbRv%7@6OKd%)nf?6@+H^H1L9Dj)b?Z&3Tgw3)5(?sZ~CywJ^`&W?MB^Pa05+J@!=>ZGB2o&7?8H zd_3I9)J&Qnweoml(KxAg(sW@?ois8Hk`_s=ZMfTE&0CV1B&{iyGplz_IxMwn;qF3P z@0xT}m?mDdG){Uf>6GYc^skW_nUudcTNu;aFr$(RVHL$`-SgPUBv@UXXUacb-Z+yq zHK}&-oUJcQT2MUanzAnGCDHt|mk&dVAZcGNo4-iPQ-Y&~ ze~H7I9p~$$AaX2j%y7fN%q=i^g-Q9uwEj&}YKfeEKbBNNWNsfZGT$YIh546fG?!VJf5f%Q~K{w3*QVOC(RWCl+ek{-!r&#y_3i%cdWpJmFp7n6EH9&c5xHRE+P zX=I6>C9vtFrK?o4=qBW|^N@>OB{coWPp*M*t5D_f?(UEI?_ zZL(!b3ez4tVNkbJ)|-&y7Xk15*H$UNvVJG*LISyabk z()Svf`X2M?Aj3Sw47~aXW)rt{%bDmgCo%ZUG*65q*ASWeVU)^DNvQc=N|g*S%Y52zJ^QF zBR}KWrJ~D44BF)(*KLiQz?#z|*JB;(8pmj6X+?R-U4ZY0f(ui>F%FFYN~7+paq@AA zQfcfUhgt$fYia)~zMlmyJ<_v+EmH>@eH&NeOb0JJ^w&s-z7RBMzC(uv-I?joF`%$~ z-KKni5&u52I*>!}PEbGN1d9I7i)ZfrXOwfPD=cMO@Wry;rpDP!hh7l5acl7XEkRqy zJGiyV=@CsJOKW@XJ--nwT3lI_7-@5RN zqEKIldWziPxeoOc#J?~-9pt+F`~35QeCv#swWAGs&)PC`xfy+Yd_-arPlOg4xv3Z< zwf^CWXd{U=2bMYXq@W)#^SCrGjYJGR1uoiuk4(oc8@TkyU)2q&T|-f{Tsm^#eaS4> ztDWPakFOhU7DiM{7u+(a)_(>q%xh{aOEFyDPYwE^q(QGYGIFU64L6{-LBEGhY0zpO zP}h`R^z=pBjGv{^D`NBI=J=kRpg*c97cDopD$#FJ+F+|eWjvHyt`d5X9{CDd6wS0~ zp+zGt8hf9S>kL10jc9!rD><}W^!*2)Dyo2tr061MoubtitxGiA3q=jeYismPHQXkz zMs#bN4ia_9%hs=(V^GG^ilXJxT07K5PMmzJ+d^thCsg@+hLmu{JacLb#L z3Cux8(WUHr&Rpq{`>_@&3O6_CC!8pli>?vV4_|{8eVl<6z51wf(Q>4>jN3TnI5aQJMN$Mq_yr4EgoV=_Zo0$leP@^fiHX~=b&RW4(N)m7*S zc6ho9we4XL9?N)~A9+YiPuS9$$Z*Qd9&6BMWG>}?m}YE#d6q-}O1sy4X}e+iXNICQ z`X1T)IJ}ukUkq{R2cY!GY2+NW@9fj2#u2nJt}yq5zn_pHs65ujX@ahxjd_9^V2xTR zh~HS{H9mS2I45%5^vKBj47!QfRjv%OBcrgqzt}g#h~K>$D2U(c843g+V`N4O;x}2I z6~u37@VbD%aM9DDUEo}5GZ$;Cl=idzy&E<1V;_=A{h*oqi>G3^>E*GEwv=bo(~wJ# zyooHTD1>>*C`@;-g3XoEPpz+(!h%}J9S$wPs5TaK1S8l+&<=Q`y`VYREp-A4(`1Yh zuYO_LJ`k}hr7aL)y1s46P?SbzvGaHaR-{rBtb^kPt;bGbhM?Ps&IN+1VT@J?a?ply z%dmUcAl!D0%xi+mu6F24LAw#1TH`XJQ_&Sf#7`)7=`-wC4mVc4goZ+dgVq5heswVoJhEnUYW?XZ1QG17{=+LJ%0lGCPfcn-=w9t<{ECB%`c;{&r!MiFj~B>I+O?5Olz!!W?nfldKeGw z$1~8PXdY&n*8Lqew-c)>Ar2H>M;vq%?!+oHM)x7s?m?z^=zl2HOt^e4ye3>*$bBg2 z`f9VU+k}X?=WekIa#ewR#J?YWK)AbxnAuYk`|(=B4M$zIex4ouC$aBjPll=TOQyy- z%#=n_;~(^rkxLU08%-)QI?xZN4naMJX=^Uc9%)MJA^z9`d==L7ZkTq$f81`ErXkup zpl+D{L^O3_Em0>R-Xn2odSqb*l}nE}l?;0Ab5pw5qCf7{(r7LGJ+q6VrMpGpHHKr& zY>R4E#F`bcW<{)7Q4Lt7sF_6%ZZX{F*ez-4oPq{@Z@Ef1-D&AZmfK=cE96Zrechtj z$iB)gv7BqQem43y1<37989a*KLptIhnrcwdSqg~(}dKLMl4AusR2HE_Q zFT=$B1Bfu43t?o|$|83dttqOGh*i{Lh(Wy&r#xp|x*fom!jM1lP4u#e5};V(ckj5r zF8zXA8AaEp;%+3s+zi~C>$!*Dq+5U*n4QQpjPUn8zA`mhU&9k-;rOpZ@Y&C$AueW_ z)EJ4CgguE&jg^Gkz<;ok@M$(ZvNgqcdl0je*{*0|5M(JS7R*0*iM zdJ%^2E+fZsU3{bxW1dn1@6=FFP6z?i=WueFani=3^9pPwt@XUB~27cCHB z(;wsr`w3a#r;^N-LL_by?hRz0*99#b?9d({{4XMc>pB#Vc;|uucbrtZ=$&nc5XKW)xt=})Z)_9=#gL05oBs@?K{jK z?RgJq(RhBa1L8WMJl8x(8##mbx?-9_Jm2 zO9@zyz7W(39zPC*nLXK|rga!Ko#ap}K}CDwOZ7l$^bW?dXI-}7-Pt(r3wnJB?w|w> zj5rhlN{_r_BWJUR;BD+DwU?bR3W~8({EtT;DC%O$K)d@9Gc)v=bTTz~Y^smEM5 zXwkKCBs`?0hY_)g@*)m&%)iH)tY`%^D;kI>R=IDnmhssh859{VK{Q`R8!3W5$6U~U zG(>djN$DW+sd9@jT6`Wv#zPiW^mhcYDs@8`{ksIkAvUzNx6p>7RAf;_m4+D96mv%H ziy(%ybS1PXdKSLd(mIGC<^DoUD)J+S6eYtdMZ*z8ihj5Kt%?{@?meqzBjy(0`#AI| zEKpP!v-WPW{&|eA)+mKptEfC?t)dU%7e!|fFN(4--xQUGW<_N&YZW!etW|UZvsTd; z7%fGIF>4idwpunJu9O>ssACkSvhar9xdkz6E5lMBRmLsxePZ*B9(o(#qc=(r)fBER z=1g5d^$~TA1QozYHxu+!Kg=UR2gYKrC+KI`oDLMGRQONtPfx)Ida|j4ETE{`a|ZRq z?pnE(m`93gV86?E3iulgh&AoUWyom^WoF{OSXx_(ekeKtOBuC`?15&k(JrzGYonr> z(+t{;+|FDooy1sc7z#8Ex%TjEtgR(Hf($yr;moBw^xbk-Xy$Q*n%z%AG|o z6{TUc6xD=J`JM|WAdCXvUpcfMqm|TzQ9bmWZ>_@kZol>;jo!$>zNsmDvOhBGyFj>a zUu*R7lP|S}pLpp>n4fgF@y~+}|VHb{53nB2>h`;(ZKUnE2ON zoXx`WOB{5HoWslnf}R>a?E#-FHxKK_QNjt75%@Lu8g_cql8@o#9jzu0fU&l)*( zSDPA#k^i)A9AaY}YJ@2dysN(YZwU4l!o2}YXA7zgPtF(3s~0=8TDW(x3bB?{I*s|k zrKxlPW63Cumcy3*t(cpS`7v0~`fz7F_BwV)u0OZ8b3GiW;|A0SgL+*W%G0W5r+F2BdBw54%T0eD~ARkB6w88RNCgI z!#0X%zV1X)yP@^Zw6qRv=h6;;NZR`fI?TP-~U z3siF|Mq0Teh)LZ&#A41o-G()X)@>e6;buX|R(XQF? z-ql(oOz$JwH$V&iKWtI!%cC{5?;31Tu03o~^ae^5CBhb-N9`i@(JrI*kz};Xmd4UX zwEK2D*8D!&RqO9VyL*NE1nuq@^dBPob3wNevEK^1I>ew&@S2WvHH`F+Qu-oh*)M`# z$1J-bC=IdpyP$uOm9GhEgjnO7$}l|uU;QK8G4$g$5N-kyYk3~w*?0{WZ0yOi@j5KX z5_AC;>=LvF7JMM6A}mle4;CnT1r{8V(kigvdqID~g7boE!-89aD#L>KUfjl4u;4C1 z<6uEWL7fmKH3a2Dv^N%X01?Xwf5Q-dz9^{qGCaQ%RD3wj6@uP?X1=S!Uux`$6Gm@F zbLQe{pP=>FZR*Xe4>p$;uF*_=n&wg>Vpruphi3K_?j5lnRS>y6&{ADc?-fR_L@!t> z-0i8z-hzgYHl_a|?)f<#{^HP+rriw86dpmB%4gs&2}zBXgN$4n;$Wa~=h24NZH9Ty zT$mpB=3r?G!7tAX_bL3cOwfGzWxb&P!TMJPm4ROr?Sfx+3zrGMd?;uUdi0f`E%3_; zK?(57IUxLPGWg{W;qt>Tw*-9y&AgWi(;E0CPaj6f(2`HkK-gSLP+9n;lAyS0xF;0! zEd25i5Ju%G+&=@w(r56C&aD~n%VQ!}IRmSKpmy-fC_#1Mm+69jM;l88wSiw=0ZOCC zhB$O;07uhk?3>OAdTljkfS@m-MY+ublyhnDOE|F%ysM$Fh3O4 z?qN{uWVJa=e_+m3hFq9FLaFY}O83Lele(k5_2@o&0rouvE{*Wg!t^s*8!p@#jPGbc1)j#9K~Ov7(+oiu5R=mcrOk0@ zmY__?%@fo<6Rinqg^^h%=y{C7DnU=N^LkVpRJArO^+VZ@zIH_m3j( z1A@{qD~bww7M4~L^ftz*zMxkyYugEW6|=UtpfqS1C1@IE?Q}s8^Q;vVgIxEDpbLoD zJ%al7z!OhFl@KpK2`V>Ck)A00L>b`@>*3v|H zjgd=9h`%q!GpdJ(JuGM}yrHwU0Aff{4@CA^DLn`|<;KI~7r{C7E3DTD%R-O16!{P_ zr2EeE=&zQ}T3|}kFqV4vnh%kqH-?|hHE05QskfYs5lvh-jh@ErsW*Z9`!>As5Kx$6 zkT*IA`WDgI1qicvglVIFPt$HQXjZEp$4pkV7c)iCP5403hnN+=ioWU4{HLG}$Vj(= z(r6jv{+Y-Y3_}#(2Et#=hIjRB@IE}GT8blIDLMuVibvS0otUBgP9V5V0o6++@rBZJ*Te-%Qccc8LakXyq<>b@A6@F>ZUOuvICx?!&f5L0jW1 z`!wjB_k0HTHvP;hGzyW}eJcC(EF$&?AiSjlKc5DQ_S#QN@HP;*^vFwf4f-8pp`8CW zgZN!A<@n7XMf~QEqIWPJium0qMMFGGu^;cDR;5R-dS1hR6|?6(Sug)Ve>+X%eMu)o z33~|t!OYXrBVJwPT=cEfbe?akk@;r;VRwUQV!8ClGH+z^(o|%hA5hvZvKHA#^?f_d z=*vX*xg>S3^fKHZn2p+6DP$GR5sxGLFiMYfOfjQX7$+hf!PD0F57}p9{Vl5R*^)** zF?;@;!M-1k*>gkCGnhS$+C|<&&S8D+B7Y*ksHLxCwNhKkAZIJOg?Y{>J;LvYsN6xU zTaP}+Tz=0!+?*q?v0Rv*!)o<9N_|wTqPbgl;N6Shu-*+ZXedxb>I**rz+ySa`e!O4H~ojMk7jJX+r%hm8QrJyXVm)0v`( zXNn@8DT;WeDB_u-h-Zo-o+*r?HSaHpuK^<_(<9YeYjmbZc3ZBZ$K|fUb3tn`iY~43 zD*kpDe2@39<{QLkA?5JLl0DRat3gw6KGM?actWeFKplgsAX=5%yUd{V7(M0q+xm)l z?knQ=A{Fslj*9p#M@7$B^oB(Ptrq?czsfayMp1O%7NKvaWEA$nK1R`{$7bVia-hZZ zNIjG&n&xrX$0O>_fX96TB1~gw0%8My@E2SS?6mYPy%xwX9OL-=&hyRe2_kRQ6}f!t z@cdKIN0>eO+-hRP#JC^pmPY$v?BNtW56z15BVx72x7eX5cNf}Fln;@u=uOOIMLjU{ z6xBht==(fd_4i!!%w{9Ujd(i#Hm*}Oowgfv%&PdpIW0p0V$0!9En4gm2P83;1_hB=z z%TP21ZRjXWM6PQlHR7N}xel;iQ4#p66-x0$0<)W+NMK)!-Gh$NS6JU}OWn7zzG=Jn z_rU%hvG34!WCqn-4ZYNMha)qzkZcx$fu)Lmo@!7! z{H*bE3M-wKZbHu$^+kUbCBPO%l@a5LimWl{Guy^JSOc`Q(Ga7#D}12b3#$!!!)lp} znWClbFjEx8+juU9nW9`7Yt?ky51);zCh)k*9kp8az~jpOXf3D&k1My%&WakCdCJwn z%+oRY9)9t?$UglC@u%p0M2^bUw3=(d_c~{~BmOjYzd-z{+&;vg#_k7*KSd8ii=yR- zKYl`llbD^Wg{(L3!1pRQriZbr(PYC_LzHM6uAOha5F5(PLXWiW_wbd*-_+*yJU#O9mj?ZS9iDQP4=IYSu^pxLR&b5oqZCD# zc1MnVNJ`&D^ecL#5zalrwXtaRE2eZ)DJ_lGQW3X^^P~o6WkvkvvZDL({)(bCyA?&( z<=+RalDdbG#}&~9MbUDj0{Bgq$n~(Ok44XVD7wZZ*s@7#jIn4aRz&5F#NhS=rO`Fs z>WD8GNsWzIFBwIbUPC|LkkWDwD~c}Ngq_l9VomTz;T1 zwaV1GxU)cJ<@uII?NQod6=&ditVPh>IQw-LGzwajI|VuA7C?(~v!F$}FL-`Pjctfs z=EBqmkv$G5J;LM0R;5RHv=s5ED&nzJG{B;_>=|s7-<0;XrSaPh_nhUvL`$k=h~-XO zEis9vbfZOK>_fH2ExW&OZBKG7i<#1&Y-tsEOlz#RTE4Muj88G8>#YU5vkbQ%QKEHw z**4Z&Zi20G(dx@SbMCg>N-KBLTJKoy59}bUQtu)tJez|uCYZmE!uBUKZ`nBG}1#IBD-;7=D5eLC(cLQ1|rF> zuvyq?>HF=CkneTXSf61~7wq(vdlGA{uIIzB_i;C|mNyaEoD=c?GgYo#q&rRo+(x^| zv)B)5Yad`WEsYvs8iPGU1wk9J|En(OH{^T0&z?EVoIiG8y;QD0*7G_dcNF_NJ(Emr zL7r84BHAu)#jzqb6}bnoMrqxAINfyuhjS!m@@Pa4o-kq#DjI-QQr~CIgT15ntsG8I zihjrXrloxc<1IqeZ6A38D-M^2X(m=lMIXUyT4RP?)dpbJs@ED=kH3vIRL^C5VV`Pl zj5gjuCdIE!u!b^<&R6{Y0dwh*8*rnd-z<7(mMNulMGmD+Gai2eqpEG(#CoarJ&sZ3 z^$1_IvZH!ximsfoRBr(?%{DGw39oUDymZ~`C7#+^E&p0yErhSQmto4nTH6zK!&G85 zZt_J-XY2*83rfLWK)rDTdx4w6jp%`F`zp6q8(vfUDo!%i-$AKzUHh4peH$#$HU4wE z?mJlbRqi0NV{7OOQxVv*M>H41D10QS6|$|O=a7{(kC#I1^7_FiS<~(@$Z5pR%EUVG z8s7^XxQu6aKzK6?`B{G>@eyp+x*O4k)|hBVdek)K+C?^C6u2~v%3}7MgO*gf2eapr z=&Oy$xh-gAhC%HyGR*nt;dVIxV)T*uQ8)H=Mgvhdzo4zKzLcOlm{I(;aG26D^U4c1 z95at+fJ;NYUg9YakZ~6u61a!p!*@GTrbQ}wMGgNnY72q{S zVY-AE(qAmyk6rE^k=qMPwT%(5RMBea8@_{UoP(unf$%mTEY)_m!BTCvCvu6RFOf^w z0+&jG`{XS~^T6>lAdK=v)1wSz740SdbiH>2v=7{Q)PT1~n9_roJt{W}^FvWP=u?z{ zs8jSOqEpdlh?izNS<4>8z4m-E+Evuho=sDc8MMY0+uDMjhMP9mpq1FksN5|?iK5!b zIjV0d=DDI9@T8(nnCE(?dkynk-<>%FPG{M6^g~f=%yWH<<_TD@+_6m4#t@AD9O*}O z)X(Drf>W-`C$fvkWY@p~Rvl~&;Ef1)oF zaiulZBCguL&83Tnn0~a07LVo%?&A z#M2Nd?U`w6j6vqox+OD=*B%;VxGM00`tK*$tiEc1I91IZVX300R+<`{R~z&(wD7NW z5Vy#aD%bBNgYLqtP_7!HSbr7uAnel-T!Fr6{tJ1dAEqww_}#nM_ZOjskwaf&1yk$) zvKBN#6my0P)7yxM`yiJ_U6A4S@8&G^(q!`{Wh-RXFTvp^VI6L^F)#7u28^%v_X;9M zW2iLpv-p*F<8&-Mz3n}E8=%}RpajlBnqmDJg4Z#Zw=~Z5r0cSOEZy+71ge7 z(99kN)oX+sQqjkMJ6_Snh6Y`4U{KAD20iwrL1C+JDMnuFK2zW5E7;#~{JmK%o!#7^ zedP>#s=Ps??>C6QH?K8DSj68&RgS;it7zO2!}0fpmFsQ$+q;zE_{+;$8fR^O_yxo9 zFL<={y*N|D^&8Zqm?`DIHK%fiF~bz`w|y1y_j(l#v%UQLE5prSY7n2nRF1#$poqUB ztSF_Ba?zvzD8_P$jL|8KIiu*(a-Hx6P$~U)H3^C?O?`%Fjg<13D{^p~r093c74=Yb z-NU6FdRgk8gr$o9fj&mja%|rgk*kV#aTwv9SLDCf1Z_b6;~IGT3-ed^qFI>xnrTiU zOYM@Jy!6;;9fjYw$%RxoZC zPoL)+Eu$wHRM)PO|G@&bB}@~rA1e1QqYhZ#?h*9xG*dbbD|=Pp4q*-01(Zff@b1xl zoO8Zij=hDT*2q^bOiiW#VXs#N2v0X)Gsi}08jIXM9-NP+KMP+~Wi9=%HvaE@uF%c=gzFF{d8Z_fcJ#uz!K_B)qBmFUa zpz(48nP1TZST7rh+!yGPaz(HkZX(S~-`N6x8vn(OYy ztkBX3Y~fOuKEntemeN?vlw&}6Q}(Ly+TWOOs(C5=qNuU$c}a{Le^nDRXRhhTY|I{g zg7ApVNGa&A_IwEHDr$+k+HNX#Y07=o)9hbfL!9bPrtD~gwjy4Xdj@+hMfYR=vQ=UF z4u0X&iAyyy@z&`%MpZG-S<2S00T zJJ2^pe-(%^YUzpgfDIr__7nEpj<6cUtoh>k}Zwambc zzy+RpcQBJff>PiOMX88;MO`p!6}^I)$H=7^j5%B2QVEQ&+A29F zp4hhvH*v0xc^Xw*2_N9zEtUGfLmNsmnl#9u?ucFHu**fp)F}De&i(tbR`6I>qub~& zpS!EkyBLo&=&MF^Ft-@xrGGKE>I0=wN$kRRUt)b7FbaIvjG5g_HAiQLEAV(C_Iv4( zK8Qa>m*EXXZ(&pwjX?Y@zZNY(KYxdZa}Q z{dH?}Dc}F`Gr9B#_ec@uY57DSDzSo@filXFo;p>kPWPkwK53zgjx;y5VkE#I|TD+oy=n zIEwZcFz8rgqs0f0YiadZgWhXk`Y(8}BiwH=cem7p<9NW4OrGg`A>BKa+>P^SJ05+{S3B@#ji| zhAlSev%3sxh%<@GojhdF7VI09tAjQaje6RkCs!Dhy40WoEev|aqHE0!x7u%JJM%#Qsyn zvrN&ga)vvN9H!j5Nv3pxwd&?p!?m$6^XSi;DgHBk~E|1}6 zS+vEX{T5xYXnMfZSYc5mtaob5hD3wLrWmxvqAGa}_hvqWE?e}wMU}8$P<lPii=!!*!I+=F2S1_7?y<|{68(}+^ z8}7m826eagowHH$xV7b!MUUFn=32DPqR%Y4Y*CWcjA`Wc_keBVe^&EmiwfGh(=FO) z(YqF%wFtxEX~v@Kp~JR~_Ez&&i;`?E8L-CGSZe3xT8pNxO*o}%=_IS=f41%_>)i&;ja*AR9w)5@x2)V< zVbj_;JC-B+o4RW)mt^I3SuW2_BlnZ#_SiNKSwDBU&&cs^POX2#dUtF|!{xK1^{VZ8 zZ#$#bS+1}hkNjWRx~)w={FeK`u6fVcoV~-gn_;<@cD-x?XQ_Q{EqA|NxAx97r3>d8 z6nwqDlrm*=%G<-^H_tqN27DY|FC`<~e8 z>uLMb!QOc_YQMoV>hCMOFX8WfDdPQsqS5fFqNURfch@pSxNXE)gG+HIh4Y)9onOZZ zE$%8?&}5cA369=*@Ev?@l%_{sfQJ-Kwus-$RqiT0u83`66s?7yH1KyT(j)wyuOhC? zD7PGcQ8PUf!5&yq{<`=G=+e{axWN@I zTRSR>F6Cbt^pVnaDT<;?xgY$troOMNh~HOM#C=nA!DmoM%uD5tddQ`g>%6lX{fd+3 z5wwQiL*Pb9&*v9$lc0BBf8u6BxpbWFm~-jybW>UYk)vNgT93$Kgqw0ij@I3VF;{dG zr$yB=0%NXkf4UfRaks?xIWEm#Zf`%aD1dncGS`r_V# zTWc5bqaS=`Pos`FsV}_7XZvn_a91kmXPnga*?V=|oTvrYp|9XyZ2eZ;-6*&C3FTbM z+ryL|pQI>_KF)Bchh1`F%lk&^JX_;nUB-;f@D46FT~Na1X*LjauO@=n_^t_yBi2 zR|Q=|-?Dl#mx8;Io5FQli(lU&tEW<4$Z;v|MeJSB)PCmnJdC>_^+s#N3qKck=?-oy z^a=T3c(**Xz>^bAjmO|CEj`rRlx{*V`8FQEK0|a4y~FJuLDY>D^uQSSUeH`b&Rjw7 zf#Xtqr{o!9K}E!nzPH{On$@Z|;peX*=hDM#6yX`fczuuCrO0gaM)wHVtY06vC!z?y z1I1YKGhBR44Uw%+b8BN%wT*Xi*P?Mhc@f^05L-Tlht3Mxi#XMJ)N_HUn`kXPkH}Un zYvvkDA4Qxdp+7Dafv*_hjuoTDZ=Ac71wZS%=MQ0?tEG4MG?o^J7S;CvVp6SIgfTBH zTE0d!Y3T}#uUgd|GfHRUJ;)6DW=IRa;g%p`+c-QP55dp-9Q@{1ck2)cp@;Toy5 zw5#5pr&2GAhFG-GqS+RW_fUGI4AvQLBR#USfI`B?q`|K}WLdQVYQq!Qke zY}uPOg+`+6%OoL^eJy*)jM0!SOV(6KC8<=hSC%m;W62U(LTM5e8AT}n=W99l{T@Ew z-}RsC>gwh3exCI{=XK6~?sK2JiMLK*gpgnFPxICn@VmtD#u5CBSRbMe#M%x6hJ zO%h@QJOk~qiJJEjb36Q+X!ap5t>Wl09L&D137)ZFSVSj z-jFonO>KnW8ZuA!^z44LV$uDJSST;?W*PbsdDeKd32jY6Y=_U0U#mUiLo7kdd6QH7 zGtKKpVoVWT>*BKOD&G86+Ozi?c&qI{SdF@a(kOzfN-Z(obeN7cH;(T@DR?-x=xa+H zU(B3_R_^H6V@!98ARw zisSo4b?DihB}CJdXW+dhFDYnEpQ88SZH{!G)8X*SE0}FTjf<5HUm~SUK&z9mpP+?_ zwE$(iE<#uTUR*(8%{uz{Hq7;F>rbMUP;JDUaqxsDfzq7_PZ-BmYHxW{8Dn6I54#SY zP}b$|L~Tge`tXDjb_?2&gq@7GOK&+RrT7krR>dT&0 z#poj5y9{bq>hDRkXE{CY4_t+TS5qA{#nlpBJnDr?iRTS3VYHe=)@|^oQjQMr$`Z?a z=*dzqmC!b%Y$F2CUk9}@hhx5g{E77wdh$EW+O?N7qNX(ROmF#>GKJ7`-p8+{%yIlm ze!P^a+u3{c*~qE*_7!L^lmqOvLy49kM56f~W6z*`dF%DSnjKgrp7p<;4cO?wsvrFNTV*`g;M7(I*23aG?6FGz*X{uu7U+%!*2jVM zbYLZo@v+p!U6!VS`GQ#V#-Ui_f|%b{E4DW5QJh@QwMJh*5bM6c+J}*zSksix;E|~A zo2JCUt4L3|3cXCO;63P#aWMAHqICUl405Viv1= z(0|*f3(I3BcNYs@6keWEYnpOnAD`~XPGUvPQnbY71#-8NN~7`Qq99%U@#)^0R25IY znZ1MB5UV!oPb_-th^*LJqm#KG_VbLZ4~4#b{rB3;w9-g0c$thFd;l!GTJ}B0h-rib#D_lFSbZpoMaZl0e1`2x+zF4f)s6Vn| zb@T+VE$is1pjhO=*mStqYO=dpQ+nO~S(6BRuc`whm8{I>kxASx0wtaC2#Hqda7EXO( zs~yr9!`66|SH`FvoiTr5)*Gn_n~gprRs-}Iu~wnai1i!FA$9Qn0ADY+pz)A!Bunr$_GO@53L z$tifxW31?lU!$fL=9qM4@U8+p2#s`66TwV%2!4$>KMeMF$Ai!V==>qn5PHC!H`A|f zp0{SA-M1qaGmE5EA<98=O0#>sm0enLm7GFv@6oR)We3dS5jJYxLOYPW+%yRL)WL9; z2kMXV99xd=n4?Q=l*VXJ7RJjI$#c}4#ILmfAl`g~7WO&kbPHO9E@sv;)ilP_}<=p&l?8X_UH^dVs;zF90jv40}}W?r>i9 zbfm#EnK3hc{_?38#mRu^DKg6~0zV|yH9?#F5Z71QAMJ&uU*pZMt>leJd{gCBIc-1Q zeE5r>%h$%QRC4T{!LMR9Xz8u3@SVc0`$sHzE?i4#b$6pBCKjdIJvq^{dFW*n0(UxS zojv*2JJ3q@uyr0S`v6;$kZ0-1199Et*Y1dgS}e}H*7-|vN>ju|Z4&S3V3tbhCZ}|~ z&RY!v>qzkH?!a0cShE8wsv(-Dq=@7t(If`e0z8SM5Q(NjU^Oe~*`$Eg3fS_%8WQ|EJz%E;t6N|t1iIRR)hV#{ z2D)Q`b$4L3R9$jP{-FNu4XlkpDbEz}^_-w#lT(VTg*EQiaYj#Z>TgHjEvURvQ+%wi zwO`>Qh*`4;S6@~{2E#|l`|&@+M?8(N7(eFtS?&e21D?^w^!@otIT*C2pV3pO{Y6c^XAt|Jl!N{72jbTn!yicN+zWpoy=Ol9Iqflo z=Yba}8l_z2O-1}{ZA6|a1iohoFDfz5K*_0hV>fOuzt(pr+Dm3h>eDx%pVRszR$)-K zb=;my!SlmlS>tZj%ztYw;K+;<}f6iaeS(W3r49P_~J?NqyeAPtGRG)5Pxwb$Wu z#Hu{bTX!$;)-Kep)Z}USFbTW*HDAwFkZ#f(s-}con_oXt81)#`q>} z_X2$9Sj3EX>|XS~>G&|b69N`*R#w8-(P1T;YEzLmtimQ4x)ZSIq%xoG>nM?otMi}r z>6St6rTqw`EP{JNTP9=>l$f@+&<;ba6!;mZb zHEOD%P0lP$H8gc7_72}k*2ky~x<-j+5qkU-u)?MXdi)Hw?i-ESFRVoKb2?^p%sxcR z*$WHbN0#?6-XK$c}B$i#soQzht;(ADbo38a7HSKT}29%*XXo#yIeyC&z-*R5WRak*v zv6hXxl9BF|))hv7)C+yB4{ygM`MKlos25qmc(9|aV8oVp%1>AWMSRIA>;CZ8^$Wc< z;t6k!3#@*D6^DCg2}>t`iFMRT{;HUF;EQS3WYpL>m|t*-)}S16_2;}K?JCi9M4p$I zrR%#LA=bdcoB<)|jO2KejSzd8Wg>*Ee$;#!@0@|*Nx?wOy~|NuEyi8K-LP=k@vHcV z%J@}M>xEy1#iMsqjmMj2NKNW~4Rpf#%=N5mS8eU)eOwP}r}&~1VqVN?bR6P$gg%M( zSF=2&JGML4LD(uj4Ci*SH8Ry-qkD#X>k;H&0{=Q0IS}gtav;`+$iZ~{8a3}C2jZoU zBL|Z1X5>J6)O_SXM!%WJ0hKLkzD5ojRiM1ILk^_ntV0f@M%?y2KLlx!iaiuo3$q#d=%DfN&V z#e%cFbUer1Cw>+7)0^J9f4sMnb&O9=ng5n&PpA&3!eeeLb+Ea!{E9gtJT=9KI}y~M zw2~o+@9bST_sbN3=a-f^3#0Sx70GYh54IXsVY2~DTHS;mc-8?HZ|Zl!D9hH#6d&I- z-6UlzP)rre79Ai_=+Pwx|73c(}+*lG5jiQ*F3qi z!FvfPujGC@%A1I=1x*IZ+mgrR1sE?Ip_KS`%}jh5oZ^d_)6m-2eO0I@(>^z`Xm^`f z_aysYZ_ewjFXF|*8XodTx~O>vHMx#k(+(Y{qh=IJDeN>#NftZ}${|-F9euSo!s4X! z`8ZRIt&6?zlnqvCob)X#7*SIQGl0?jYh#qRQj~oA^*u2jRi&%&AJ}$i3!5c~kDg1$ zo9j>;vKCpow^}jf-PT;vm9;*~L2?SM^@-JIg17pk?FyrP!;;fpNSA67v$<|^ml`$K zye{Kl)MU)Se60@s`dvT&j^_{PTd!f>8a1_bbU~Yf=aO&WT|y6)QF0PukuiA?>Oid7 zD7jd_piaeVH`%w?KgZ%K<4y!m3Q%79242)mL+hM|dWo8v2%8NHcaQThFUG1;)D*_J zO7DF}%};0%ovM+pC7AT-iZ9^a87$G9gpc^5I@Ru)QFuQT7QVVLMNaIEnhRi32d%-x zT7|Gwj;Of-t(dH)DRnW<(63EX3L`$UaCe+f`gwB7qxroxFR*S4tS18Ng}~|@SXT$u z{=oVsu$BeZyz8nfhEZIVDfPt_tqOw*+iP(6dpENW_?caw!LOu%mpVQVb>MjBh4J0T6WO2Eg;d!IYt zH^h(69O12G^l9=wQS)yK#@a_I<`TjDARj!Y%pJc#@1|LO6>}TL=@ZaZG1YtHy**g* zre}NHvD720;TxFYVGfP8mU)oE$Ogt^65wqBbkC5oC);lnP0;X5DjIafVSxoR>NCojQ@Hy3HN;##`}K2faw zD6d%Wjm8QCbntA5j~Jeepp>$fR^b^xR^&^^^Zf>-OTl%M`Dp@t;?-by54=CtZebNR zg?qsN!GdpZ4=)OMzmE=!un>hNOEQrIZ=>-6#>ohy5zZ>ErknQ&Pl7U&@I$ z5yaP(Sr6nz*fHdp@)B>#V*Vm|*$ExZ%#%~TQh$J%To+%WyHkC6hai?Qhz~O@#FB^t z=R~dIPNO{WNR&2*nhCF?u9Eu zv(l)kGaoba`ZU-38|{Eta!Nc_VZ^#4ur8r@3mXJ~Al7|4YG4HiC0fUK35{RGU3n`? zqvtTs+M#vc4;F7aV)j55-grfutjX*?^bfL-H^fqhtSidfcPYYL5pqMn)0^-?Uc$9HAB%h z#xkou6Y;TC7wz;-w$d6JQ}!v$&P@wk)oQTNE|7bfX}yE1D)$C`a8>D@N1V`tmbeSL zrYZAqRVj^l(+P1=eE7E75Ulj#*Ld?I<{y%S-iW1i8~U{oTD#=*W5goyRfHF4&%gFT zd%pb6kiL7<7r!Q_(ARFrioI`4!9Du`{xwals4-f9IF|`6R?4vsX;8XR^XC9zQBx7^ zk7kj$gM8Ef`UHLz-B$Dz$!Q0aTw>{ro+92hZm|FLE7a);&ixH&yMMAZ9Br3YFtENe z*IQ-L4uthY+ZC&!)*tRjQEQUMEvPF=<92uz`YtzoOLs{vw&Zl~O!=p&Ug+IJvDWVL z7JVsOSp6@&Mei93OTZHZvFL4CvFL4CvFItcSg&mNEWW&F^rS(4{ozJ$EokDcZ&07| zYpe6#+KYA}tV}D<8kg~`_Eyg}o%U9P+Wyx`%{^=PmABkdZ@phG&;?eZmHyY4mV0YQ zP;z>6SMrk6S*+MPrDy(hrmVaPET_vJMz4@|kd*4JbHluq@RGkLzHSo=oP{@= zhrW)htnCVSS!hEN%S{-2q$hVo-`~o8x(IYT*{X|q`(C!jclII5=;#OkfL<%H#AD_` zo(ppb%oK#ZGs0VMqP@tkowPl}mv;2OEKb{IApC|LDC0Yc>UJ zX3*}dpmjevk`vx5Xy|P4Ra|8aYK;Plkn8!hj3!-ME~nkBfS+x ztw|jm!gZ6dOHt!;lEh>EJgeNnTcv_nYN%Ji>I$yDg#8|UTCBHlrKMh;o$XmwjNOvj zoA4IWO3uILfBh^-V;b6(#FBt9S*$6+HE#niDlr#9JCl>YzDV)9BluO)*fP(v5p%rt z1Fj6M1>v+Yct_D)3y&%B)k6reI_tPNY@tQ6(NNFsUpT(5rAY(ovn$zOYL1?1$f z`=9Y;TaNNdh;#6I67$P69&x+siyR1>iX2Ee4hL=giJ(m$AM0Z|i9RFoO+>v&*u`is zQj-IMnmmErOB%liEusM0vxH3z`foqPEMKl19JG@Ef~(aC?MhB7y8*3^!bZ(E@Z(~A zg1Q&$uJ+#g3++l)vW}(-!!AJm6?f@DpY8^)BHsufjj@)#5sq1NkgFL%*aql5GFG%j zi;(YLcSKv0lD`{xnse|V;(Z#Um(f!ctPY_4$(XVnwI)_i=){_h-Xqpw@0&mBWwzElG8`#dg~!v8DXhtYodDwy^+p3 zKxyFhKqy{k&`7EmopVRwJ3ybC(X)l;Z@vF4%f#dQ1_DO z-#YqVH=>lHn*!fnu?m&^PxK?Pn!=BZwFrHI-bjm@ykOG1GtdWbM!jM!4KWvEYYf7c zW$RX4x9V(_Sb$kBtWxIuZRj7&zC>@NCnnf+Fx^}4pf?IDigqAYF|_s;9HKUIAl7cQ zd$GPkZxkyFt*ImHnjx0nY|TeYl3%-^Hx6M|48BQNJgz>S9ThdH?QsVME77E)#ycUM z!sa8?cz3q`LX8h#YcgtlG+Upe#y?=I4{CfPTL)3&VqKTwt*xk+J^X7ePJ!Y49ZKp7jcRz%G(xK49~3)uHCc5rlY)F!AhGkD6hOtGy(C^wZz&r zVxcP-H62mjIHVmlWl-L$VPTX*Dfcv`)V_lE5$oahxL<8U@nzg8`_|)4(J6js($(k- z>*oySz3;(g8})V+Mi z{a4hk)MQcQz7lk}9y$X^H1$yjkK#W(6@-6oz*hbaSgT>H8%q8REbMZ})so(`JsrG&RLc4ztzedfk@K-W(I{|<7A+uZ2&S-@t(HulxXg5q@^8wQRpaWgOR`6lhfT0h< zhe^y2st=1dtFFUWw(u)<0^Wpu=CBe?U5u-*b1dyU%CGp6?Ogd4>qBxk$uFwNyU4A&OIqjc>2DM9Hgh@1cJ7FIc!WK4P zOvIWwTYYhTSFx3d9=3t4Gw{?upuH6~U!%uw2a7jP_mgiM$D2nwVD$s7EZ*FQSkCjW zO>uoM!NRB!`1|3AWgX8Vx5BGXiO?QVYtml@@neqDqb5~~-}Y^CB6&aB&%D`VTLEWA@AyfFJS*kzwS)&-&FYHd&194<=E>u(ytp#RBc4y!@)o^aO$^Tmk!NZLQ8VE!-F5du3i(b|R0K>MRsf|Jg06(k2Q!e`UE8Sb%BFI1DUr94>Km`aFU&JZFi_E&n^ zT%CW-i+uoM^~dT0S+P1gZSX$U)lw_=*VajRi-&(b{T9B>aaq_i4e{KCf2{p%Nwu<2YNOH_qr0_vNydoqbi<9acX5h z@$9;i-nte$f8^KdK`b99_z)X{SoQ>Ar=TV!#K0h@ML+bcBkrf=*Y|^8X|+Pwy$yWK zbke*qI%!@kdP6}hI$c(*SA$Z%9pt5ZhX3`dWFNNE6P_In;@f(I|8;CrZ=EXPt!KaY zA>sq8L10y^?sX3Z<#;BrBI!QttROG_1FJ@mt8sn?j$+rS)c3}0w2@#?@ z6U0|ENViB}(Y__pMa>)ViL`G$YF>p`p|2m{eN*`R&(MP~hv@D7)$MPH6>sLkhi}EN z@#e@-=>gU3cKA;E+UX5;A=VgvBg@2NRpKf1vK!1xuxOUsqy+wH(ucin+#uFtnJ@O}7Z+j+# zm`ski+l_`wVIuKtW=&uG=Hn641|=~jGqS&{G3Qv*o=JLSLn(|IOu8!B4ziZXb3%f$ zJ9BwF&b*+Z7GhRL@i;S5$uba%$C=CH$#X?KdCWKs<@1xrOjY9ZlgG@tEUmm|!DVUX zy&|nRDmeZ!^Us2oau|@fJ#>RZU3lTL^ECPzhcWckVW0@`GS{EurFm2e|~uc0a_DG-nW)kRY?@3&bv3Smacrbb8zs$JjTH{#+2&TPC_6^6W zc_5hdDye-VR?R`+2$bBmU1}avX(9Rh$KzQ5G$AR8mDGN@$dZzHt0Na_7R65HT%_pV z#*E2D4#5Myl8Zcxn5X9=+poj?GZ*;*WI--6{#vX=5z-`h2JC1 zEnX9seHUgk_*X~6IZV`lvB`a>es`hs$%g z9as5aF3r>{YHVTK%gf#cWhdVornMpI@)}F4X3S5yVtz$Q)W~0E{)Hta&-fqzW#+#| zds9fhnJ;}OB-hn5ra0VwNG?{B{Dfp>rZIQq()_OV6KX~txIFcIKVMFUcQfXmT=67e z0zl(M7>{p1A<5||wGomAYw&hzu6X*6L%EnNxq}KnJuyZ!V25MGz{^mm(nfsb$h> zZKZsFEe9+G{HFDI?<5ib#H(kdA9ajtk?Rl?Pd*faA#Ur{urgS66>aOIFDE_J+k zq%vL;R|P%ybHp5w`~_};PXK}Q;=j!NIq=UkzK5h|aa_SzOI^}o;4Xq_I3fD_CTU7IIbIh!-qENdvp4A{#nEarc7ax(f zTFzvvW-Q3PtU0Ng_j{uCGC8YcHAoT@DSmpy_qg&V!WEJl$;!l>1e)@uAd@2rMQ|5G z9=p6Lq~yhOn3J%k1e5d#`Hi=kv{FKTgGdEKCj%Sv688%#>0KrxhU|r=qB+Mz+H<7{ z?x@X0uKXjG#->J0I+de%GEL1_q=i>9y_9rEp6QgwO6Dyl*|z^iV_Gv=8bf-6R5qtH z)Mr?q7yyE^u9>78A}@ecH5JNBTJ`H;zJan=GlQ6aVHs+nb~@pFl4=1V4NK^;~z-$*EP{}7pPR5OQ^ zT*8=p5IL-F&L~+hQZ#p)eCS2SNIjo~Ceid|64qEE39$Al-o~MYqjNIJv|l%unC}Zn z2FQPi=l+1K0x6JymZv0)=b=DD@z9*4RzQ3_wF5%&+=O`QC<)_1L&skpBl)3t9u0_( zr(QsOJdc}2T+;N&$ePlg8<-VJ=q{i>(rRE1E17c@*3dy3noB|guins}nzhm}J{azoD-)`IU*Z`Ma5%VG_3aCMK(L zn0f}xCgvCu2}QH7CZ<6Z(j2i(9}^$m#PqC6B$e-KMf0+fqtD7*w266}$&rK)9>9t= zr~ZKu+x@l6HCt=o`|GS(s+v_;L6uURRWbtWtW=7o<`R?iz(Y1OS5>3@qz4|dnJJ)T z%O>gF%}o&{`$4FuG&fNtBbvw+Y+=eOnE*ny(ZW)nXY)l{ThtKri~KY4W}7=B6n&H}LWqK*;4l)>;R%W1*RUgV2 z_oR7I$wrXj&^&2IE4jO;F(W}*8}go1KaYZpVKP(6Z2XI6aZj1KN*04qo}V)BDXHF4 z(rRNqR#FRu(rRN?DYNowS#iIP@3b5u!95K60^xe<4&G=9BM z33qJ>)y~vY@+Qa!OvWhbhSa}evPQ{dknKz^Dp@kgnBz<;-z_nJ2a?02uaXg%rO+)){j*$kXP$k`W*eFv-IXFq~_(H$|9a zTJp5im)o1)m}J|^XdBcEI+z0NB(v=Xta^4uJRQt}d#M!J_G6F_nUr{dNZ6M|X&QaYOgOu`YZvniq)KTGLsqN=$SHAd~Qvni{Z@*vbUI-9DhY1~9+DVX#Y|F72M|iDi;j$Py@bq?Q0A{X z#?(OlbT!MB48uH%NH=p#$*W4bn+iN&iN|gLO%F4SiTKs#OtvuzuV4?emq|E!_Amu` zn9EM^_j*0e%}mlFi|&v-_cSG$WG49gxSpmQlZ^@cFn{icw0fHAs(EI$F)1LuOgAPQ zt|{3Ro`K15CfN!8E~%G!iAieY{OyufFKrt#b7+QI?PVshCY($3GFzEsT0f8KWqxFm z9?6>|SFo2kt}zz@p)1(S4(t?QPnt#@}i7Frx*yepCM+dYQDgPj#g=gnAcRZ6GSu% zR5Lx$ELP2;K=YYu(&6VMp3hY?4MgI}RLuwQ-=f*7nl&Jz*`pd$T2@Ymm_w>b01?eG z)iiqAYfh==Nf6Qeqngb@S{8i5-}Jr56k_*E30tyaPhLV2HU@H4DFo z=jW?)yOIo$xI(0)|*Ikb$ue4 zrr&vE4l|)IbI?fb$LX4YVg=?`XvUf0Oj0A0L4Id4Mm4mSa?{PES*wKBQizNMW zOJ>~@%)d<1BGW)JkdFx_z7gk#TMO031ap%RGXhVd=~_(0nvxLp&FZPYGJiH_6}&WS;!$OPGnpd6UuJ&4t`(3tf44Q+)WZl(SI4*B$)<@C-+CvTHcYauZ!J?yUn(%#p>Hj( zUXh<^W(sSE<0JO+o01#&olUiPT~7Tku65vzRE)Vqv zWd?t;!bxcbB+jSxhRNc1rkmPBFdu= zatEE~R?^(rnn-5E&(P+X7EID3yB{#-J=Tm@@{5xBCPPE{x!+sn6qAh+KlgjfoYznf zoy2N9;#puSKSgP!N6J5k`}eIa1IxnR!lODA7wr;DNMrN{ejsOL#SLJ9Vnh~ed7bO;u#^d zzVZ5Pq>=G!qxo@#F>mD}hn0M2dUlpje#PS>^IJC|w7x+d;v>_dhY(ud5K_Ijki>4D zj8Q`K-d~GRsJwkeL!@wVB7CYq_(LOTcnS>+SN9I)} z&F{wwIYKQltuSi(dH6w)kIfi(VYFV;3D0i+U{Ya_kbm$j=_1G{rUjGi1iv!*nQ1YY zG}#G$W%4s~16mTrxwMtaVZ)t)a+1t#f{2SI2i{G}PDq-8+96~cYF z562@@mvqka2qqJFG810YFRv*3mu41gr0lP9s0B>K)2?7rXEfzdV!pK$<>$=HME>6~ z8~6)ft5rA@{hP*u#}{HgpNq5wq4x77D@c=k8+Oq_vo;{zKyY3B|i0arl=C1`g&7JiBEmKxl4&peS^81NowQ^hFII3zX1zoSN z3_Wcnt$*UP$*gA*uDoqBSwaFIwaJtjPoY8|waKhzk{+3;&&0nr8#e{Hf< zLpua$UF&PJOEouwP@caw2UXLkgWR=!ZGKkG(;#%$_O&^s8d@1xju!Q`DKmlclOCa! z0V3a+woJl1`pu@R5bj@lq1kN8OypAV9oZo!bD4yz=$p+#CgBR+X7jOXetc5yn>L%3 zs>uPNQfxNsRpVFDH=A!%<5$r)n_W!OB9DHJy<>M!s2g69QXFf6-M>sOeQa2QED4mW}j*{KH>BHqiVK+NS;rqrsXWJ$x+R-Afmabnnq7bTg@_gCri1W z0ufDqCg~B{<(0{$D5RPckZ+k3Q_Xk~de)z15}AZ8Gt0CQ!Y%V4huSNl%yfhz@0Vp3 zOySg7Q@S*fFPVfsJnM?lAmeMDL2f@)S{ZbSVYH8z#h(C#e^2Fnk99-W`Jt^z9zJA z)qIbgP7+VLYWz+ov}Y!15%Od0`RdG2jo%TJW!}_yUQl0`WfrQ&??=iqOH@cPM3nQNJ(M&89XJwhv`|u4+ew{%i-Ov|jzh;tmCXvwlZ8bkIk^2Ig zF>b{>JsgV1N@`FEjsDw@Y6RNfd;h>xJYzYL%YHZI?g@bsgFm zJ&y}X>(=6bz6~@lfKciIiKBUR5v0DuYvMk*3%iOyb_HYya@q|4`pzW4f6*Gg-&gsa zxlzfX1+rrQomr>E@6X(0vXuD!nS0Frvnig8gj#rZL!N)HX)VNDS|Rz_Yo;>^?;`h^ zg4;Ou{<+!5r`!t-~Ko z(KjjP4CCwY2eVy>r4?zKL;hfXQ%$D|QX4L;W-d7gER) zXoOTyayRazguKKg!+PJ6W8P*WX+2bq;`vYswO&d+=ZaZVj>%+=%$oi$&EYFFJ>RDs zW(GTOa?BVde)n3AS*2uWHg-mEes(Hpdxwk#Ip$9$(&K19UXIDTm|_+(4CHUqAcl+r zIcHvHB0FQpf&61Km}HuSOUAqja^7SsS&3123CKkg`GE6m%AOXl{;#Q_e;&YhK9{!j}rtvwv)(-nrh|ggG zJ5!0TpMrLukRZ?3+apSRp0BrlUESyTdiy(TG7|P^KlLOt;;-`c_6%#na$RpDOC>-4 zdfi|Le?}xMS0OuTnGj#Do9wRT63UnBW?KVeB$d>c>sGsTl@MR9qV}8;f0b{ubykbU zw}fJLo{*sICG0{azU(FJQYK+3O4!WHLPhPC%R)u1e}3U>ENZ{MES{)6$|Nj%Nqcb( zE5w(*tSz%nh%b9Ncv2?8uKV(KsAv*=Nz2sv5p12XdEP zqU3du$xK!%*?@b`*FY-TbxIC{%wzHmlgtF)#wyt@Ov2s8mF%}l{O;mP_5c%^|4{#` zWZSRj+OWPIR<>F{alXc?TK}Bdx0Y(Q%mxW{z9D=M@^iO_%82uRey3RAz1|Sh#jy+2#ps)W-*z}_aMtzvzSS^-tvgeU^3l&*3g)v(A2fh z@{~T^oW28ZL4rJLTkN2ir!%<(Qr|9&A%EV5J>qsB6Db$%h;C$0G70bc8`;aB^*6E? zSR>IXLhnjl=wV%vcD_w zo}`mKrNsB#PFIW$o$NW$1fyPO+w*|r+4rxmcGN*3z7*Z?Y9!?zXvoRTMkk`7^lr(HowM=}XlX;SR#OfvBSN7c$0@B1dHk-$R9o)^5!oL#897X~7nz8Un!#l7Z=?}__y&_zOwuE?X0?*ZUL~|< zCFGov$MeWDmsC6K_b{F;38jSmypU3VkVe{SGfLIg5n_H{g6)@8i98iUQb9)9u1ush zX#an@9W9#3uV{~RKt|hFn50Jj0h!NarfN0@nz^dk6KEDQk(ep<(e}ENxpO$$mJ(tb zpq?r9(Y8hm*$*<-zQ;t?`7;o~c>9THxG&S2IOFXq)%ZQhi2TJQ zEz&*>Z{0SmC)Y#Sx*Mq$oY$(VQTq!>~jn)hs045;yXs1oXfYe7D>H=d(VsfP9y906HsA5%j63jPK8%yv~mo}|Hj#w@cl zm2f<`%FFDU7)^859E%}OF}d*{NsHp?$fR!!>CNQwdDJqyg*D+7Tz17gYMK3>HPQ>l zb37-NP!5H}pQqH*xTN!0bKwOclpospu*}}bn*VoNu}^%qb37$9W=d-hlQK-ivt=NW zWws6zS?g}bWRw!W<8GNPagp*9?*Cb4A7pYQA@P2k1%+O)%=)*P=z2*!kEq7K&9uz6 z5W;UfU2`>s`bY`w457D~mfPh_!nY(=*e{iMkF>&WQu4|`8DUr2?Mh~X&FiDNj9DweJ)>!g!r15VDudz|pG<@1?%BjY`WxU2#Rn5_^cb9vO?bjOp3L8rwh!`>62<^|?)nA+tc%+l5S|e&`BruxprvwXwmbU`2^W zYF`^0?2`tC!OY4(z1?U#FbSU~Z?xT2<42H<_IcI#Yr4@6Rn4vIWHoG~O;e43^JSwQ zubM8nliGkhZ?vzfra#CxOkP*b))M&I2a~r|a|q;nCLb~h-yGj)FMry;(f-Dobn{aq zyuP|LO{#@RhxeHR2QAL(E^|dct&*A`y_wvkr1=HRtU$i8#g#k@GMvffeR;Dj z!y0L;Z$q=$E@l$8)y?*BoLn8>RyW%c*r^$Nb+%k_b+*_FtdXlT;Xyo$wAEwCbS5>F z(6xAz$sqpv&AN>hW2=jdI--MZ677RUu?C5G?ecbTWyMJe81Rc$1q8a z_bdz8~5@c z+wE%AbOj;JcAKG^eoe5888Lrrvs9A;@;Z}~s=4oW>B~E8eylrV1c}rGSqpQAEzAUK zXwC5533$jIwwP+7Amkx;*gI9@-=p7QE2+l6N58|~tD2s8)<#dncGy~~83aOiWIJqq z)g%X+rmE=>Xj-erzt_LRKBF4{UjGi;Lp7OO@N^O--C_GN85P+MvWUqr)ePy0eV`ya z?I?}sWsv1eCaC7r%W}81)6z3_s-KG>l;@rH4b{B=D9+&HPzzPF7UXLtCV@hwN50JR z@kE%UMmB>;JOx#AYoNJVHB|ylN!4^3B;)B$TTV5DL1;YPX{)IwPoQ}~H8%yCI;#06 z&@@oZ)q{OJEmhMa$>+JPYKDSHo;#~1y_GS$xum^SGaY0IE)yMGrbUW<`)m#EO z#+osz>DC-ix|mE-%@B~kn9NemYiJ4dWNoLNrJ`8~Sk@F$Oa& z*-kYVAMo*XR!v@bVu`1ZYKo8Xnn9{54g1 zPqu2xE%EvJLp9YvBtK_Wb9{i;TvE+B5Ya@gp|*iBWwO^4V3Hd77(_HTtLB!5zJ8*r zDG4H)a;ots@a(izRr6m*AI}4-DU9b1V0})Mc)$}NXGpV^{4phx>kV;HaRC8Y$U-s=vY-3Lb=ac&RH6V+X z6b?uY%tgqr`gN_H_A@2p;BBO&KQT#w&w9-*s@Vi0n%h;AzlqnBS4~9_(NtGWJjMdaPYoqyK}7S2k}rB=RSji7 zY#VAkdq9daNmk7QjFr@L58HOCSr0-j{IKn+8o!79u-(HXEwZAPtdt(M`EH=H%h^zL zM}OE>h#@p5Ic#S#8ObZRq&Z?M(=JQc?vN49#VR9gbG-5(eZGE0wGKrK9NgF1`n4}we+q5H-$}waF z$S-z544Dmb!u}RR=H9iggRyCD0u}ff%b;w*a8?e z(ZYi_K>xB$lpIAbAkA63OUZeJx`cTCu_ek%sQ>EWjC7FmwuzFdGjWn1Mw$!ufRIRI zZHE`_=5iEjWN^~aMc#20yM}hr9%D^-62e8>r#ywq?{{ z6YM{Gj!AlO(#U`I0LDlv>DiCv?2${h=UqgkMfE_QjXP73)3W|OJLBqAl2A>NuOZOn zaXp#HlL$IFAv$+Wc7U=3m}7!ZFV zS)nW9DI93%gf#LtMFK+cWFekg1LET;aYa1QKtu76kGee|6we{Vb4NgYJY}zlr(B?+ zc*wI=2nfY<67f_Fh>xf074cLHG!zf{!@C1Q@mxYY_XNbp^S~AH)Ce>b58c&16cCDs zcI(y*h>z!Cx0J77dc?nfk>u9MHI4Z9FOu9w)%bHtliU{76sd<(&JlBx`%X2t=^b>E z`+-Snq^#aSC%Io#e-%m+$=TsBshnJIZ7ZBzrfwv@u@2n-cf=t3^{YmZ? z)pUAV?irI@N!9qXbdp?o)y$qR&)<_=H7041cR}dcdXlT5nx%oJj%vOPG!0ebpW`RF zma6g3@snIT)vUW+Rv408XVv((W0G7S)hvGwrw?+i4q}oT*$P5?6p~zuYG{T=V`Y+C zp@e2=MCv$shfbao(LCW1cZxOQC7L6iy6)nAJ zX+~7ny{P1x%`zjZ>&7t2H2#E;x^9CKzj9mGosdw$p6z<>jK<@iJJfR*nWP1$7}axe zH6-;}cSw!ZbNQKsr$yCsg;nF%VC%VJs_|>E_1v9IrbnKA8Y{ObX+771NoH`WNj;aP z#OJx5yZ6D|wegsn!$d~Cv54m}XC4v_je|5YKJFST@#A0vM_)dNpN~v<3SUP-s0Qvi zC9{+?y27hBal=`Y9{FMe_6Q?X6Zeu5IvBK1gmA6?$C|g9gy(BDb027^Hs4Ae zHglgbNsDv=p|;V?eW99L*Lh8*YRZF%W}9m0Nmdc;GHK@asD_?o6=QN(HEC_|O%^7{ zR5J;rJd;0FL+3D5W%7?|=p2RznYfx%W9bn(1)@HatCi3x5RIAKprla;>D|rUtxPf# zhT<6_wbkaXG?VNE`W}5@MA_U`R`O6oDQR=pq86vcbvPR_H+LtQ$g_$0AT8Z(wMi46 zUEI>uU?S&cEM-l9CgDi^#1+@{375he>4V?Y#cnz`M&tP#v%(xEBOal&rklGl&%Zz&- zt?sFZapqn?POlfz*>%u(mY2fGIk9#sGIJhEQz`aE~^i)WdxR z16+G0G!K`wwl^Y8*e?cL(JuzL1FX>;)~A?%Q9|?8W=u{i;h32uG?ub=gzxRfq#zUQ za0BVX5`)-=E^5W@A7!l8;Z;k1~HW-=y*NJ)QVg1tT0 z;vR%GSxqUFkas`^x}!{FuWSa$AUCHOX^vPb>0u_a?KV9^cRmrdEGNz1luPL#kW&ShLE%2ex2Wi|%bBmRX2ca5Eb1Rrg{WL)iN4ejWQ2h`| zccofNZTPvw7+CE17f^4PURrOd?Nks9@FT zW!FB2oMX+Y7(%^ZtQ&*S)qjXm9#>%Co~PWUTXq@+{=awEAnPFo*9l zNsnws%cM2NvF;<$MD~GD>mBP>sOGOq@&sY5TdSI@(U(Q@wQBs`)>!u~lkje9tlOuW z-_hnJtshnMH;BY@LNzPf%Y1CC%Tdi&AoPT8th=b1nODo~Vyw&Cj_M~hG7p4CnXxWE zlk~`W^?qYrAtm|a;ML*T#=2q>D)2w!Ts+DlBmV5-YjLg$pz9Q#%GhmwgPYnhx?@-O-z z#WT%aQgUq->6O!5q$AZ>dc>b1JKZG-3EKR0_bijJo~OIDO8j^+!(Eh6{H7A+XNIc* zze979JTFNOXSl9P79e%eEMyY4gc)uL6Iq$0H|uA(?r7!?V$7KFd9-q!b9nGs`t$ z67IB|?Ve>a-3-K=9j_oiv)vLUmGB(kb&$F4l*Gf=VkyYmu2p9$X?R}KJ8qK_zn1cj zJKRMyeqG`ncW*Z#G*9>w@ho)5n1n6iJ$G++(u6&3k=v`ppBA;qz1f2_=@Eawv&g;6 zBrVwGwa6_|jo+EJ$gNO~-T_xqYhfo^6r)Ni}{a z&mwn1HGU`0B6mhL-qS8}7gggu?IIWHN#&B&p6a-+i`;l7;R@9vx2c!ZkFQn4+?$Bp z*)2e*#V(_dX#9Na19wATA-;com}_n4TZH;B*V+z|C0DF{ed6kHJn508m;=&jQJ=U5 zO8mD4KXEODaDPw56Ut9qPbQMXF(9A0g-pWhzRYb`;&Zs%B|JxYmYL@qgj()yP(m|L zA}d`9COGY~f#;+uQ;ht5E{|45VuBU1u)n%pPOE*9@esAGgH$^mDW8`Vqx`jfF zi&(P>C0*wVU=~Pi-s~0+u-+A6k{Y=Vggn4{7gbF$5b^-)U0Kys2O$rz-c?miW4(7+ z@9tMk8@+c}?~+ur6Hn;K+pKqwtL89>q}4(-j|KT@qng%1embefe+yu}>!lk1Er9iI zuxQMRB-{^SjEr=g)Fyn1uUsv)rlv z6i>K^9GU@As|!n5lOH$3Tir!Zv_)-o2?I$J&YHHmB22;=(N=ed5Z&W*(neb5 zHdmWTxGJ*EHB#bNMYg%N8j98ruSb5iyPix^`4ribOm?Uyy||2e+uZ@xybeO`XS<6I zl5)|S=Y1TitP&dIYBH&=gucl&0OVWOQwe>Oi^vYQK*{SB@y-p%PIo}bY9+f|!NHQ& z?)G?l2AbXOW+thTA3;86Qc^XAp7EM;s<{(HG}Tm7cAA{fxZBlJ(yXJr%dp#xS5n|M z*(bBxEmmUj^yvVnwOfhb6|>v@rKI$KnBlXg`Vh(U;flC(X-;{r!36Kefkc=*!Xzy+ z9_{%$CJj~d4oD#;$*Nh0d#0jH+NowgNJ%DLR5LtLdgX5SJd^auI~(yP7i)$oNqYp} zCt|Wx$!ki!bH6I_yDav&qAyS>q!+w`Pcol>qnE(P~*be~K2U-W&nJ?^*3 zTy~QW8dL_zCfrYYjgYKq9CPrreUIBdT|yPZ3gw%vDAdduqT#zpkiG6VC4A4wq}I$( zLrqRHD-16}v)|qOaTxOgCgWqs7fjZEA{r{iK_)kT8ix9v$wDTQA3C3Zzw5bFG==lv zE6!~>tR9ADWo9Y2z&_7EPrxc*AS7rtxrIXx(Z(sk#=a>6RAb6N=apqpWVV3GL%W1brSPU z81YXqd3Hk>bCnM8=B|H67|;DoM#YeanJkVW|)Yf3Afc;gz|h4 z;H=9tC)C-VqE!OXe~ zebph6Nk=6wfzY=yb6gK4osq+bS<_ERA7~mf8LDJpevI!-(v%EEsP0V0DcOQh1DQ-! z@*p%LnY_j%JHdbBJI5_lQt~`zM0qGbOPHias)D@1p;oG9NlE$YcaFUWN7ro?~sJIAH1qqZ6D)Xi~Ev*t*G->IA9x-yYD67{bfHz$i*X7B_d z$8BP=(Hz_^Cq?AA>RU;ZVQ6R7xMFcsJXXl zT%8|7%|X68d4CECee(}xXOb47Sscv)PP7TSL!oM05G5cgDpXrTUR;LAxmbSL#=WDiIS3g+-`8g#_yxXWT_5ndXiu@R?lC z@jp{q=@I%Ob77c&xoerEMuvb;&;85Y#3by?f4Q0j<%CGi1ylu3Go zP6T>@HI0=V2C2=Ym6DbxF?+ogryaWXN}dB586|RgNzb}|s_{Fx&bpyY(tCPCT{QD z&$=&_q=V4<&sn!g$*G~T!f?)Q*Ld;{!yPLw*g5yTYVNs&o!)tg9AT0gc?^WU%zMrq z*HBAvPc53$O1=US%>|98PH*fH?m?mQWTU`XOKAymJ(GM)(j)(%B~bmGb2l>@cZX^cL8x5kTt(G9kk^`P15X#R#?sq0(TRrb?I6@$Ikf(`&ug zTJJUPb=HuDZ%yRi$aX^tZ)W*~G{HB~%`AIj$XB4bl|^0VG-EG%5XkK;rDMqVMCu8_ zK8iJMhy;Dz?YI_qJMK&Bc9w1wDq_BPnEQ>}S^5gu4+L{@JIfFuNL^a;Xk$9A~W%r!EuaoEP$KBCpQ5PRT25`HB$F=_&-@pvLt&?aUIQsxdLn z9wArn#XT0q=?h=29riW&V!wz z&Tb;yPBE_HPWT_xa64tvSO~U)LeSq8cgFn78YJb3tR@n?Rae|;_aAC_%xzAZl|rzk zFu6*EV@Hn_cRJn-VrQ~i2*!mkRu^|#-(rpJH;OwuZwG|ay!j52p!b7NA^f-tdcWe% zQ6fP*E#aJvBbmp_63!LUaK7-XwuEydhSVE@BoxNDSc84S-A-mZcRS|)psD+R(2R-E zpvUfZX2p=Mke<7pHALVR0m$9;>vqLl~QBA67zKLoMh{moqmvp`t zja_Xn>6|9Qb-pjeUed{u1--$0Jd%jiB*Ol|8db)r?@;N4?W<~KoGwDfd;#}nL8vm$ zSR%pbkl^ef0{dyp@E)@SCsjySASn=Qf^$H~10DD`pakc8A)SHXZ$JsoX(9HdyaeYw zkt8!p+F*k7hiHl}fHnx}NpSuVQW;1?=m8U)EGoz;*24tnE+ViK47`4vLKPLV8OQ}9 z2{ECt1QVS4OlT&&8pf~$=VA=`(MPh<&FHZNXA_Z#>0F&hbMTLl{y=avPjJ2z@>dnU zbqf3=>`qE5-z~{4`|9eUlB<%jetBxh^ox+A{rf*kr$~zr}*maxo&U7Jm-KM;=Q;1!+DewF(#IDo(<`X+rEO4Wy7r za0a2gb0izui9|>9@=kR+Y8Q-y<(*kMNn^HDhW#ec+mv_q2-z#7g2Tu0BBAb0!lAZf zV9n0iO9%A0hrAEIQz|)UiA2m#6(L5@RC0>tX3KvHsqC~T!l7^A3(FAC>MI$QU8_JJW=0gc%xY>N`7yqzY-^+#tf<*an)0PDeVxm`uM0GFeL`68d8#{3cAA zl?6FprUJal0Hm=~k`6gW%vR{*@t)hJPFEpc3wgkqC8QX{j+$o99wF6*Jm}ma!s+=3 zEH`)Z7sj|aJxr<-iG*&HhZ+7es97mwdOmnTpeK0N=~#qPXnum&dw`~;GegL~LLPQf zgggYXqo$Q}Q%GMSt(|-5s3xapAZXe+Ers9=jmZEa5wi>G*xRI8DCD4!w$70lI~zgs zh%>Ah`w)LeJ4d8Y@qoC`LXYFDWMX=$sZZMo1?oe+l*v&iz_}rn6I>NW_HU zOCgEKAR&c>baCE}3H1tSx;mR#6KZt?<`P84l;rdv!QXTHI5&h`xC>52jfF3Nr%!3t zTm?-#ApM9ako9{4ZWCDj;19sj9&6CdR7=ph74|nR6 zMGcq5hoBkZEF%&LecKG~4eJXhR-ByWP?PT4mE=)QGa?Zafp+mp0(_@A6ND@h@|3ed z$Z;rl)Qom63b`s|j8m$75c@9>`_oQcAy^ZbbR-fncWT2Nd>m?K2q{p9$umw$jGc<0 zdDh8Sf%9IjLO6scxt?{}3Bljr8-eCI=XD|1uEI$#AQPPBLcSL=$=S%neESfevYG7c z7EaPf*-4X7XF-E!Y9>3mD{}0A0>LviFE|~ATkodBxc#q_U7#onmy%nZMDnpP%V;BoZ<6p;h9`&NH1ELRJZx<)jGd z2kAl0Z0DwsXNAmhYF6QVErM@q)V$^l6S7{&TxW@pk1xYJT9Crmoz+Z2`%1#xhDesG zXotV-kj!(65s8G3ie{cOK}bWW;pw1x!`To+{s6Mj`8@ssn^y=^UXW=MnREH@JBc;#%pP5V97?b|UA5^!N?d zE`Yr6ToN)G$X+5hn1qgO;?;u>oVj##Jrb&0%952tIE8m0)CW$-np_r`9we)r#kJUD zoi4%%GahzYI!A@f5VFR}aUW`euj3EnzF|Lf3X_I=Ef-d1KXl3o!MN@M@{x0|5d2y% z1!S$$SO~7zR|B%nX(I$rh%^B5vC~}$o)BpTWW6&`2)=II4af#(j1YX?cp#9CPLhx} z`@mUXAe)?5h4f7hhmwJO;w%u-?gjYb0J7OxDWq&)*bxL|i?dNk(i3oI1jwh+ul{ex z^NxkBQS#SonMwJnnaP!xE!i!WaYWnF4_;Ds_6lj-AHG2#uC2~dAx&T6P}`i6booGX z=&5Hc$-JJh%^4^fw2W_RZ;LChZO-#Cnxha`M)D((c*}3ZgmR#sZF4NaQf3k#>Vue2 zxK8rF(zE^lp!xRypgA9-;ar63a1C!XmA_oJIYvlpAo%67&B-Ig{_3&aDJ;b9e%$Vq z7Gm#7-r-adV(&@b;nXA&tTlb+%o1YvLw*)l7CW7A-Ha*R>G(oy3U@kriKIvGN#5lY z5sgjZE~kuWaEBz`a<$v3EE?P)iT4}tc4~^IGRz|u!+7zzQ(rW7fYc5nc~CU=F69)b zjcDv$$|+7~A@`N#+i1UVdI@O+1aG7L!Wk%b>|M&K&Ir-iyOdL%v7)i>wtwk#t%u$S zR_DHq^Vl9|+Wn{rdZa!7Lo=5&+#|J}1b6c~ONm5GQcY+(L{|ue8-#>T!psZE zSI#yeIe|P)EX{dJ?0f*f0Hd8WXS`_aX@WE-Su`yqt~6(cXzVWcG-s}8 z4klPTZ;NJ4IZkt$vz$n>*$0H<`cO1>|9qOWOGuthd^c~Jb6kjBRZDaJ5MobGr8&79 zqJJXMSHIJooqLkKn^;$i3Dp_-#EqSq7|+QsQJbjMud0o zuP1VrNbu{@A*W_zOiyrXl_165*5$g!szDoHQZ1 zPr>K`n&VD|2hcL7ITgqWrx%g*@EsW64-y$kgntpkd*e?yV?~1}?|vsu=3f1jQ>qzS z4%*DhZ{Ds&q>gCt=I#1Knh*(YI6vh)E1?ELU)PZ| z&r2x0JG}>yX%foTjZ;oqbIe81vjMpufc$=n^TQvVv=*r074p_eNLsW+!anQ^Kw1pPl<2Cd*-5A%6`tXPwqDq`QG{Z)ZG_V9h+;nIal{<9fQ&y%pLC$}~N$ywaT~ zNW6p0HcT`#b}-*GB-w(Ok|@FoDIHBB=+R*G9nja?Cd1cq;(eZ2F@4{ z5XoFK)13jL!THZo(#(t@oaQw|A|_AiaOehUHVeUTMQNSNEe*7L}29&S~r&I1?Qj;Y~9a*<=-6h2*nk$ zt^1Pm01lR$IX8ApWM@-!kZ6A9Y#W#=^_L785TE7Qx)JEY+<{g~{0OeAPumz__E zOo{FTx$Jx{8v924@6JBa*f-*TcjS#izsOLSSxkXD_nhQi;EYhJU*;DWU5Ol_P^rFc zDOa2mLaYz3IOjPo>LCw6s6U*1?a)7|;oloUj|Jo}r#g|~Rhp~L2+`n9s6U}+yXL$Q zqd5zqt~qmv1i$|O?R-Ea+2F4b;X<&(#Q9jrG9ZI}BwL9Dt?9b+ITJtS5hy*deBIg0 zBs{7gq?gEHA`yB42X8{S;rt*3Ppzgver`Bth4g(0)(>BVeHYF}A;W+SB65RBsSRelDwCF%JihTGn*!jCiM+;%EFN|r-57q^|( zLhR|qJI<#>*z@0jZ;qUiKft1iJhq| zqAApBwGf*^t!j4R^0H-WR0krQ=Jt?gUkw(GO|!4Y2(fAQ)hQwobAAb|*-)q}La_IH zk4WLJR63MG9KW)v>P(_LQM0PE-B1$=O}zj+rlz81Lw5=lJ&BV|oh1@6rKhuhvZ;$i zl1x<~Sf<(3HPP5|&!%pP#+G|F<@KPrOqH(i>k7n`P30t#WNHJcLZlFppp>(z)jcs( zsPRF= zb3rvmh^>c()Yn9~_I?97Eu_wh#@60K>Y@pn>~jEuu@_cF zM6&|w#`tMSN{ePAkR&3NMKkGRYv*3k%mBi6>WilDdaG$Bnh`))(?&G-t_z-HEv!0; zCY$2@euY&p(VRQLD;0&+6QcPC2(DBVRwIc7wYRWZ+&^Op7LL|c+mjTD5?gzVDD`+y z7I+VG5jBX2_!&qkl}03}tEJQtA+~f%sj*L>ouCgdr8dWr%)NRkl|mYs)52H)mWALP z5=Vzp>MW5Ur=?Waff;jJO5G$9F;C28&zDwe5Ncqz1`zaoX_bRW#7u~4@{49>R8xXA z)Z%tz$50JeL!=tim;^O|NKjW3)D$7s8)elwA+~0gRhNX=nprlkRhCm(2UA&?0*$$S zl~Z|%B$={6Fc;-iF(QFC%Bev@Z2Kyw&a)j#GlnX!LPN-7(eb^4svyLsxuQxU!sXru za#~T%7mdwDMYULn%|*qyTvSpwNE0#dHRSnEB^4UV`T7`0H?UkuWhWBkqOuww#O9*1 zI?JIb7Z@s0-DFKP7gdzZA*17aRaI;l=hWt_s%lMyd&qHMr>g2N8k?_bY8aDf4_Qs! z5RJ`gHI?s4&c#WXp<(Y=T@@peWG=~^t-7i}B*<5FHAaZdS9Nuj?LWHy-ni1Ksg_bG9%ViOJ2llRCX^l=yKAa%V#s#V z{1iiQ)?G_oCc<9DHHX^jW(>g@b)C4_>#AlWGUl|d+QTF|lc}fRB0fk(FbdaG6_`Xv zqWe{KBI$myRdC;@1AE`qAR*O+)K`mzJYADD4b)K~GlVo$xkpi4VcdJ!9_%zyvxJQ5 z4li&4X{_w6(Z^wR1<$86iIOI;M+ieTReSkcCS3d=ua7+t)!5x@52*QMc}nO(NMSaJ z?*a9pkQI;~B+XO`k$IsRZQy=3AP=f@tcjLR3-vc^=-$*4plP9Q6N#83pF{sd#CrhsR8|*x$ zW{RD?@ACa5z1317b_D6I)(Uxl1MKU08Sc+gDMDOWnS2F2)?0le#Pr3pAV^LLxe+B7 ziKO~3G~znnTh$pydH1nCw@fa7sH-Yu}3Dsf( z#U6@O;MzM-t$rQ}zj1(P7g=+?AM|Q7kX(%*2~JxKRtu*hNe$b!Ggz5vNK(SK zT?|$!LTtMjtR}p~cCc0Az43$9Vj@%E=HM{h+zz2)GMu}@HH4#$Nxp(a?Kpwfuok^qk&6I7qqD5oJiGEP)a65+K< z{FT9yGhzH?za`k2sGcVc*VRXfyg?-RzL*%dMl?|^B@KT~V0lectBJt*ILP~Xva>FqN!Rq99mEaHCIH_3<&lK)6_qrX?_p)Y|~VD zE|rey4kQIqJ56OJ!sBuYvYf~y>SAhMoWT--u?0njsqOX>t5|MXe#ib4aXFud1DFIeeoyp1DC%>~+jV zdiWBMyMWA66NqrANs#6_Hb2nQw;39rn(4O52Q4a zK1ARKFi10wAam7lwqtC*=Bj6e*nG`Z&r2vfPk3ET6=LTJud7)?>^xzfnlHr86XvNU zLhL-@4fVbdJ5P8+eJsSz6XvT=tsR*s%vYZa8So*G%WtX^LMAV;^pRCs>(v_J9KZVnnLWGcWa}`bb|gFXEF;^8zJ`Hw*{)R5c}@iLe)!% zefMpl8blDJ#`KqA36bzvEV_EDJHG>HZ5RSo6NhN(An;27;RR)m=ht34Wl8 z3b7^lfl43}l;A4W_iePy{SUVH)oK9|_VZZC#cFk&NU+wlTAdYQZ~R-WS}fwSu%*04 zEhNIFJPYisQAdSfDI@tvU6oL_l-J2>*ctzk!SMYGUR@{s*ctzaUi{MHI@S3dN^|rb zmUXI^khG>eGhe3$3bCd9u^J)7mh#7HERnz)>(w3BMCt@&?7O9#)JsC_yQQC~ z*+T5QrJty`gp8D$xmmp{c&phnnIoN=QiaYV5jOR`Y4@hcoP9}2Pic6O;WA$H%+F7>St`z5toofKl%;&!WaA@x?c$H2x<@IwMRW5WId2BL|O}J313OL(*Bj|D5M(@wDXmE zOvv;Dtl6s`7xF$3)a+Hmg}86S*Xm=iJ4+=A$qS?x#I;W?W)iKdX=+al!5Lbbn!KF- zGx{s&)gjc^YMPKKKt>XoEu>vnp6TsZZ!!rj+6^;z(Ck+mhy-^(?pGt*|0xKg+E|BSX&Aa$tuLwmP0D9 z5L;UgsiHz29Rll75bCfhBV+)ON<{20vsaImAJhgRxQl5qk<7oaoKV|E^T<)I2`AJSM3T$^AXpPlsQse(4@!9t*g2uT z6HQjwQ?Z}OkD{^j!xQSNkiS0R9i%6eT1B;mO7L3>)qn{1v?qa_QvHNrPmAQVnjxes z%vX{8q|$`66mnK&UyYXMnez)-ldjqcIV9w~noA^@4PH<`39&u@Z|W+M2;KIKE#WtH za1Et7dKdRamHR^^!MDRj)s@J+P+{sM{MWAN8n2e$YnKo zEs|hA$rZJh$h^>l&%inyX#P;u*RkgF8sX3&sE2>4xkBt6$bYHJMCO^HU~M>rx~8rR zX&)tU5i$J#Jk!EhjV}a-6A5*f5EIoD7g92+DKF&tD5);wva)vS3h4){lANBVLVk*p zRzkW&`V56EZs**E>QE zM9E4ad!m*<7V=Is_N_wTV@uMLBBW20d@W>Jlzc}7_8UXpz!Bt{Iwjaj{8j`^=q%e?w)C?l&;r0*nD)CLV zlkL#n3>@Qbsyr3IoXkM#K&YE4+Xjx^ezD$EcL}jytT$CLA@nwj{BMrJI7Y;0wH#et=wfo>>S&5R|~Oo zY}ef=#Llrjce@Zf$M)P*A-^``Z))xC7xE_%OtW^s7h^t(l zdqs$SN8We;5n|te&+3LZa!t4jwHNJVb+ZbwFT!VY^9rdfnrv=SA@*xDyIV$x{Tj{g zCJM1{yXSCg3bAjy=WrVcv2VNQbRQIA-*(UGwzZ*}a^7>forPQif@#j>_7-B_cF*k& z6k^|Y&+U#BV&8VpuA5c{@!es`M?`?h<2_X{ERZTAB1*Fx;u?giZMgxI&;3%aL-*tgva zy1xji46Eqa1`D~r3#kbNeOSo7A!I!KCXAZGZfFzNjcGtoQ`q%|*p-1IZXO|aWuS;# zgh;aa8Af}YnHP0S3;7EO_DDtD%0&2EuLJZ=Mcq1Vhx)pP&_@+@6E}2h2TqGg^8RHf-iZYrlfmT2)^Wnnv!uTEahHe zO*Dn2+#;zkJxV?!a8@rB_;0}^dw&fPhID&%e;J7MxtKF-e-+|{fhKW~DvsNh~C z63p2WUAf&Wy0(+(w)+e{AFS;pxshp)jcaD7ff8REmw{6 zd^PtHYsm9cAn(=OVmr~Rf#++v8<@~)2DZ3b?q(vvTgJ8AokW5fUd#PTG}b@0+;2o< z{ZlJ0J+<899E#FY@=c8E29Y2=b=~f}Foh9g*GcNS{fNA1>^ezZcc^Im-ux9=&mB!9 z$>arsUy=3P2}FWlX6v~TBK%7P_DJ>I#YBSG>$~CIbr_au!2|L%`U{wF6z7a zh1l6e1Gl&kJG*G$mJ?!U7Y*I2LhS6Kp<7#son16?8ws(qi$-n>A$A3?vD=PFvau_8 zjoogdu`76u-M*r+D|n6LN~f_qghNs3?1kSR8oR5B1paC6@(!+Govpds>~r*3&`z7X zy@kwe%I&MUn|YnBxjRracAc%cJ3@$EXKU_GATrmiAI&!uLF`0=(+$ntmx%+$E1jmhuv;OUNASM2WaK?C6Z+Pd$|W_xl9x3D-1^@!c^ovD zo{wm9q=P- zmP1Za^SYZNWa}DE&pfx;Y1VuWdjU{0-(4o8AEdbh#Py~-_6%z#fgOx}kz4F1u?(U5 z19{h-Cge9r50a(sRUuiPU~epQtN+ZQwo00pxwC{U0?X*Jtoa4VbRg^9i$c;M%@}HvJL4DDY&*ps+v4^) z&!jKp9do+FRlhR%vn2Qr$Zj`H$WgGo49J&my9=zjCl8ao?hzpeL4$F9?T-14HLHGu zU*~`vbpN@?1n(5t2jqlnE(PQmkkf7BBM1o&W(%mLP?7pvbweDajip=1mrJwijbU(q4fi~>W2Pi4U&~W{&A}d$vTHE|LgW7 z66`4Z*UfhwL#2o9jWg_H)e56B(&rjUo{!LK4fvUnYDaO`=6 zxZY|Z4S?WICgZvPpk_)a469hUFE^W4M#x^My%#}~!)q)gUwz0WkeuGbLUzD6K9XEs zCn5D={|1uWULPT~*Rm#$H$=!r_*zFzUT=($`(WQPlDoV~LPF25CZ9J=$RJqNM@@cj zj*uw}m=y393aJ4#3N;126+*BVMN-IHC*(Supg~gD+a_d)q`8RqrI03LIaE>akdSbH z_~M5$E9PA#5(!;v275|#!hKa<%YV^_!CvALp56OB5NhUah^vIxlQfagy63o0xZ9gg zWJ>5(6Zo}jRVY-_EA}6k#Z@5*-eMx$kM#yk1+VK(F4HA{zzDn=NuS$HcAtk)k;pb7 zFE)p_D}hw5aD04&XMLS zk-$Ioyk2nCHhQC|q`98=gpgWMGMoun#?w{xyyuDV{Tn%;U#RabBNF_I*T72?Vt>VJ z=>5%hjD2^Yp?8}|@a{lEPiN)yya9K(;TmH@FBg#{^Dz({tr~iTL}TX?4ZV`0v2%%r zUPU5&nxF`zu%TCnNDAHXiMPWw^iHsbeow(!Tw||zHjF*+Ph+nF5pF3DfStx(gb3VQ z2!G*j@W$S&tTE_`he`7WkreaJYdj7%_7)LIH7`ZUaw5U40*$?GL_Vi;6Ig)kA*Fy|JAKr9TL_*q zJ_G6L=q(q5r;L9g@}ZELop{#N$=f8PF%YbWoxIP8aF2Z%LUs185((A{x_c9HQjLnP z6?FHCO@Hq$A?1#-#|C&sh13Ot9vk2#Frj>*#~$~dvznFgy#qNN>`f7Z=Q!p78S2eq z62iF_YKD79h2VVbT_7X9TSRyy!WuQ&tCok-932@)dmV`cJ{;{G6k>fi+Uu8>L%jrP z-T{`!coSksDv)QqgF?E12J=4FOTaD8FbY2pbBcEJ1y$Yf9FrxcoeK*|xxEo6B+ zt{X{SAt4)qV7`*PQcS4at3aq1yw;+z?LFdkB$5=ppCsZvCYtBPa>RRFG_Q!|h&Nm` zKfo*oTT{e)Ml`zjsfXTB!xBPjX^}J6kxBOss($9fV|?>7ozWj zxjgiwuXtmG6czHSw^~S^daRl0ofT46$SkjLL9`r?rfPcZBVPT67MF?gh;}FLxn~om*vnAa8iBiNHPovf}!N z*OADS=!)wb-eaPfI2=xLL8$rO<3y6oTp+!P3>S^9Kl8n3#Eu;==6lbJ#;yRr=}i@l zT>*a6n7I=^;_%FpBIvsYQ!d&7VFY`&0B_2H6 z0h)-J0^ax;)=HLmhDefm6UYmtkmMnfY-~=Kc!fn{bGpPUEt;iscrLNTt0bBYKyc+` ziFdDP^7gWt`$ba*2y2>&rtM=^(^@pRw}mxbiEvGr3wdASttJx8>y~>pi%^XU*?HXx zFNFy2!d(ZUR(KbMyaKz$kgW9b6~$2VLWf?3^&uc1c=L$_bFEcg2|VBtvm(7JZuY*) zt3(>EQP;uFDz7Gyh&fdmW)>eK87Abikk#HYA;Y14p=OPDLdawxAA0$Ub9tSH_Jx{{ zyw*bg6tdQqdk#CVTPGwnhn?50^O7Xg`6aOD0>0tbd2@*bUv}%f8$>>L@B~%|$n!ey zG(0te-%UtXLG0FYifh|fK(Gz|4VsU=i+5wFDWPBCroJ9fGdFpsOOg-C&MT#%zw=s` zLK6I5@Tqs22*0G~Prc8Gq=a7wA2K;68nlCC zt9Lafl(Qbmtb`ygCb`Q7mRnG$W-$b7=2mY&dDP6NJ7-!FSxjVJ=!-efJ3>yk#?_YX zUdsv?ifcv8UL+@J^BC;HJJ*FP-Cx z-qe@s{Vp1O|J~mRHOadnn&Uw76G^OuDU6s8x^dr<>eUpo6$s`u)oV;7n0IWXX199#5O4dLUtdAY^Iul7Ui9NZz4tk@BB%9jueR|NFMkMI94tj?<6!k57 z6Z{t7T_qCib-tch_2d)g0rH;DxI103>t)nd)}9pFZKSOz=fO(4Q2cJYUTL*5M{ z+(-3?xDI)F?!!>f&rjfbc*rYEB*|O>g7xr_S6Vc~qMAxXl1(J4xmPq#H3)~ECCm4V zofm*$z7BcKSVJTJL?W5j6OMR&NE56l9P!Q*;ojzL2=!gudct>J#oCk$vWzwAJFf;4 zgLA)46sob1@>RGMeCLf7(pbp%-dZ6zKg3pW)H^Q(=Z8p+c_r(x$8dg#QZ`W ze)uKi^rSaAhWrZTjJGz1{0HPG?+_8L3F;GQtKLn~bb)yklh*av^2mzZn$CK|gv|Nd zl4AFxCOAib*2`C)iCxb;>peguSUEZC^%8<(?r4bpoHvX}(3*brt`OmJp9Pv&At+e%=!9=l6r1i_u;^PV$2MG4zSG__+g4TV_8zaQ-gufn_uj}5o zVrR$ae9y^s?1n7|p0r|&!K8BP9@~=08 zNO0QgKW`?Hh_N?0|L46S#NOompSM`Zf~Ncy7lc(9}$dvoYRLTv5L5mz^I>K|A`-gp91m{aFyiY3Uc8&68+(v67(bv2ic-RFt9 z$fXyOCTN4X^fDnf7rFErAvPDe^+q8!7rFHgA^oHt=FzD_hD$xnqYnsq{8=vLy!v|~ zV}W2P=Z(whUHUX@D5ql}r+4YQ9|&@Kts%VBrTY*Ga+*&UXoi}gx5=lA5Q!LD5A*5L zLTo+Erz;Dw^)SD_SBR~L`E`9F!JMstUM9ruzAm6c4`S>=iz}pkB0+iz=^U(y-uGTe z=NIzryL{HQkS-x4Taz%{y9TR@x}1=LK=3uR!n&G}s?B(Hu86KJq&^T_c`KqD3wikg z))dt(guDd=HAQuMA-P)M-f&o@(%pm<0#XWW71RBMy!sfg@)g%Zge(Apc8crKLiUv9 z7FR-#7xFz2Y;h&@6eiKOe7D{iLn2VhckAlS$%piM_-0szDXHrS!Pmpj5oy9CYNwR$ zLWD=F6=0{7j@=25b-uJ7A$C4^3i^zDkvuD83y_@qseO+0-lSnYamet2&$U9w8v+^g3 zE86py)3?Nq?SIN?_aXL}ZMo%iP9d{M~X7xEX7 zi$tmm!FLS*22xSi5mEz4cngvyLSBJ8aB={tq+1Gk8%RMSna8-wx+80-1S^B4vffN2 zXv>Lu{hEwu#!=0sgjG~W$|-a-F7$)50>lbB0_cm!S@I2=rTg~OJ7%4R~B+w`ntNhCXpZ)_2P0-Pj}={l&=F2 zS3NyWLfKs0uO|z!xwu~!=$J7V_v^EHAn7q9+Woo`X(I5;KIi>@T|>xjAlSO^*Y`7t zdbPgp3B7Qvo%*^NX;O_%Pkr4=h)qv@-GNBp)%tpOCsu;?)j%&7%Qig?^ggj;Yg7Y$Sct7r4fKgjmK*By7=kskq0ZKc z%AH~_4e4p5OA`rh(rBzJF^Sg9#=0Sgik_rsta}Qvt+KIxT!=kK-$Xwt#E#}o^duoG z%X4`()eD7e0)pk$R4*4|NBjr$8XmQMt?r!Whx8vpwgJI3KcxQ^va1YhTIejDxqW>D1T`&m zJ|T%C@s<&o-{?g`b~okNTj~`;(tu#>E%ir2-htT%{_^;+{zS-HAgFm*?-cS?MV^zi z(tCs)1A>}X`Wqp|9)jIIV5hY{CL|HaKz{;d@5{5(Hu{2)gFtY0+D88+Bzr6V z1+%UGPe@@PDbTLkYSo4FZs$Lb=sl z^$sDXCVyo=s`m)NFT~D}o{qX_H_j=3A@(KGx+jugCeul8h#}YpJL!DA15F5i3+SxN z5efR1E>LFpUvRTo7hRn+kWh7m1fyP8z4~z`c4ppHCk{cv z>qz)sTX)@vNN|p$yUzUkMRz?JX4`h1WN&i})rvv|GyLwlQw-U&6*YZ`1fxR_Jwu4? z*?Q>LgxH?8hvqwV18?-uZ;8gvGkfSoL?WhOp>XJ1h^vQQA*3>p6GT1|vJG0?Ss*?2 zCqfPYxj-cInEROC$r|b*{{YQnI(ZjlJ!ahLrO%Nj7%zIo^}W4x_Mzx!_79eOFI_N( zU8BqP zV&?$;_2WY99H4((O&DNnW)3^*1;mzVKx~-~(8I;@v(VdM`x>C15%Llc>>+Ka6c=B< z#(B%*`gziv@l&FNfAu)yyRi0!Uw@Cst;syDr?O=#L3bN!-jTTG!p!|MAW!J`h$NYJ zfP6({t!T0ghuzmiHVerG0f-im!)Q6Z*>}YwAzATzNuo{hf zX$I@Rg(L#O)$hUjj@3K~YuFHKh}OfX{+Mb&ej$=u$P=x&j~c2A2^kLreK=H?5_07z z`+1nIDC9pNs2Qef2)UHTnkRKVA^!kD&6Ckm{^DVdYj~8{y)*$SeE`cK?L`12*Fqx2{t9c0(gC_PTdwf&saQ94P;EXnC8y^=`KACA%~ zF=QyjHA*KuiDjA^8q+l#`ZEXY4bu&XL_!l+!YVS6PNK29Bc9fe5lJx%;SFNkVe+&d zBxD5;++p&xP9ws-I@)9y7g?HKDuVI~PaBiMrTGw4CDGG*8qOh1fJt)b|nz%5;*hO@w2| z^i0y7V+j7Dk)-<&;WQryO_CnUcA{Q=L60F4c3 z+EIh=D`jTjDyO^f05s4Vv zE~e?*qOtAbCGCyoeA#yKlFmgW-M4Ldx-Lj0$$SlUem2B4U6&M%ZMoBR1tGTOPS-Vw zaG9=vP%rBlM1mbbuj@Nu!4&*eE^+K{f z1U)v9lSCrsGiVps^Uu`hgd75bJ^xI7nMjhUDJh(#vp!8V)3lWo&eDa61g&zGt{p=# zg|l>fBD`*cJIZG3F+_sd;B0+Fi0uJp>)g*^TaDOeA7P|HJ!IKGdOc7<1^hF}6(fP)4UF>qR_A4 z2zyE&5MuXHpVCK#;MuO85b8<F5~l{fPW3P*OYWF1y6~YssIHSIxOA_JnH2*_LhQ$!n zT+(A>2)5-*wr0BaTjsKm6xV(iT$VbY685LSdGoq(=2~~1jGfgc6$G*TfdBUQp5J^=1;wiNJ`kw-v8908LY9hoj-L2A}L`z2e_Ky zml14Vf5**0{?+CY4f2nkKxCeI5Y{Ryk>({L!JOn@ zJ(um6r&D1y5k|EC^d=$6K#qb(|I=v_%BJ~0o&6QeSMZ&3OBW^*%-L?~;zS}Qx2zT1 z(q)B|khOwaaXG!6A*T;Ons4i>9E$qG)vX%^F#26YS_H!BKA@kgVnjj-7r5#`4N$GS3*Znb`8e`kc+gme+8wlhX{z zl#86^sZ43kX(kd$GB3iZ=cgf5F2mojaEb)Tcp@_-lr5cHW-VKerZ7)j3iFr^q~RL% zD%i=BA%$-M$!Aj8PSnr&%zlZ>wy%8V7?C7%AFNR?f>8NQx{#JYmJzuop=`UzZ}Pp$ z^~a_;ziB}v$$aC5L!UvYg3+9&1KCTY6KR4vR?u`K5-}5(!l+1^zCz{!IYDHIkW#(i z3=5D#<|!fdf&5BjypS%UDQuF3JT02SW`>a8-{rW9n7Kmk0KvG5n74)0lJpcc%Y{57 z=_zVH6p~9c#mpulrA1RLuDpty&samP;0nZ5+?*xCJ-}@sC5=DR!5%4EUL{Q)B0(!C zX^M%)_DH47WYO5NC}WDvVmr2HD{ICQ3EF#E6MJtwwt}*8X)bFnQYfxtJ&vO$wiWaP zQo-DoxNIL@!FaPVc5cgqNplyGAQu%)2_i`*eL0U-l}u$JSApQjSjp6vP&Qwc%t#_Z z{Yf;L*LD)k7}9W>ac-Arc(sT9JQdPY%_Om%sGqBu84{Om7u8If5L@S~o04-dU%}U3 zb+eX8FlVb_GXLsP!)zlBk5*6ggVjkByVnstUn9=*HR3#9BZKG9KrU*TFUfLHTWV$S z{CU!3_WXTup06F}`P$}SrkvI`>NU)J#B7C?Lfp|>*JKxR5C{ygK=KR0(~HzU$0 z@bn_K(|V?y5IiY~n)^*vA$U>}HTT2re~t@JN}{H|X(R+sN}{H|X+b2Y2@OnZB1r~M zH2)5HZxHQ&)MB{Xm`FFaLv0Y}Bn`~`7~=kbK0NhGMxP0V;9 zwzf1i(}dXC+tjRKJJDXfnOR2!UhR*r zs6Ed!o0}hmj01wLsku2TWacm2;vO;=g)9exnup@*VGDDOHRQ2U;EfhWzmDGE)>H{d zE7P1v;Gb5eEfMYk?jcPNB7w(Riw5qUJI^;xv@uVxWm5`^lDGDa6aeVo=xs)mI*oh2lo;^&8JMFZSXOZ5<`wanjbUy-b6cm zTV^_tJ|>Y!kkdY*J5a}MecexE*WrU>~H2(GU_Zr&17eGBZ(JORHU znsM0uqSIik?Io4*3406nGoywQ6^y_ra2}2<`Vct6+(?Rv3pao9*&M{O`}a^3dOU6 zKPXgVBJ9I|fIMru5D9Yetm#dJ>saU{YK9RB@-_j34fA21I3RWIXJ|XyfcL^eyYgAH(oIU`fc~XX) zmW5DLOmU7YIzyWh_dPfz!#86hgnH4$?(aoEM@(5xPqa0`tE(~Oeh3vYJ&ABlXbR*d zGcZ%^FPRZUxF)nD%>*JrO_*+`39-G+%jO1=B$HB&&jHOa?jr7Oz5#->rWqz5kziIi z!?Yk0`1#ei+VZMtO&YGxT_HWMX7FKeAhS(BwiET??6}%HJFfQ5iL1SH%#cj!nG=_u zIc9iFdY*vT=a^|k*sCLeyly_vl%ChkJ|ck+UpL=zD4G>K1EJ=bpM~J8U;>dV63W)h zd8W!c8EfWza}N>r56<-FoBLzPTVQ9t8AXIsxCF=oGlA_yJ+{D1A;RUpk~Fi41ZiGq z77DTD{5|sO5(?>M6uUl*evK^|= z*!M0rPjRSdnirerhy;0GY^KBz?86tE*-Q-1u@hj8WT|;m2+px968T67&ec(~%zPpQ z=jy0gX1*4JbAHq;H{S}uIX`Nao8N?V{g`{m73MD?PXNIla)t4ipvRzfmgL##dnS*N z2Z7*x?7g^Jx6%}04S6FC<^U^AA`u>&egLw{Bohg|x;oCz>I^*+uJ)|X(3%>ZhF`MG zRJIfK^GAmF+y*t_BQrBoTx;XvTALxR=3sekhPWO9vfjLwDX#Tq0g)iC^=1W;BwF|D z452mzEf?1PaBtWKvtB~kUVVevDa5v&4dyJ7z(1SJc_I;Gzc@CTD?;oS$0qZS5c|dP zi3z{UC1}4mJ~3H^G=}*#&apR}yh6IbUs$G_O;I7IJ=Sb7WrSP@f|@OHKKwL;4+lZs zKQ)OIirexiAluDQBEe|B-Hax}ZFwwdCKCy~x6XlR|5Tkc$l>+=kC<82_RCg(D?Y}Z>-O#vdD zuVthuLnO%8m!e5BZ4bjQz@XU^tu1|ltS3@kLfL%nF%yXJ>1kY7-)q(p;S?T(Q2Wer zCZ zu^%$Ar_Zni51A$s>M!UIaaH7yX(_~qUKAV#q=S$~Ku~kobQjV|G>1)pAx+>bDcc$N z#n2438X$RzWS&QTYsRpKdcPu|`PRHegj+!wAV*Cak)RbEHAjeWZK*_>Gem;ga?D&5 zVn?*&Ci_bCPw-9jgXt>7)|MYk8j&RPKqI)#cMAN9XRZr*6iBz1kht%oouIcl8P^I< zW@rVSAcZH5PocQh^#XF*RAM{P-uR5EK_tmM3ZwMnpg9w*;X{B7CDMpAK~B$@)0q4}_>n9WYCIy!JSyO*C*Op=TbD5qs&4j!F1k3%bX(Qyh`SANQ#D30n5;7CW zNg}<3d<0*ASm)Et6GHX?!8)HF=bvB92-Z-!UjRG5m^nna+^+++|oXDr+cT5zzc@9w5T`dIQK`rU#KAUw@hYM7TaLBF#u5K~Arl z2}FW^?3#p1GV4~tEk_XQ?`Un=1>_SVH^q+i&);Uo+Km4BJ8r%AhWUduyx!XvR_Jb+ zuXllaV*GO>u1s%asPpL%`;81e|L;KlGkP8QIa=rcGr5Q)nNgP@2c#(`BpJv}B4vpL z-uTbd5n^ljO%r?07i;+KxD?*bkiuL)qbByZfI>jR{u5+5D4noBJX7ppe;kn{^K6N5 z=xzv=#kaqRO#xDt$h1r;%;Lx1dw?la{ui{E3lX)E=t7w9CfNarx*>!+ye(d>o%tbao^sxhVBWNkv zd?UoRlx%+HTx9o4uqN74vimh-$U;bAZvQDFoUb)N^7+$=1o_J6$F`jfq{-ZN^80fs zRM2(`NT{Ig6pZH7ww;20vGtfQ81Z2oz}2vV{$?SqfZ#}7(7!<>7*h)Rxi?^_;G3wR zpZWKc!hUno1nsNv|IqY|(>z5a=%Whz&k%{w3yDK3z!`9Vk`R0$@dxlm;W*D1@#nIJ zJbxNAMf?>+*w4QJDemte!adSuBKwF0o-g4aCK9yD68>o+cFet7>?E0L@Xd%REa?~A z=#W<%0>KoP^ve+m@>Mb}%_aRxq~Xys-&qXRi0x1=5`ZN5o!L&bZ%OcbvBnhZ77o=S zjqO{i18G8JC~1ORB=}>6*gmtYpL-LgIVjVze(YIAYz5{0G|~jC@#Xy^LhNdM`M7*l z@MCAoxCTrZMjOchTFDW#g9GJi@B&0m%=I; zQn(RfuaY5!JAhR8&t*zab^j6(u0LOp=3gR#pYQSAPq-%7T^}|43PS9zj~f0kB7x^? z_^XB3(y8fhArjP|n*MeoL0Q!FzYt=}qGnu9Yh}nOmPM@$IXw!wsN?VFxT1BePKMI? zku;ghqHbJS)Qc;Ndj5Bra#1fXh4nI|FdgEmmm!6}18LwN&y>Oj{!c^#&o_umPs6zM zH1w}XC_BP7^z(1#@+x)`#$|}BaWqsdAX&~KDNUN7EE>nsK zPBlY-;BLd#{$oUfoVM`?39&hC>nCi-P(izB>wixq@O(S}1d)i@_YdqyOox-Ce&(G9 z?fjlQ&<=YZcN(Jvr)nRC=-JB zvKsBiG^a*ycJ1qTAu`Vl=p7DqgxLG~neVyn3w!7@?hUhq_lEU>Q2j;2cTo-jf-hfT zsMIj-4J!zL4fgv`TwJDj8h40)lt{XNsSM9#hWe*jW9&#J$tfdWNal=h>{e8o-9)dP4=aR zWGOL~3R%YF%cM~XMaU9Gl%*ld@3WnIpP8rM@AaEMdeytnIrrRi&pr3tbI-kx*=`4w zC(gYe?pAMsr<3OB|Do`?^Bv?o+*}+bSf1f#t_XdRm88?oAQe7A7;erN!7uflq?oq!*eJ8Rh;Cz}%w!hoNwDUhH-}5i zTf5-3oB=ank>dgVE-4l1Qj^C78d5d|ot< zh;$VhV;0}db@*jXj(N%KB(hUvtT|catJ)m%vbkAgx5z8zC6Rb|uWbu#Yn*xOw{($%+TnDYt|EFsHU5zLw83laRyAQqhu{$>zM{&RVnxixBA574$^W4?=u856?t z93tVXOeWO$G&7fkzdeh`bedUrAJ*8>xJTOX3Z-f0^8G;_a?Ey;qvqv$aYi#Zo2Hqo zt3x74rN1UR&D1fw>%;rP7RKaosyN)!mQ?0C2Y6e!r?ns^)oer(37KW!ZCr?X-E2jY z5-I|cMAAuOHf`f8q%p`-B>8LX4Rf-@9Igi~ z7h>Kp8~=nQOfyhE2bpee7TFDQ0%V3+_aMg%5SeMNIm9wbWR`jU7Zm;lQcU%>Ip{D8 zwhfl?B3OqknIc$YEb&JuRq;n)o~?T!%qRa|ylvh_G29Ld+>0?4MQ~dz%|vipEJ-90 zf6Vr_IY2~@+1@sv7AaSRYd*~!B~k?hYd+0~cvGu?; zSAE)xa}K0Gq`;Y+KrC3-omXnTpFif~W%;EJ+$rQurD9TTi%GRGMj|oNNmfw?C9Ey= z6G-bA=@}y(3&t5G{)pbwpBHi+`uW6Hg!=;gJ%vCnw}P+PMW>3-`4FCxgUm7Cq?$iz z>RO%Wi>}pqW}3w4TAgPu5Ye?d&rBE5wK~sSDWYq2p1D>;*Xlg;a}iyu^UTd6x>o0z zUyJBkooDVA(X~3y+_?qY#>qIn?}6xjpJ(!SPEN+@eV?yWnfh2P^hFj$$^k_&va~nDQ($vnhsqfIlcz)zg?ygjVIn6^B&~W?d3^-+U0> zf1osxm@XN7ro3-HEHO`k;Mx7Y*_njL*#AP#@0&wO_IRZJaFgHoe zh=e%9{0r7pm{}sPfOsU|is-GcHus6>t*-vj#4JkLT~znfyh zzf(Pvln43POpKBPAnVM=BqvR*pI=E@lcX8T=fZO>lCdJQrol5AkWb7HM20;H&m8`S zXGP|hB5#ytS#N$LQVD7TV?H;HKd?M$#w*)l)dS@9h1o7jN<+*BGg)M^#B4HOjS`I6 zY|a;X5^}ByvejHKa!_QO`Mt=)4PZ4r#AKSkimc0lFDij#nU_V*%!04xfb1~UnV`mS zOtRBVBnieOJI%@>dQ7s@ta27p9W{r+2n4@5ywe|n;5Du@W)y?>JvTwjUURd^->`=KEXa>$!rz#R$0TtV;r(25^aU2I!%YvPEG0=ZKEFTC zsNL1h7W?hmrBXa|Mdjn#AH*<2i%=MGudIECBJQF1=LC%`tD;(1WVo?4x ztBIseh4T;OoY{mVsOR%B^?crJPcdB2T`t4huV(bS_Q@pOBnI10-WU{)=|eF=t)7pm z)$`_9iV14UYtXj}S@L!a;G&8rflYxTSty2f>gwfZlmYAd48_rJ_8 zBx%O9j_~V||G*5dnIVEJon8dFXm+{Ib%^pN$R%^R$m`AFccMTpn~&e%nDz_c8VK@_ zIV?(cf?P3IiTv;?tW*K{H>RGioBJt->lx=Nubby3)%q8pSIpv6PwSAQ8AnRscB^$kj@b*TMt~HxhKby>8`kZD6t|9x zJUt0|VvxJ7`SF};#Wi?y5ypR|tn5O8i~zaM%D9!K4z#HGAZ4w_A(r={_uB+g-Wn?M zoJd7$l?eV8oe|jysZeTJ4?cJ2nLYqb+;G9B*SK_0aBi)@_*V+oLYR;JCVhQMB6s`}O$5+3Cq0%>41bTB69 zTN=c)4G@aqwow>rt%0Tc7Hk{cpiMWhI!DFaNimO!U`ztZ^6fB4g&9sf-#Jx3iV1p~ z2G)3zV06*IIwhh<7Y(db7t0*{T1o@!0};#_M;8sOuSt%Y9UtQFp*65{kA!_o-Wc5@ zHL!9xRUFQEG@>%!?%}qM#*LfBBanvH-6Svq$$~GWLQEs8oX8ugEKRIxB1=!RG_&f9 zWF*2IHKb}`Jr*U&Ag!&zA~1(|ZB@N+uz(8xZNX8S;-fzN} z46XD#I8~+RAqB`_>l#VWGKa*p%pq2&7^dQu`5vSilBZ>^A_+&uY$4I9u(fciJ1Hh; zheNEIBtbhIVvQ8h?Qn>-;ZEFo&<=-K2S|>Zdtk&^0=oPmeyh_hGl3-X+GT;z#ln4JO{X(g1zt*03%AA>w^C5tS8*_CY|qpisz zi(wY|caRsYPNg^%uK4{IWQ=u2q#L~PR5LEl7;9}v2x2;cybQIEe_2X@6=$3;`l~p; z#J8vi_g+H`W4zV-Ud#u6)#!1)JDy+-6d4GDcgGW~XG9)?>r#K%)rAg%!(vi7 zWq7~z_g7!Diiosn&$AG(S@($S?8P$`ldZBMhd?$%uQl1q|65~ItSS=Iburwz+!AL@ zv1*Gv0aBbK|5##*e=PJDVgu174CErzY9Go64h{Te#cz2IdZRpozf3n2WU9_N9%VGh z8+jxGGBc0N1bN37>{pk7%(EKhtBnO#t9-Sw!0IH@^R75!Bcxhr^$-~evW+BvjV-eJ zQp`zH@B1R_DG|Nzi>%?C548k*{=UeXB02X;;-j;~dP`(52p*j!)?AT3FxJ9Z*>|mX zMMi>P%)8b~k+CpCg|+&gwN4}gl0%aJc%@q#DCVT8k43tb$*HJ>x5G6f-AXKrZR}*6 zK04{vbP;`YmRi@M1dq-K*3fdC3S;o-d}!^D5)!9MjxF|t??rI+3|YoAPLXK zVLM!J9T&m5IBbXOt+OI;!qp1rH$Jy6iOdGU@0ovY-4M}7=L@S)BA+{h8t|LHU&Ne) zUs_>_!C#qt2WsO>tF;LJ$|TAL>t&I1i}_61XuV335-PBSOSsXR&M|%uu+efV;MV!< z#_PpK>pV&D+zSdmI-ys!`Yu!7JQtsD`o)#)Icti(zjgR%rLG&1>jI?OZM_qfPr>*&BgfhiB_&8+ zx*xZmX_P4iZ&Sjt$g!r7@Yc&h%pPlYl(Zu`LlX3mdt#2x9xIn(xDLlrs#~hywpb!0 zg+*}O`#MSfvtW-^A}SRgi~rhI_Wwsr>I1>n|8My~_D1x#DECkwh3i}JtAHOs3BUIR zzmK#AWUnuJ4a`2PO;tXo*I`V%6=L?s)XxDcnPWm#zvQdl0c#LRO6Xw_yy_jWhD*$n zHQcuxuwImyk3g_*IbcnYm`zP|%v6c_7KCGFO3W7z^INnBta%c%7bFw*`hb-#F^!Jv zd{#+JXAsWk6NwqsT<5b`%&khz^m(m`dZym6%)hGyCh#0Nzg)dT&)UAmUtUdV2 z%C5zt*TDQ_<$u=yb4(rnmz1c3`2=U~&sHi)B!nmD{OWLTY0VPBO}gTX^jU@PH>@lldUDycp^ZI?OV^*hxy!o8?4_QX3-U>aM1y5k$igv1u_d#owZhpB%I*#oU=}m1ij5U>mo@c zbYUjf^EvAp$;41R+|gq_pR?i{;I@L_$~^Kz4}Hd^EvAQi5UU1jifHe&@tUfGLnVPi{m8OQGz|vIqQrF_J=G^ zL);7QjsKuj4M`@3?kTFb-jZWNl|Xpwog|gMlY%-ViG+&75nKUTp0mzNs)``k+dv(X z1mov(R*^=y^^;~$V=t0KLil}1JPXcSDIz%_So7ztR8HlOvH!9{jWM4{Xe6}mtg`SH zl9ebj9%K(mDhvEB5yxD#=92`y#6_z_6E2VLB`#XoB*99#OV$|@zWeLg8fG}HT1_z( z$DD!RBl*YbM8e-Td@Kax5^FTa(9yxY$hGE^aLr$VRJk#&CD-~WDiz0UB?<05bFCvJ z!QFALmCy{!6U+(cS{F%zyW?ETYfdr#x|H0Qd(R+7-+Sg-#W@w-d*Zs3T&tIeUYByk z>Q54s@TxUlV)WhdRco_|zB|5ZrMAHG1SPy`y-yOXoVglP!XQSM@T#?jQ~4#lYF!Y~ zCH&XAMiL3NZ_iJ}u34p9;?^UfsTKITbIqzPQuj%CYhgUB2)7!FvpxfgdPOJk>_=*2g$@xGw5yV!d%jID_LTCfHWj|O7f{zjjz<#trtZGb>Oq$x)ovZ z$B@^pRV2Zc=(-ix3il%T1?}rrm?UEKgEF_EZIz0W4j?zIDkA4hj=5Z=*h>_$2e8n7t)`ew zBx%OX-uUD;&IsARh-~?X#k9|p@D(HrVl4aZhjHs^q1Rz_x|`$&kvSrcU9=6x@Y?46 z5aZg7NrG?Gd-fm^{ksfdd$FV%xgGj2NENorwB>wI@a(?L?nM%;gDq@7OTw$OEGy0^ zY`-9atFyu+<3;ox;_dci5qR|4N5aLSkIp+f)xx9GYLMzKiQyI6^+4|S1xKf?KuX$|q=Y$R zV8uO1Df_y}Paxe$3bn&JjD+;cv;^A~(JRvu?BXP8p%V3R1~ksN*RIQAbOGs4k}Rpr z0sOV7()J*c5+L}ssM7Xu5&cVo_t`Is=wA}N&z>N%=^2=nZ47l}PZik*Qi^1z$c9At zE+}lhtUXWU2apt!bdjefb9u_yt3<|vV0p^fpNRB@IpMWX4dv~PBEvv3p3dN3>Sk` zwDqzQqkkEyx?NqQAKclEcQNOdBK=E3Z8t(m5>e2`zJQHXx4$BR-|rr&xBj)rk097K zs@pl7%CG0@w$T|&$X{~CF=Ta{@1(XC(4)%gc9E_ab22U^mEQuWZYPRVgOwrp`>@sR z7f7<=^lVQJdlHMGe+{IDeTF2H)^A|ztwFsFoFno2$x#h^G`{3uOrnuh1t%^l2FDu@ zktFrPm`s|($1>NjH;dr2B$hHs9D}KP#^lqVWHH6?wg!{@k0iyY{S@a@(>^a!a*&o{ zy)ozD{-S0~-&@lzLos|cp9eYDv>S8?8qb7KOZM) zMLC}|FTm4Md@@qY?nsgnx&cy}q=&=|g?XF9aJ*{Sy|_GnOQ>a!AjvZH5v*lTBFQxL z(Wzx8^uc}SQSR*uOPHPI2`lgdR|5H&CDAHSEn%jp(N=i%% z`!^E!2D`+xu+K}(AwMQpVlMbGaih3ahx#!l3A{h(#}t;B2L^DNTiADrv;e^}x3KRM z8B>a5TH2LFUIW3HmUc~%SrXIAt}n7gVp`cPM0Qo>-;ix>j}dvtmybj`OFpgbO(Mr1 z(5WtnJmJUO@;vY9Xg{AiB1e1~Dze&_B_h3i`B9{ijGtTES4Emm<$Z5$mwbV@^~n@1 zO6 zf>|{jhj+0jiQMZ;x=3AL4vHlEQe*<>{5%Nu#$D_(B2_!{xVNi4NF<|zmJAZ!uXmw@ zUF}m*l0h3%-UDJbmkCw<8H=gu2;XNZ_4E*o#bv?q>Iv7`!iNP#D%x*#jj8?+cod zJR>nLci@?vZuavMlLmri?q-h@`TQWPP@+_?iR=RDL^7T8@!MfHJDtUEhu!RpB++}3 zI2rdVV(6CH%^o%dg=-bx$M0rO+yWrqKAK9~%AlA`1T&i1HqUl?7bp~nsdz4cHP%G1_fh=+g0CS*?c!V6#*G- z&ySK@Zikg2_WHLtrk#{$l3ij(Aehf&d(BLi=cPPv+C^psf-y7gBO+hzXQQuAYX%Z46u8Qak#08L-Bd+VZ^=V_H2}^A9ClYClA&vJAZ!@7v8pbRDj=+luHK z`^bJ&M3;G;-AhF8>1TF-5nbjl?I9$=@%l=Hw~jTo*%w{&JMswbX|^v|LX_`)!SbN| zrpk{dIgzan>k z{5x4#^H=QplB)C~Sh)tNuGk|h^q1=M%4w&h$Lh@Fq6kN@dA?I#R!TA`&^UwHhX*VZvVIcPw#Z*J1HeF zIXXda)6IE=Vt7n4l=A6G5(!;-)G)@7^d(6N73yLb<4Fcf46a>$i?%gFBmy!3O5e?S zNu&^rpI@U?6Gd(Zd7UJcgvU{w^FodZu0+oAD4{Ewvo1=OLO$J{uSmGntpe%k>>}Zu zu^sku^5@(uCg)x;IVZ*B{J3+9w;n3^7+-Ufo%19qp`svI!sM83CC6+lIc8gZohzI( z?K`gE>L0W9fsV5nk1~HR4u8k_NvDX2{#MMB&OI!C9S(A8k_6A+2RUm*^fyrlJ7tzo z&VH)FPJNP-aridePf)9aoxLLXHr$^e&p4gl#Z^6-##xO1E&cxE@! zssA2ri$)i1AZDbqI7*U98mH&Yd8E^ZBoZ0}Yoo5isB)y!S!6Ou9P|kzon9i>9^$d+ zNTXFN&Jx4_oR!;VH>sYm*~pRS)kbp4EW zj!Mq@3p_75r$zMJ+%GwQixld`Eo!WDRir2gwy3dAfu&SqA-&S_WycoLD=lAkiik8` z1y?Jm!&jUVB3(dmebg&XSrNU;bewa)h<-{wE@n^1JGCX|PZL%?LeArzh9Xx%-X&=z z(gdypAA+Pf9YxxKd`{9sWF*`VZ3mg)B#Vp($su`4ME|bhL}$2&{$0h1&KQwB@V>)A zNHxisAaVlaSCVNW?=|Cjtyi6yBA?+36cr_?+xc3k#4Y^n#JKQET^o4K&hVzz4We_}MR-pu|4bz-iB*FFc9cMw5WJ2^iPX0BD zvz_G}6B<+<-a>}0&vA~3ydpB!DY_cB6$#)Q6|aNyoY04ub5`8Pa8J!LQl#t*c%~P^m~0ZhkHeVx&iFN$Dp)}= z-x;))1?PKlmEe2_0^lz*9#>;xKJ%TIDQ0`zp&Z`Ud}kI(#3%|ezrdRS^PS4;f_w(u z8D}hr>3wCeI+RqF(1b?ZODuKPN5y19j!PY5 z1Ls_2BJb%kXS7H;kSsXLA2?k$Vob1KE1W4L!RUQOOl_=i-i}Ij|J`sDodqP3(Es3k zuRjfX9A~M>5s>4PP(Ebwdw`YBRu*Gnm}6Et`$g~zGiM;@RZfXbSi+zOSnbRf(Y?fK zr^r_vQx)nDzpnF<(}5%s`l2fLejhpAM0S8+8~e!VBa#fYfd^rY^P~vg?_jUB#u+A3 z6xLMW%FmCT(IWSO;Hu7#ofMI360_EsBGOo5);cppUSG)l^Ezj)$YKz@?yPg(6ImxQ z8O}3o#rMdZX3n~cbc0Vi-gzwKLj~%atccf zuK905a<{~M*p|O8ud>7zF1AcRK4tX2Lx#ZflqGrN|->+}189Q{>M??rpN2Y!RaZk4dtfA4Cd+;N|5T z=a5JliTTDkMH1A;Zl~(kxbH!W%5fU*KnZ%$9H$3K&?DtIi90bS%{T!^`5df&%5gS` z^r#VMI2U1szmu?wW1bTE-Wf0QH>`2Rm>--sNP=zcbJAIiQ_W#T5L_MiIiYMSkI{ZK ztm^^!$$5+=(RnEo#!P+ukAS;bH*KZ#&IgDA1?D-Q8FC1 ze#A+Sl9xb!b=F78WRMe1c9h_E^?!3J@8+ZPC48>{b3WxHMG5A7+L=zmV*t$g4<{!o z26O(yalYkz^lz!3b&^SfwtCiClY=oqTRrEj*n^U0oE!lT`J8jsM@h>Bc#Fmvz87P7 z>jOb@oyjB-BmND(7UVkHNqA-BvlO#i1XnhqTy+kT1bx&sXY&u3bI_u$I|qMc(JktR zGkYHjx9~d9XWno&uo!LNT)qf7-*j5<4@!6w#87=mA|bmQtlzvB6~%%oL!`_T@I?VAbG+Ihaz6-O(c;y1k%^^Y1suc_Qs0Qo0;x*! zBa7cZ->NP}362wPRiOh^^Km!=!QVp+sbmsfvyEetkQyy9y61-1#?zW&Vs3 z3Eg@hkBu#rEpjgi){mv~&w<$L2Z~7xUHA)H8kE^q2Swb!;SQVRIEzsO<^i%n92NQn zw*_B;$cAGDds|pNK@tgJ4}c@jqUsqDJl|QK7ij`lB9=Eq47fjKSuXPEd_KxW)m{<3 zD!8aJ4%5~{58n^>L@@7ARFxs&w)$5e+c%g_B&ZuCyziM%vILd?yH5$K#IIQ9V9kGm z`tl@+{~eeFrN6qMp9UtVZ4#r`&nKuH7FuzK-+@U`rGDd_ajg-)|9-D3PZ9~e0Q-ek z^U~@8kzw!hF)ghck(@O3JxFQA&l!)JTj4uPcq?C8wc}KD?qovgORJ%rDh|KQfcK~O zsSiZ-{b?EXnTS5hWz=>Nd|rUZy{!6PWD&H?yWrd?r;dr-JcHk5f%UTLJPDtJxL=7X zmn0I>^ID0@IEDKj3F&#QL}iNTUsbE1ZWGbJs#ZbWCDJQ_YonqnEiwcI&(n&kl1TYe z98*cv5UB-%F_lz(k=%ydk5yL9MdG9%tE}3K6r05}4EL+9BKLw|t=_L57wP*O-=|bj zPlyZw!TXdd>RFKvJ)jkR2tA;BK_m&}HR96|%G7$5rdKUA^GI6ETTP0A&i!=o}Y@+OAp?rAj$D;(>YIW7| zPs%3_OUUI(62Yx+axkXrIgZhH^wrg15nSDO`YwzaEuzoU>S|?_U<(Rb5%Rt%)=}h^IjS{Tq8Y(kNu%2ru=OUL` z*K-ZkMMT$g4K<&|(DhtH9T(BHT0>R7g!u%uT2r-SF|hZ;R##IE5y74frM8+ZqWiOGNiaE-&`RadPQc^s~cFO-4xhiZ#RF^Iwas;|Bk=>RdfUk%hDl9O>*<{t3ev!Oai z67-M_)iq8P3c)-R_Tddx!OOH4p}Rn^*J`Mg$T%2BVN4@cROD?CjA^7wij-Z4douciE1oTYzdDlo2u3#6+v+H)l@wq((DxXe$7-*k;g!= z_iLv5iR_k`=4!CWZxYj7Jty*{%)qozFNuti8JHGo5)1Wpxb>E5QIudCYo#(svJ5>M zY^6s2gLN4E5@jnjKbJ+1{aUH_uW&tAftk5C;g~+GJ`!p90Dm?5Vf7gcZT;cC@a$ZD zD>2W&5nc(Y+NymbV?oxE9EnPWC2Xg1S*U~qkD_$FidzrHE$w2)E$viais5lfF67)! zJw!Ce~ysdu96FCBc+v=x^lLWuM z+CQd^^;caumETtTtNkp-(arD;54d;guZ&x0TgG1^1688PEfqNCN!3Xtv;}5cyTR9! zRYc@ncrW`;$Y-#cBeEU@zt=rP?Ij83(1xhW1@hMF5Y>)_#tE475VctZM@=ks3vvnZ ziqEoG1h2xpt(x%|!{bb>!y#$_N%Yp&kOXJtkeC`9qBc+rpOqWoDmz5&5W({7AldW( zkix4wZT_E9BHwETyhN|L)c;C<7179}>c}CTbl2NIKF-(mn z;n^pACu_K>dn=`)Z(nbORKrzc5uAJ9M)I)8t#D<)xr-6%5s{)GIBz^cJtpEd<7?A% zs-H-45R7?F4H0QG1b+9U5v=G?&x!N_8Amc!M85?&N=*{cZ$XYyuZz@&5+;s^-z!mX zi*y31Lb8y>Z_h8NH6+0?eLb@sI!T(*1fI_Q3o&C)p(KSaK=pmd09=iFox^-6BuueQ`bm>t&fXoQR7s|##G#*vLT=UiYXEm zgV*l=in%{3<`CsmpCl66P!L9pB+W%~K)i~$t@bQ_jg43RC1xVbb+>|;@oI?3VvzPE zBlD$7QK>A(q~Y+?8e&qE<8X~#YYETCKqji1BEx&btXicwBccpNF|>8O?o3uCNO&xY zdpcPqiRf<#Ojd~=*R#HFnygxo95plG+t;|FW3oR7f_(z#qhs_O$Yj-tQ_(jBMnHKc ztItIAHw328^&!sREIY&bEE5cI|h?%K! zMcRnWQdPrL8zFoz31i+?gGBJXB$PC@SY&5imUq;Ck)K3ntMF}{5B}!w?19iPsALiA z1HN9&QG-P81i|aY95q}-ANRTH6%l>h=c?%}bi96p^2}4GSd7Ubc8h1Tq%zN9Ema*wrh{OsTdI1H1ZxzRsYx7TEQV*$cz3f*WpfN&iLga2 zQ}v4B)=!3BUc%$3WvV#|_s^K~GBus#xbgQ~xMzYR{k|$w98(>K1%vP#K2UWyQs;fvA2wrnn$Bgto zRF88EwJ41FP^FRtb4?$q^CEim^)a1aFs_To-uNKQ9S4HF@iCCKeoWPu;hU+`;n}(` zfBerd{(_hcHM9ipd;9|U@<49`WTZ%85KNV!UKY9V7_4lp5@&p(Ue&2!UeqDUf9?KM zO{bWkoxg8ka| zA2FfQm``*J{3rZn#pC-#*bcY(t;GSsy~y-Mj{yR~qf;90&$s(Ay47X*f~~G9#C)xi zXzP)XZgpR)0V2B9eXX7r!8a}&L#iEWlnB0Y*_I@KtNWjNg<{f-B=~x8KZyCCnkVva zI*iIecB)K~oJS2~JjgdH^FFMNG{c2{_id2fs$dxuzVlrNvPVr9!RH_wLB5Zv)xGLW zRH_RQvsbMz%lTlcB30vzA7fJOQvH1H>Ft?}}imLm($&Qk_&ABRAzeFZ_p^E~4+3&&F*1tjeI6 zEV{qI->W^V^8a%BSrz@8(br)w&Z_9&j4o6y&N#2ub3Xp>=bl%aN%*&Q6vcc)68wtm zUur)|N+|Jr*msEeTj}4+eh8!l$tg*tf1&knl~^e_I@l5}s%av4bg=zgj@i?H)N+d9 zD}Hy#`5(29CDa2_^aHu34vX9eZ4Bq5uBqQdeu4X7mUAMt;kg9MKO*>kF3U}kRvCto zumMjxet3B7$eImGi^(`pRbybOk*RL)n=_E1u1(k0|CX3(~R8VfH z%_8`f)KGP}qfogb_?1)?!@d80F3+be_?x%1rWuSxw zT*wUmGUFeHZDD!hefdkaRme@IRFe$++Um_uVD*FhBT3Llh1~poZ^-?XV)&Z!1k6B% z-18zhf-MZsX8tQxZd9tLp@bpVu7+jiuY0jnunM41jok%L=t6D+#bg>ort+B^a?>Rh z&Uj3Md@T1ANw8MLavN61oP*!bwcVB?`uB5fw*$#U|Mzokx4XpXwcobeha?ihwcm4L zTedq$V)U$<;|`M;oOMix7{~oUVmvsT@ZzuBk0qu!$cGT4+%F`i@-P^Wz?tH@nG#bE zWIe>VZt)tt7hCS*p3QSBiUU|ZcUO%s4~ct(Q* zc90nSrA1tzcbnUdWFr02Vlu?t=Jt`8naMood7JwrNlIuL2+n!l<_?pX4<6?^>f7AW zB$3dUAUH>ToBM{u=hyrUzEhm@pCRCvav7M;T`To**f-7hbW(V5;Ifk@D8`B$a0uH zz&#Dh^CTQE+^;)aena-8xvd%3*d1;U-JW#2Rq# z067KjNyKx^cV0|GIu8lr^3GOPWLd!L~ZLL39Ml60p*3`dZ(LEC&;G}$ldOE63%C7 z2!4mfJxvk`O@iL9CB^(DG83d7$@YhCfo%pe+a=s<5`*>g0mPJWYu3eWMM79VDEGKe zvG^q{=}uvxJ_`4uq`O?CF>LEwNLA9U`XJ?F32#LQv`dP!q7Bpy8}pgL{ysO6N&NKan09uH%iRCAUJ-m?Pf_#njDg2_9975d(S}9e=hksUZCUku-SuM5?s{&)ZkUSC?msCXM+C=ymq-#x;5X36a;vN7Ru#$g zr7owUUi2EJ>Lf82+UnZqArdF;u%6pjWM@fUaaYfMN@PC>&hFN8M~GlAf$KBtxno4I zm#9FJzelPkW8*CKLv=pm0{KnkAwQ;iZNsSCAkL`oOJk7+TH<#ff%67p8v3#h1Y1I5 zUp9l(hnU9hL@uF!O~LPj<6l<%EB8Vwh-u=^<`|>mbk3)#%U>xBW|o?|*-`mqLi(m| zQg=LF!M#H>cO;2iDaa~r9ky|+_M>h2-&kntHYQ06 zeb5}Hclr|;!^iz{dssc<9u&daV0q+8&d06FW$xxClW?luFxu&GbOx`+ooz3_Up-o+psFBBMYykrWfT-V^SUNJ@#g@C0EmNutP?X)xwJ2*lc>c=FJ@R@s&_MTrZN`1th`fBFQ~S65QP+xjmi^^7)AJnH?qTNq!@Nxys(~#qrj- z^>ZSlALrJaA@vAOL?s*p4FI>Zr1VHm@sf}bg${JlhydyHbZja{LbXu0)a-1-@cNi+UB3*QYRDKi{%PBZpE zYv~B`xZ7By6tsGsxXW z5(&+LxvyCuPr18T=xo9>?kV?Bl&pi8A?_6xsvrE7mm#h-BB-%Uk`hsJjAV3_a6ZkS z!&E`r7!q@y4sqvG4A)`HHn=TF0)JWYgQ3T6A1&&e`tS{QkY{y_IslSPvVyl|J`5uw zY(K-?43Q)dEYC0*6=cRW0Kx0&aJTA6-meM`U?mIWGu&+yB`<=Ea63i`*2YM;Pn2M7 zJnud$(i`%@ZN2EeLK2KX#<){Rg0BIL@%KWPIS^guG42dX1#dXQo~A%~UUJi;WD3a3 zE`O7UbH-kMyz7jj{qpUMgbkEzaQYD~-_sWbg-vKpf={Zl}Um^IWg=_LHley)O51n$r=R318a@VESy z#*}%fdxTNOSGn6n8prdU%W8L*NOustb6M@~Wud(|16%*l%_RwX z?2p}^W2xptcW;6DPB_CqcKeD{0=bK1u!w%@y4D>bqMy31bzc%W4^Ia0K7XA%QKUct zn8_eXB?-2k5p$F?-0Cl5d4i+-Uoj0ShL7@7kaLFHmV~cgqezlj3_LnVK|XcYzrt+{ zpF!UQS?`9&vEcD?+r!+bJ4{3$uZ`|(lAt`B-18*7_Mj1@+T^}5K5v=7irKHP-27|R z{wroa<-_~+807qwdtL-bgSd*|zf#?dN`?LNSFRRJ#ghLj{FPfg1tV6(f3$kRKD@ zzdg(iLQB}}J}x!Cy*u|CTiqu_z6HU4W2^hDj)5K>?{~JjFNo;xbZv`inVIf*iHTnh zzo!h_%5+~BIkg1ls7V%!;Co4*l58LeX5O~D&ID{x!OYuscRool&z9w8uuwmbttHDn zGYL~gjMRNFTi*e`C+Pl55{!R#=oS^PS1|>mpRMn3%~vU((5_4HL=5uT>E1??5;_d> zImulT^YKN)*iKSfVlqK;NGeIplaT5lNezh^1M(Y5eTjJuQe7ZvE-~{#ZjiK>m=U+b z6UB~Lo~{z}3P@3s$0g=KNm%XM2%e6(Pe{yJkb5cSS&51K4R<#rFG$R6kQyZ8C1zu3 z_~JOpWQoZEX+!ds#CWh5-ALw0Oc{{AB=1VhgCI|ntdN*aAfrjvNzCgYlSsalm{lNe zl4MFu!+&7@nIv0cI)W@C`9Wgp!x#A0k{ptlM?p4{oZ=Y2Z`tW4NAM_f-}`natkn{) z1(FN$qdO@|W^{%XChnxE7!$0EJL;Z!i$%AnWA6Dhl%StK7IUT?bFWbh_n9BV)AnPo z^$w=uC(d>^SeN1!7Qva)A|!W3#oR+uIx40T$Z@w~l++}t9wiM(>P1OQlKDFe_**~u z8?20Un?}WSq?k5Q(u1UPl=LNeEJ_BG^dpIcYOmv-?YKKwq$vnK8$IqmN0Qo>>sjC~NJS504Xi*PDhH9e40^%v2r zrZ2lgNVtU9L;mBAiV~b{{>Pn6k`?k^<@XD6-K88u-=4)e>RflVh@PX)b@^SP;7;m_ z?pKx0=Zd?YQl%N?;rY(HP{JUlKb(=cmgcITO8?$OAjvS>gIB#k4$E~gkkbS5$E@g| zU$SFT9q7(iy&#|8LGY><$ahoVY6WHf*Dv8wkdN{R&a{6Da?OvynRb-xzPwYN<%Tc# z1|-JZ)Dph}`glA#hKK*ay8z>Po<$F&B-A06kR^I0d>p0Hze=+WaxUNtu7v*vB;FTX z317^FcPIR9Z9W3e`XR>h^tCVBfji!46DhK;EuwVZZNvQ*5YbW z`mV&6_^024RFHEZLqt43)fAYES^>vBtR;Sdg0G`Msz9E<46Bo1Uhg(P<`r039R?}v z%Tu?)lWmaOed&4?o{WGL@g)JSp0z-V`qC51(+uPeU+#c>+JO}FrDFoT_Xbkjm)GD* zGzjEQUoL?>4|10;f5X*t63E@YZ2ueHNB}9}%br}rSPXKHFMV9N(*h~!%WJR~8$e3= zavAnwCrE-X@nQIlVvu`%c?8A)2ceOb_T@GhU7UoNymArbK0l@ytgOBPQr4HLMPPmv zj(a&@^lGv4zKq-p=NH@|C;D;`q&7$eUsi*(2C3-F#f|W$GDsy~CVp)g`{Dk*vM<+5 zKpTX%c)u@MFhfl1e{+ujlm@ z!QWHB=kN8r!6N!M6Cd(Mi0I!;e8_uAq-iyNN29(sQKSzcEFeiUK3oE;1Yy4#dK*OWcNI|@dq0aj4mo!NY3f}i3Er=4=HK?|?)yC_+4*bXtD7G8Z4_{AdV`Fp@Q-@=tS{f^w)Yy4q=cq{;Of5iUQ3B7`y6~b9qOmO*FoeV zkfCtywD-D`ObqEOQG0KQNY5O@SPp$ld+#}tl+YlMq$Vh1ImSOz+I#6NRL_6Hezo^1 zFXMgJ_0!(V7SZ+7!7KiL-umh24Itq$Wbq{U4H|D3NhDMYwqA+kdy!TkwMh=LPun(Wch2l9`TZwQ>_}h<{$CWN%-kf2gs+3_qW98n(yMdA7Co@MZz-Ndb@aci0Jvl zE?y}SUC&*;@*=vPyLwec(%@-xf7n(xudc`kAWxDs5qUn1pQCm69u}Dhg3r;q$DFx6 z{1&C_IS^gXJ^dKn4j=P6bIw#_DX{fk-Vl;ZqZQ2IkGl`vDe}gP6nGW-Cm4(M@vf6_ zjeU^_Pc^*o3aTH<2U}gTSArzdz*K)tf}YE3NpjNEJwQLNgNW_{`gz?spU^-^g=3rk zULTPZkX98?29ac%T|jUgHNbm?gwG2cs}JyIlbnnza}OV{f!-cY6^HwUUm6)GwUHT* zB}|8Xe?r%f(j|P-7hOVM;&sl0yla#*_ed)spFuHoIM@rXjEDBawSjdw#7iaNIvfLK z9_lS3;e4<^9O|u#N`>?DPkTqBVsKl}#B6JrcQGmzZfm$Va20Mn*wzSdGzo9(2gqlH zmrBAVJOnb*JCiTfsF)It@{HBNeqjlp_nMLf--3I=Ya?>+eLO?{g7>J%qAPrqM|-_Q zJ^{h&;An3E3$-ZR(-&j*^d)aJ#RRoF);liwd~p@}LrC?qcShtpkQ*eIM4tK!R?5Q@ z+*iEoB4a@mNudvMzhLdtY#3)kI~?b^B7H&Xz|k4!6%%=;0mqE@N{LJX!I<%0qR1x` zI3~rbDv|?&F)7}IBCFsz2ySbF*F+=}1Y;(6ZA7N8;&&@1dYwg{R_L2zd2b+3ZR%Aqjo z1bM@&Ch{dnGm?5Ne%pA{YbPK)a=`9p- zWK28DTPAX+jA>_iABng>K}`&TciX+sL=r$|k!%(z@H5P~f~0vnbPUK6l072BA=Pq_ zcf124lR!Qs`IW`5)!AMyNl;_6y`-{v+s|yzJj%79--Vg&-6rx;X>J>{y}Lwojm_~& zi|87g<5d#DXOUQAbG;fOl|Zn@=6dx-I>Pb9xu$tubCJFv*nZ}D?M1q_=6vRReRK>6 z<}=?LL=x=R0xz2+XbB6v@xSJ62@5x*M@UhI7X`y73qF80<+J_VcczAyH^5Gf9V`@Y!Q#zIFB_jHMO7LIOoKJR+FCDlFU zxPIRCeiW$yf~ns1j*tW;d@rVi?|JK1=gs*&uiSCo_Z?+9=l8rSBKtrv=l8t2Btg#U zF*&Du`!?pS)pYL_$>(XwIo*qhOq86{y*EV$_ThVwrQSOtBS3I8xYS!r0`Iw$;Sw(M zmP?EtKQHq>7Wo^Vq+>gL-}_wT1_Qujk#b z54`U=m4C*4;7weTKjxt1to!E=yyGIefBwKbE28_}72YKg-S@8WZiuLs+3JfXT5!xNub9a9AQ-dCOAtwunAKjQ$O?&B?Nt+rIGoRi-h(1BL^{A#8e=~8hKlqA!I+P| zQ6jr!TWh_SMShlTt@R=z%MZW`0hl#g=e;442@+ovWj0CBOJsQ0PR2tW2G@&>nD+cB zjS8Y?fVJ?qP&N#P)d&)k6^ApxSs-8ff-}HBf^7EgXr8xiZ1q;1!mUR_Nqe|GZ}l=r zQbJFHU|ZelZIl?@uWt3WON{PUw|cuJrsfD(VFE{StM{YCv;rwfa#&)H!sxXu$!`*q z3sQ;Xyu{!aRE|TLw|dt_<{pD@u#?1}j;As&2lW1-k zo*5w=?dcihvUwe0rxi|8lUyJK2Qj`!nd zd5?0A7x_bKO!hR#dqYI;X^!`fNV6BX_uJzw66pehz26>hxrkmx@SV3tM6V+F&ih=X zz&Uug0FKc2-WHL?dOo!5B2f=ag;g~u+ z;zj>LE1pe9ye^a~SgipuoR2@2IO6S*ob|}zsCPg_j~tGAzlvNh%g6m!?{^V=Ul{lO zSMP$z4v9JD{VSr!3CFyG=Xk&L@jC7~BKmk8_lk<>_k~Y*_lW5Cg->|pbUt#tPI^^z zj2y3%UL6trhV^e=BN2SV`rhWS^22K_qTdBR<#iI#?*gCldWy_U=3{!=>npMh1dr)y zZ!k&F#(wukkfelk8~fdRNm6aOkL&PvZ=%Q^5Uj)By;KqX?)D$vED`3uD-2I>Z5{`7K0^!wE3y!|5j zed=@GQ4u^Z@ICSK-f0m$FOs2socI0~(eEbz?*0DtY!Ti2{p%GG(Pz^&uY`y`o343fMfBNp-Me2zpH0`j+9J9KxZyPv(LKNo zuN6sfHr@0(k_2bdO|OTfswLO%n_jX=3+a(=dQXXTm-;cn!$pQl{TSgfBKj!Dg(ryU zqZ}8WMiTU*w}h8+jNgmi5?)Udw1fiTx>xfa_X6S6fAb#q0^v3J?j7R84X*RC&{w_q za0?NA)r${z5E&?=fM#@@E%LO~VJMs; z@}TruW_XH7Yw5Mj@C=bZ2gCe5JZH7Sb49L!3?xY>3F^lVm$;F)e(dmm5q)0R;rw@7 zDm-^^fjno53a`D1`2=g0R5+U?nCn*IQ&F-G_RS678dnHXMM7B5I6m~ko(R@+Cj37y zTwFxgVK|&1qU$gmt{{SQjQG2^w}q>T+ygS4q#lcZKUg??=SO+>qHwqq#RT$eyDH^^kf@_5EeWE+UH$-rS(k;(G4;a4n7OH1|-;0HtlLUKOEWCPF-r6V@ zp3SM~=-^nZSa`9BJ`0M4KM>KiQ9S&yh(0>S!(WKpy@jv2cZRo$=)0sl!@ESDgdTfa zJy?+t{$6Ax$WD@jBD>)#^Bu_D;o~BQK@O0dCkgHuON3_^#C;EX{*p26xn%gmMCo5&`ZQ^%N6;awsJKrp6Mc&|v|&T%yJpAbGM zaxVzZ{3nD@h`b1Mupc*sQBnA;$P|#zNG^+*qGD7JR* z-g{pdY?orc1bsjE?|h%S_)(xuX(oJ=>jmL**HU((VPHL@Y^Zw{04mC-R#DrQOok3sIj^+TC-5C6T9s z%ecWORm!-B{5;qEW!>>U ziG0thtUC>P)QXmMp9E1WTGm|*qE@t=yAnjLXgPNs$mj>noh4&8%Db|z$3U7_ldJVg?kSLthS*h5$vqF!3wbKLe}FuK zJeA#4p3KDpL+tESanpk=H^k0P6*n`;nLg$k*Yu*Qn*-!eL%#D#L6Dx+WmQ&li-Qa` z#I~oJTNXrPw(4$G5RKWYyLCYl`pdI4HQakaG8$r^rK#bz0g1!vuIY9L$&A>qrrQf7 z%O&$wJk!Ej?f{V64GEiiYPrKf>X>H~ZJyfhqaf`Jv3Y8{k1NS9+FQq+1ETR^9rtOF zA$cUv-R=sIDTdfFyxUz1qPqum-Hjl+dr;SX8|3&y=6fJUdOdds$Yn#a`{ZMga^|^H z`yKoG?m>{2hK$K&$x)E!Yn!*YOuz1NPl9YQWQR}A`6O~@wt<^)qpu0HKMmZ>QRLw& z=KW;1h)=eM)c!PbCrTct%Tn`Rq{-9BU7W$z^DcjzyRq9elMuB(joqw)n7@g;J*zK0 zG51CDRmF{t)5P5kQqqvnYnB8f;im2s|5>%jJ?N(H0hG#sxoGMh2FZcBXzHE-(cPYA z?oS|v^2>ZRbAJQTYtQEHpCEee+1yRMS=y{S3@uz2WOp^u`4(E;5_>&ljH zVGzBpY~_{&(d)`qZg~)mX)YiQpMDHcF zb$f&8HEcU~Ac$VWwsS{F90(`NI%fPe*qf$Of}g%G5C5*>rb-d}xT>3+UwT_esQqbWWyG zo!$1?WKK6=C#SR9734ibZWw5uDs%e?@mGr7)$iht@X5Hub;r&3TTGsA?s%V!PTXe5 zGM`MBJiH3%=8n6?%93a2>{GbiTsONA`^{nd#BX=^9Ed*g+ru4ntK`uqetWo=LG+2= zo^E1J$)itt_jC(_=yTn@+&Uoo?DvCiHxPZ&ySF=0NTNOo-^V?MJo+SjA9rOgk)=<< z_jP;a7NSqWKjb#bBSfEs@8_1zD@31!@9*vg(I?>txYP4V9(@x2VK*_q5PcGUpu4+( z5PcH<5qC==A^IfzAa`S7A^IfzV0Z3q$YX4aeS&?6yAed6U?1v!2BJ@}4|6Yo=o9S2 z-AqNK9({s+xO-O=u}`p%aC?F16YQhhDIod;`)GGHh`yCD#&vI(n)R)OvF-p6eJ5g^ zTe_I!(RU&qb>oW*`OH{i`%c7o_bw29ihF{)r-bCur?@A$ca@9~`xN&?_ZWyiQU915 zUrO@m6ZMnayde5S{baWWh(1w2#qFYb-jpZmr@G@n^ojb%-Q^(qMEx{(JBU6}Kixg% z6M4Sw;?R&Y!@a(=ZI6(w!z{TFMauYO`W=xxZGAGXOoTk@lXG`Q$P+%f<*o>M(I>N` z$PS+z^U1r8J}p1P?N-*78s{9qdY|dO0FvQ^<9u#P&2o2uj4^pm7&6;E4zkMRIcvxh zZm68pQ_m1*xOw8m&FK^AscXoSZnr41{d&icQ&FT`M@tG-u=Q*Y&1hzxIjt5=MuN;` zp3NZhNS1@lcT1q2xO`?OwU~Jdh2)gEZn9(%NqOXXnj{;@61SXRkMyF2>BTa)T@>kL z$TRM!DDt2oE8JyKWRM}N-1mJV&n>SoD#SsX=Dk2Lqz-8CS# zr`Zg7&3yyJ_OzHGo7|lswx>cq1F>)C2{{2`-|npKmpT_kE_XLEeX=%ha#K~Y{gO5} z^-JC46WMhf=aa@hiM;jvrrSaCILk~NexHf--gM`JYy^4BeZwa*r}o<#o83m$tgP{g z8#>7ysLgImkWUS~Fh`s!PoW zIw)xg@{J*H`guBnWHhpDsjY5zkOGF-JX_s zKLxQmFXWh%O4N6$K5%cTDRZjtQhn$)_Q}{p-IMvqZRL~EiMl8Ak=qG*@?SAJX6E!G zw+Hf+H^gexM{a-Q$$YE4#rBaq6nRP;V%z+YJI*KXhBS)%$Q@MED6hFD(kN~ZiAHgI z+*v54QQTg4K8Qwfd)*}<8pVC=t_0C2ZlC)ih(>Yy-B&?0W;@`%38FFEC+-;#joChR ze*@8&?KAfZ$bjF>mH240=j$fcvg7=2NTaxeZUqpH;=XWqzAb&%DDIG39eFg0J4~Wc z+?SN4QQTL|qfy+~Ze7%)G20QhF^I-&-?%M7e!0Q?3X^H`QI5RETE|E<);jLqhf*3@ zop8H?X!P~1`zVOUQQx^!qKJ*7zISJVX!P}iJ0CLl}MWOa%}BdZ@t^j*cD zIL;bbopxV=ERC$rxNm@HWc9PV4diE|;m?@y`NiD{5;i;kFB@`}h4878ZH0keg>k^@^9`}QN%{e7u@q88ZBRR)7~xmqi>sDa>t5B zrO~UB%kE{r)QOP3;dq&&p)vL!Y_rB}SKREit@h4#mcDQNfa&RGHJc(b#0!=K5bg>XfhJqxlQYx_Vb8ee$dpM*1G^=ZRP-<#MiSm zkM)0dd#F#$p%eWqUs_LyJj>Uzvw7c@#6HV6#*|7=qHlBG=%w>(9-r9ML_~JPGI$w% zGCHx7AzOTMEArTPIiE1~WbpDMkA0W3T1Q)|nB?)j!va6g-6*yH787|HlF@67Qb!Eg zYWZ~LVd`b6$-pYciB`Z7M&?_BiBEk23t z9%S^2`y?_u88L&A=cqG!E8j64MvTHe+8b(^!!<5S8Z3MBiY}}V2yL~b~@dI-$Xy+o6w;$w?A$Be@d51w( zZhv@na;3B+Ta7 zu?&w}G9Zdr=~=yzKAGd#cW*bDdT#b6fXw^eJV9wlHg8%K`OuJCyoFLvVva@fOJ3Q% z4dA-{} z?D(AWNqG=EKH*2rUXoWmili~5fY-<;A}hCvAq#qKqKK7M(Cdm)R@Tj?R6(yFh?SMo zCu2aYtP+M4_9jJva4hnIeGTLju{C2x{0eN<}2|4%qd7OLgIFC;}X~_LPN#m2ziG`~; zP9H;xdN(0YEklO+qzUp2N1kF{8{~N!d5U>md@?q%Q&Wk1i+iIa4|`GEdkT4Mo9+F< z;@&5c$LVD1nQCMe_fCOK``J91Zb%8Qd=tOTPG?hMy&)yNMp0zHA!WSPK8aYQvdLCu z*<{|KtQY+xhMk?V-bSgJZ{wErwt(nPa#?Q&NMc91A}iU^+`lCYb4Xm8s2h0kNAr( zOq*+X>!ZjwKKTs9j@S=A`L34fIwG0NnXL4)QKinBJT;Tas_8i$Y)?g&^_7O0zv%c! ziNrtln==t&Pi?9(A$+N@cv9l8)SFgu@6{n_`rK4@(_{39Y zjIHmk>E#4TH%W!t=9C)fgB%f?nC-5tOoLjAq{;}4@7qcYI;pT zbZ6jdW7yWub0Up?5wWJ%1*Lj-k^5gYy$6wJv?2ES%v#>V$TJ&xYI!4&N57g_+Z&HO z>F<|%YJ1a=N57+3$9ocadKzzT?=RQ!79)>-S@CXfCGyNhp1Zwu$fMs{tn0moJekas zvD?jv*Y&m{kA8izp0^Wuez{HZ)bsWs&&x-oU-i8&kY}4AwmtQ|W5{y?dG7H}Ah!;QawI%#b}kN$g}bA@WVahTgF*LiBCQhTfnDd=k>%;A@mj=Noxm zH_<5l{%RvHD~Nu7wUL(_WYv13&y$V5dWAt=GvwGfOG<&9>t;Ou40EOAl?MrRH`h}} zqwe);fLt^rugTNIs}GXc!#oY-lV%`idYj*8Hl(T74kWdCuHbQ>bOpKf0du8bNHecD z$Zdu!_Q@chM83Py+&d#AjlO@{-21M3(h+N(YvQh2o6_GrCzZ#o*x_VWF_}{R7 z(~xf7!rp#+oVfAk4H83oc;`UuIR9WsFRy7I$)n@k#~bF8h);RQ+wT+kj-qQk#6w=6 zzP3~(Prqa>?B@l)VSBYa!~A;0U*tD6_w)9ESWjfXVSBYwUqzL&UZ$U?#Fi2gyngTJ zT}W0+Nu*Tfhy2mtFS+;ga)9VJZ2NfyLGG&}?|=38ii7kv#M-+4URfcGb_S?Er|bBw zd7rqXxxY@*(-8a3xCpturTJRm8uR1_^W163w@FCm*%Dn0WS(4xSm_Z`$=ErotB){G z;d~*dtQn&Sxfk_B$VFoT3XX~{rN2lR$PP2EcNAn0`*rUw$upRwJ;)G}6LTfcP?GZ?!$?L> zkUYalrh<$h8TYj08A&n=WE4rRZzRuXlHwp^NV1t#Y5O&nq%g=hlG;Vh_nJ(eM@ia% zj3*gv?iSiS6G*0jOjMGv-dNhlOrFO`J~I8XWD?0|11rzAw5w2YKGTv4tj zBIFlC>=jvr*t=%uOg#%I%icA+7)=tYRx-a9J2~Vm)I14w4M}gvBCjDVn_7UyUJDTY zP142QhqJ=Q>O^+umUtbICw+DEjko8_R|ULoAYa}iHerd^*C(SBzc*ylH1q2K-eBZ; z+3YUZ8C>Fx2H9$e?bi}-5=f2#()Xp_Opwxs*gQ+U1wwq=`?jfhnRhUXSe<{?I~hgx znLI1B@9Feu)m0?#ejrvfLNxv{q@VQ{vfKQvsb`f}!6!4EXA_Mk_@s+ZxhK93aw1cU4joXm0SU-W?~k}ljmWP-fy6iIv_8aQI|Xqg4CI(WG2Y$sY*70Twy(jK-MkRJfVRi z>p6~LevltIV)Z~iWDEO(JTqA9c>-jC*^`hKZUWieL&;Yl9nB88d(q_>-?q$J3jEY$)elX)&fMt&H`-CPArK(bO3-UWH-NiB5}WH)u* z9V*f*F3~)9fb3hUq%}yw2qhyy5~<sEl&pP{98gH-OXy6&qR<{&GoB{^9vvuIffsD zG@<4H4J12P!7U?2`j>2BRgl%RnO#A$Qu<_&0Si_7T99I$lpFxLf%fMj$k<0TPmWO{ z{d%tV8X%wa)I8lmW^ye*4ssW5)XN}kdTFW8Kx$Ka{{Z=&ZO%Pfq_?DPsSVPH+S?0c z;!|4l43Mo{DX)TjP3d2NyiU*Pj1gHQ7HK{CK<=c5*9Ezo7O4+NV~)mbko6qr*FZ{8 z6TSrLN81uNR;0(V7X?9ncu?DO4@f%N)qWtYY1y6x*~|6y21pnB(<30=mutQbPVJ@dbBi;iuBjG3W|cfMf=&J`q{$`QNIxPWGp5DAUNMTx%p&);<)MAh}jDOw% zd6Aa?2apdqJ~vDd=^?J=(jYrIU#&oHrY{%)atou2Wgzt#x4aMX813PYAi3%BZ=5L7 zd(%7I3Gx~1X$vxWv5wDZka3KEo&|Z2Ben}YhZ!?Zu;K%S)sxeuf~ zwPhU0Q?xCsK$Q^RFFIAmtO*TlBGTcIm7jS86*!^L9VGH{Vc6bEs!a+Ha$U} zrOrI!=pJ0OWSs=lUQy8C^UCvW??B2c$Pg>~)a3ocFIm;BN>4t&JgKO((Bz0@+;Tiy&$D&`5ytf z!u7QfWHVd%Hi*6BYRs>>f<6Iqk#_95nIgRzV~OG*F8y*dkTbLrLqNJP{&^bYN3OW- zARFlMzX$10pOSu-NY6?eRSKj6BaoIL1*yHmK@PIbOF_!gw!8<@h}wG!q&p)AZ?;HZ z#+6bAb(-i1a5o&SgPvq$acnX+qmF z7UUSEuLRk}=;8yApV-r1K+4myWu7C_U*h_z08*3wv?EB}<*Gm9LB8j_KM#_c7HJR2 z9!4PNL29vuH$N%T>(IYf2I)zA*ac(=J=$X+y|@b2fILL|ybt6zu7V37wYg5S&lTy{ z)7n%6*+x(O0Eqq8m#m#BAo94&3DSv{zX!-D zj?XlZ^^5`5gAC_H$h%v-0}@b8hX#vi$r=p+UFu5579<72FcHQ27)xD zKV1MapW3noqzvPq;~<@AZPGp^(r=QOEzZt& zAd_jM(mgHGf1qtC2~wBwPYaM`T<^m`K4G3EAkWg9zY9{C*5)KgE4I*lw!`1u-AO&X z1Ed2zNNbRp)P#{BPjCj8gG{6c*#TnTl`+fN_|u<2-sjrMutcO6W&Cp&NF%PMb|Cxd zQ^tVIrA1l+@-;PKH^@HPv7bQ(GXBZ5RHWzPh?NKVgBG#_$Oc9rkAifk9eWPMzK6Hn#Zy$TC`qiy)cl2Xj0l(id~Z)d1;1kKY|6 zKYhyMAiJqiFN2(Bsn0;ZSfXS22gp>mIrp<7{S-C4Hb@+`w-?AQj0$FeG-u7Pf(&5% z^94vl>Z-FsWZlRJBp=8wuFAR~hp7pDK#tR5&jzVOZ}l3;JDjgCK~mAS#H|$RPf~h8 zkP(b7?g3fCp7sOzi5Bunko#$)-T=A4=;8>-AGFx1R*Ce5ocG&6HgUZ-0vXHr=V6fF zIjZwPE^*~<2Dz2?@EFJsjDONRC(;kGg+)P*aCJ8Ud4SRffo!1NSOhYR)@Cb6DbDh@ zAlc}o&oKr;Y(fc;_G6Va2f2rqWGF~(uBOExBk0lI0r{A|`v;ISjDK!eEzMEYFDKjlDna5dcr(w^Ef4#d7oBz<26GLT;H zLy!ryY-d5H()MO~QKawXXjBBr&7O7w8BFgv0ptnV=hYxbxSIBY%whcVE68?wy=-em zdRp4dDj?6&@7xdaF<18_ka4ueFM|BS_-8*z-cdR}zk?iQ{B!F%k=~5!t2#(gMk(Du zF40C!1!>LI{SwF~miiPV13m0zkXl>?xn2_KnWzc1KwhM!?Fq7jIzJtx9Ig5*AbaQ? z4uXVfZT6a=j0 zuFeB_n=O0`WCg9?QIG|Ue-d96=`S<>xgF$2j^Vu^jcEBF0lAs0U?IpCY~kA=t7tP% zfMlZd>o$n=hZsu~2Ps0o+zjM;+MgjHqv!{p21 z8z#ABbo0(rg1H&-zd`Wpp7a8 z(vrTiB}ffw?{JV`+2*An&1hTR1L;NWJq7Y8qXO?Wk-nKLr3}bnN^b+Qk)ClB$WTTg z&w!MshVKNaMT>MAqyb0crcEM!9mlyWNKtA+dyqb~En`6(_F^T-w~T*207*-``U^-4 zTDHuui}Vk@0CF& z&?0pKnM05E7|2Aff;AvhX`lCjWT5m5Af35Rv%e|Q3o`zx2J#g>^#dU9QCFvcR5Q-h z{J**STL+Ss-ux4g->J`+Kt5!toNtNr!i;}vf{dW$?*X!q<1-Cp57*#&ka?W1&q2PY zMqL3pNsY?0S)^xU+)@XmCGFvZAU{)|XM$9uch~^3g7q8%X}ws-C$vRmHQ}h{2bseW zs|WH3C7(Ga9kTukVks#|hgUdmd z(}U~)X*)_~{RDE1YbV3IBE2qs_gx^pxSHC5oS{z{1G1hLX$8n-YQk=i)3jqhgUsYO zXL?VhSLBG52g%BL?*OuoF~FlBkB-rPJqJ>cy73W68b%lAK)$3NW_@3z4`BRL38ZyL zt+_Ku14^F=(v>#q1(0{R20sRgqvih%llH22J+hy9m79B zR=s!?8UN%1`If7)F39iH zggzi4j>c?|F7#HffgIs{eF>7Awk7TZk^VBJ7X+El=;9ubH`&vEAZcmYo&*`q_~#9f zo9IuEfMi{+BcJL+k-m}hejCUqTRm<>=o(RsT-w1PH?_jfs~;y7y)t@ql;xA-59sL z5AqCc_>Ul!=<#p-Sfo#)-MACv9P4QdvVwkRG{|C(;js8Kg2-L9S0l z`VG{CS|BTFX?ucfrOrDL_+={*=r6bH#izuXKYf%azz$YA=x zr$NG8@7qCk)8l^+GLdUJ{b7+_ls2jqNMlAIEkP<$dxwLZW}BCSG@xyH59EGo?o~4ZOd4Y-zj}1$k&W6J^+cMUHt{55iMKhuSNQMTwfJHTGO9)1i5dydXVuTS2*v_ zgWN)kvz_kBIbk^zW5HMlt^B0y2#r?Jt zK=yHVz5`iI8`nv<9)?Y&Hwi?B$LGd5$x<9Apta$PSQZqqNjdAcwhjGMp6Y)#@crB4L8k2dNBkhi!7KL)u>EAbo1 zyIfzl{3OyRu!U7Ynlb+A3bK`UY%)l8#u95mK4Y5?fNZ3dxCl~&elW*rk^Tx-Tn&(s z^!VLD%G0Mj4swJV^)g62BaqKP&MeU}`~zeu+noE1NDtAr)CS2y?d=6pg7MD`ke;mh zRglLh{R@yT)K%wakyU^ZNIsCSxhm^|{6bCW19FiTdp1ZrdaKt!4syP}1j$C*68DQp zUrp%+L1r&WiMPocG&6KH_?B1TvQq z$ipD%*~0lCF8%vvkW#dV$3Q01qop|~(uXkqDGD-@@lO+w1WF$S(u{Uv5y;K7Hd{gV z(o=s6@)~vZ`tu^av@w`sGfRM6rrl@`a&olh849w6r51zSLXY+iNMFW3KY%>W@wwqw zk-n8NKxvT4oUc|OUr?h)fE=c6Sq74ham)K4b!o$Y1UX8JedBK;y%_DrognjAPg{`s zi?#2gL27bTp9PsXR`cuv=}yab2BZo-Y{m;By$t(a4&(u@ru#rHa}39U+{d|C1#%O; z-iILhXxYw!+(z4*<#&1P>TR0rA3+35x{ zk2Y#5$OYP#mq1!Fw)qrf9oPG1kYmh~>#|5+Pj6le$az z*+V^?2hx>Z?=6t}9d*Qxf>fdOL?^ZXyn0L8sM|p{a1GuIa-LS=5s=MXUkgD-G5&cQ zqyhcu36R%m$F2*B^h}H;ii7NBo11~Gqm>u}l8=7yX^=Hsaoa%#G5+};q%?g>`mjhp zNR285@&`+`1o?i6j^S{S1#I(Dkc-st_dq;q?JL>8v zkerM_o&oust8ypENovArkh8SdH^qze=JZx&LH2RJ+JoFg+cFm98A@LXGKJB_2Oux9 zr@w$)riIL$AkzEOMpXbwMSt26qWXt`& z4ss7yciMC!{ZCrIVjvGv>zaZ*O&>QHq&;=xDUdykF1CTJ;_Q3}GLSYZU3!szfVQP1 z$Q_JrT7W#x^*#*bZRS}5GK1dyU68`Gv?oDou!ZgoBK;NW;T<6L=p9;v3}^f^5@ZzP zpXDI^IfgqxZX2cA@)O9*Tss+Dk)D`G4apOMv9( z>@)|-NJ}ylw}|vsv>SJVY-2rbK?W|?@fi)$hokx|$V(itT_CgA!ZRQ} z=wUNv7wH|?_i`XJxSH+*$xm$=2lCixZOp?OD&SAXDh|vgHuzA5&YZfb^!{xgTUcSN9~4`n1L` zf^1^^vmfLXecbOL&olnHHK$0=&h=Ftk zo~s~NE|LBvy?HH=M`&q#g3O@KPX`Ips=op0228+zm2l zlxkFO5QpoGT5-f) z2f2gu{x!&HMj#1!Mfwt2t->G=P&XQaGfgN?kj)L@{^u&B3 zeGKED+d+jhkng#^7J@v-7QPKKi2n2h$Z^`S>+*~AD)i>XK`yb)%|O1O zl^6ojkbdxKkk7c{wu3BXB=bE;d-|001w?v0ZB!|c0`!$FK{8N#hl9MsHZKLqOWX1u zNO@}SDUha&3cP|M{c*08G9ardy$#3&#y_J#>M{a(1|*Iez7r%PEz)U_>>Q1o3W@Yl zom4l#G7tar)DaAa^X+ z7LEtm&v}0y~i7KiP|j^oq1L)j-bCQ$GN5h`Krjqy;0Hbs#zD%|8K2PfKzMbdjg1NH5N~r4Go0jDH>kNkxx8 z6XagnjSV1gu%1I8eHj0Qiixal9M$|F&vV4;flOfw`+{_)hkXL175lykWD-5dS0Fj4 zE%C)g`WV`lLLhh2>oowWL(A45qycU3T#%U@jWq#V6x>JlQoIqh>1kfpRy zjX_$lo`E2b(w{B>*+p&H0`dU;&T)`gv^Hr=iu9Va#>GHhrXOqya-2SHFvxORiKjp^ zaeZwAInCMm4&)8msC1=7dN%syk|0AF|Fi(v&-FeGBsEvT5|B^n&EEy-%lPLc$fV9{ zqukOW{Sx)?4v-o2Agw{RQ4>ajyvrF}4)Pj3$PSQ)MyafyKz`@i$#92AzmN9tE|Agm z4(&h^sJ&xAcF-cN0P$#_cZ0+;y7(Dn1;;s48IgW3N31+ZQOYkfDr!UI01BHTW?|7Fzz_K)&Jn zy5%mBzKkub3i1&BX;+XhX~!mmlw~Zj7UUe;d;rA$s-ZFC=F0aXNFDmY9A!oNUaq(r zAPea6yMwf#Pk9{V4{FrQAUPRWo10fn4UQtP7H!7O4+NCXU8zkiqm;uYvr^`T7#%PTH2Z z3L^b|N-qfVBBP6YKt5(q`+?-3WqT524%gQkAcg2pkAM_iuJfL%qDcRM^L`u1Nv`)s zAnO@{JPcBpEu0Thg#LXqNJHAgV<5fg(b7~B>Fv1+ih^{eeQpBsJy*dXkg|+_7J*!$ z^sOMT&{Ka4vWU8RePxlJ#Vi3?1tmbfrrl@`@)3RRP>|&;wHU;qM|%gPDJ}mGApO|F z8>)!(XBh*O2I<53Y6Y^H8Z`psZQ7P)AU{(R-Ulg48~!86d$ia$Ru$NRB1hK!tD|31l&3#b1?xVGwt(gkj}JGdqJ|Wo?k&~ zG5*O`L!?ikwp0NrNWXJGNK;yyNg%0cjb8*AKtH%2-(a@7*)bLq`%fs~-7?FmwkEu0SW2KDe2 zkbCJt4uXth{PQQs1javkYm4+D^dNVGlpLkn(i_D75|QZ7ERdYEhZ{kvaWx$Vd6zyV zTt{S0V*FD8ma9TA-@J$#Rw$fZjs)GcC0W+F6u@@ zkgvE327s)m9?k=)%lPLlkm8Ibj)LT+^u)R%-Tq#yjN$Dd3%CaF1^Jv-;t`N#Twe=8 zy3q2!4N{W+^aRM$v}4!R6X_S}t%`%Z!ZtSpd4g7A2uK?G!KXoHa>Z>2X-kj)JxCV% zl=SsQ`diefQXofIswKz=OLTmOgN$VSvlQe@YWRB~=jn4#fuv_t;N2tA>#*iBAYB>% zv;nC~T^$7y&j{ohkTK`zi=j0Jg&(pQ4? zWlui$N?MC*-B0U4GO*N36^wbZ4 zyhL4{0&=@qq_R%eft;t^_ypwGXw7p8^bV~-=1~(y zf-K|=E(e*$_-6-5?NK@!KY@J2wUeQ>NWX*j@Gg)RTutpjzMxMT1M)O2(h87osR_G5 z4$_YO3^JTnG*cUqUXUYJ9weUg-T~w-Mj(%Z^ranp4x|iq<0FuBTm|Pqc2EzqwiW63 z(d$(Lso7CStTRYiN}mYQm^SJKkhNTcAA|fvEAbo1My{`0+KKeRwER^;YSW)~1zAr! zHW}mw#u95mcCyU}K%S$OxCnA9{a}vvB7Fr{Tn&(i7|C=8xt%`cagcq~sFy*0WvS0V zzFDGU_y@>rwmJ8GBK<5iyf#Q$YHu%)Jd6rvfV5-HuYwGvr~U$@Id#?PAhNPD0?7yR zF;`_>kYm(@J|L%Pv1fxcq_=tvWH;yQOOSN5EpZ)1`csr%5adzDKlgyFWKa8noTr6+ z5~LSx)EgjwGP*bd62DwWK2;}?zKrvJ8_3&S?~OnvG5&cN#9<5PgM{hdH-qG(Jv;`o zobgYZ&LVvdTUZoi0pp)0Ah{@g5XeKc8;d~7(AsPT`H`ObTaW|P)$6;6^hO)2*7GaK2ztG2-9`F#YD*Q64)i zAU#Iu4E6^3g==RPNK4wojUYp~nht|pp-%}vD6+QFA{78hOZ!|Or{oacbl z_Hhl~3*yo8KLYXv*VjUjg>2#5AU)_$Pk?+%J9b@PkzSIqL~)QG+2&>- zAJ9q+0jWwq_%z5)uDIJ57LM}CH+Gp{a0#KDUhs;Kw5$%Paskipc|Q6LovNNQT7(;zo+G;ZoI z()-a{l?6G?`DzbRoVH~w$lH{@5@aQ#iw{6{v8TU)WT9otJV2yR?Kqz>(27m!2rXpez>$W^cgWFPJG zK9E+FegR|^?MC*2BE2`QO*N2woaF~V($RNM0h!oAM`ImGBYN{sKq}LcTmrd?t10Ir zBE2s?T1}AcwER6lzTx;x1Ia|)SP$|Q=j(HjV)O-9KuR#W$TLW!cVygB2jnT*@CQNe zq{p8LGM08@1IUl8=MczZ`kl~Vku{HFm>=X2N30&mn`~iUklFOGPk>Bf{Idz6MOm%NKbmt)I&x31ls2! zAp2;e8iPz^{4)^bRmMLHKrT~Twt&o~-#HGloz^DpFp)lx*0>nRG5W!#Aem@;2ZJ1- zm3RuIHrLlSkerNUz61G@Hp(4trO(LH_(#WSVP36XO zzK44Vk85ro!7%x>K6@NC|5rB0)yrwVU|K1=ttaH1+9%~c7%r*uV17MbIYjaW)1iL~ zFK{wxs^v6Y+dxP`XYbF1j3X)zepY6x%`Q&`uPDy`$LeevzUj10x8%(v_ zd--LYhgFjDN%MuBo90>`=SaSg)4!OE=YPAL=tIaUlSbsHZ6^BA>3Q3};9tsm7;Ak0 zRW9uaIq6Hucy4&lwkdf#=3|~!j?QP#nv!2rJ+Jd*kFB2k(oXuX=VPT>Z>;mmdls;( zEbJWmC24zGVBI`?o#px$>CdGGmL>0R`;RRTJG#D-reUYVS5i*-Amndb``Y@YcgICdy?8= zE8}1Ji;Sbz`*+hmE3Mqv^+fA`biS1I`)1!xhaJ`9FI!tVk>g-HgZYDUQ#v24FL)d* zA3P4WPmV**YYU~_d284-r=1UL{TNxz#(>SGz8)KRc1n z<_|j=^V=*Tr^^9JkK_?<*SWP{k}n?lP@Tyij6+^|#-0m1)*jmWk{-t)o-umv$2}U) zgq>}dZMl#WYdljLr{K7#9tOw%pQ>NySLNvOe@b3ZPP86I$0cj`+Hnj!nR?kY&S`Vb zrXeSq+xe8gkaO}W`(G00TRw5CJr;jp^+JzTZ&a?>uaHv*c3u5}9_#$;vFJf0znqVQ ze#GMOPA=o8ZF{tyl;r$x)Ejn+WE6XR_0;w$`Y*ZvjTOhL->BKqmN)ZzpR9KqkGO7v zGqH<8VHn=hMC-vek38_M`G98$d7*_^~v$yZ{L9VN&6|sf4`nNdy>{8 z$A7n;NPlAO69m_%u9M)p4j!wXsoqt)&FV$ixvhxkNAS4VcH5t@bK_a*m&z4+VMpyw z@L0L}r|0WS{lVjj1EpT&!Q%?slFE(Vzq0FnK_jVG`=2z8v`^cU^t|r-nCll47uj|C zcgM$$hxOAMCupkUq-g;^gbO?G!5##Ubv`uzwWh&(g2%RA+dlb=_=EY4EektK{p+4c z{P?f7L))8Dd28RS-Td0@=h*q{GSwc(``2IA&-HF-k5f5qCfF8>-HgsJ_Anm#5|~fz zUrPF(()pnN2ai?HMPK5arun3u|BBxHm4DXpRDIR`IrR@${Ph}kzVp*K=dz#1!yhF$ zY45hL`uWR78Q5mi z$zNps4LKzFMGtYBNpe4Oryoa$ow3EGT+-v%c3bVqd0g*Y%XsN^obA7^6HQfrg7pln zAngkB|Mu}3?2AkM5ZUKA+1AdlS;rqqJ2j3~uI+#8eL0^r4LiCYC;hQ`>|e-P1ApPbL7sgl(*=q)Ly`~6zK?vL2xu+cO5i`c1PzfwBZct+xv zI7hEnWIQALd}6P{&cchLC&A;j{(g1X8IW4Wzp%zkPc%j29gej4v|TWyaMoJY`3o6pXp&1dz>=Ck@C`9e;O8q)8Sn@ z>kT`XTG%ws(Reeuocaq*t$szHwsLp<1u?BUUeN?IwEhembis@y_UcSO*=X zd`j(7|D*M)T-$HkzNgU+Iga#0^J}Vp&F%w+O}k}(LH%IJk^O{-UvFRFul4qlv46HE zTYq$YT2AXz`MRIcX`wBbyq$W!#hl3b=z8O^-truYoVWF8xpMw<196VjAMc!QDfzS= z>PPHxbiLX>O}k~V_EYN(IocmRukyP2`zCSDb|c5mYmt|2n&1rg(^O7YvyWo^K{{Md zYx&n_+jg1j7L)#!oYGbPx*b}dZBKMP+TUQi{}`JzpPtuoiPjH0u5v7T5naE^O)1s= zr&!Nd=yM*L*7Nthj6VBmoKw!<9|}2%=K9@U|79@8wq5o)5WBAI{!#MdIA0%aetiz3 z!de;cO*3q2;-Ut&T)dMC*X@6G-oKthJ8WqySN^0ODnF7&o=egFEo*f?08&DI~rt`acyq)xorNtw^}`p^PkJn>+tA$ z>Qs~b!F~nz<8IwA?b7*9dK~*YrB54Mzucd-?dVhiAj`Jnv^Y&(+MpJ4m{?y<^O z|MF4}>93PhSs#r3C`z#YCj-WJ8r8i^E?>WR~!A% z+uXOrKG{EgtmD46gXopW4>>lqcKU)@w>Cbhyi3k&d|}(~$Bk8m2gk{lvpJ+(oU_B< zZ;5xN?Uwv&8p(O#apbYa<^Odq@nzVtcG}vL;CKf6AIu*-AM04{zU-UDIZydn4@oG>xq%?6mdMIH#^jleaHYkJKNT2dOVI z4?6GK@1%MXa+2Ed*!85;F0EJ7pj>HJP#?oiJ^wyoobwgp*J7USe}YpagH2OGzfwE9 zyW8VPzk~Jt;Xj9A;^`_jU)Xuq=tpw>4`aNOw?pFY*!@pLy*d0e&RJ;IP4amOI~mM< z9Xqe7{50PA(Z5cP%*Q0GzuA5o=Ny0@zXN_0e5ODDvHQE%)a&a>b&HM&OZM*?EF|o>X&(qcRCq; zwe#@T=M$VC{PGd|8g1un`M-O-E^OCb$Z2ezf3b1;>)*=!)-l(cb{=}*y13MD(!P~T zB%kCr`@w$xct`RlFn_kil3%Z@G_~!sa{4ut>jb&(3HjG&(Oj;B{uyq^#g4PBKdC&e z|G%5s_UL^rJO1`~Saun&!e(5o{?+i)IA`$pG7qwjLe5{ExAQ3ZllIfrf2)7}8+PiL z)b96wXs$EO(+y5$Urxx;cBGVVE$&mKq-PpOCT(x>`^}%swEYe{rQ1vY?e(agCwpw| zj6HAl@cG_y{&(}dzU@z;9dfLEyYF|x$S38jf6;nlwO7Y$!a9-H!KAkSSowoo_d#qu z);@Lc_iG~iNx}Sf{n-9WIhkMEPAQkbeW+l$;PGF{wR#|W9(IE3NcIOJ`ySExwBM0+ z66cp!Zd1|2u&-y)$9+Dx{nGO(r@{WjI-dQLjMwMQZ5rp~f6J!v&O437ZrO3L?X}NE zSbfQ1;svWOHg2%{F+=?O?x~#PKilKf&WT*2Ke6N~Pf4Ft?;hM~+mo`sq*PyU9Z3CQ zU;k~tMUUg1Uf9>v>$;_Q&Z1gsX-Dwb!FthtU2CfSOR3$`9$ml5*J}}s&l#-q;+5OyfjT|e6*bA z3mymeZGy*IF35w&norZ99Oc0@R=((QxE4DQtXGeN?F`CG=~(NDm1=pt9{;_ktixFO zl?VH$_2_v`gY8!yE5DxC`_Qq=68jhIcd*`M z_W4g+zqyYBJF4$t$Z>4@d`o(f_YU?&@;)c^JhRfKaZdbno5nlm5^X;Hi^MI({O1r% zTsqjEGw%`qWqDH${nNBB@~?PP$_M+U`6P|~K5*4I=|`}<^uvscFDK+AJ&rvt8T@!D z@?Os5-);NC&h&B8PTTLlW4Hh5`c&`!>GVup>6hxQtv`Al2lZ0UNBp>6zXkO*cx>yp z{y$iMN_Il~n^OCN^+fB1UH^LBDrs!JiX}(eakYL~Jqh+JR(|EznreIOb57CoqUD3_ z2_9>Cn?JgIaJ*ub3)=Nqa{pC1T3@VG>y`bah<^#{Ww8CR>Qk=u+T*`7F8_+0|AgKL z=TG~UQv0I&ZS8#<69=e&inedj`J%@srE!lgXY1c#p0kdwKiKZ*{LylP`C|1eR(Tx{ zttVDKJ+JvS4a&3SuQrdGKUV#E{%ZAI&2Idi_FS#J%1bGY)n1+7Sm(9gl+FkHtL0*) zvFZzsN3h=DvDRbbuWQvOy)LzW;$&5s=XHJ>=Y&nX7QHUC{Xx0PgQ>R1^1o`2mJ8Oa z<+pE2T3+WVRypOtRO?%n``Y}8?ssTCT3(LjdlIqMw~nhFzgYdU^+b=0%K3NmYdwFb zz0vcWQn{4QN9*lhjlF0{NIk82t4Sn^}VM>@{Ia>_N;>&@t69S1v)^1OEB`9F=f^?Ecq&b9fI zKK~Xxj(z>3&-H0LY^u+LN9%9!J=y5|(Rvr;N0*5mJ&5Org6FUGSf4BXIiIaB^1gr^ zha4-%wtEGh+ZVa!dd1jtdp~Q%Gm_fU+AmEli~h6zK;H8VIdeAHGZA@uJiLxvhibcm zX{>(jZe{BWIoltUdK=*SG8S)y=PwKO6Mfxx#HR7i<8RqC!Rgk@+VfOS!LxGS)@siu z{R=sVzO*EAJ+JN4_eWp7N&2H))5SS0*YBa}{gn{Dj~2%LmXK5Tl&xRihmIWU^Q8KG zu%=qSq-KA8jV-V3lX;6ij&*)*2Pu~{4Le;bM!22lU;Otw&38Ba{R?ydbe=sQ{0li% zpR!ETuw$Pyv35eATa)wJzU1XI4zVoE>rUH0E8o6%XzK}nA4%W;{QhHGPVf6l{^hTIbH#^C3sm(>OlnzsDb4 zR`R{?uY)kZFBQ%yyp_LhBHvlD>(I*4dD7!K*w4{=w14tlhe&=s*8F;`&&%oYLDW~( z*Y`;ISjSqQp6}v6pJAR;G5TTG(f2P)f3$t#KSGWkf0|&&FZr>&pDOopt^HBIspX1( zXzPu0g5{TSo@>gw%hho=kKq1$k8}f_Nl{s&s@scHb?)39K8>v@0q4_{2cBJ>v+C2*47tt zf?V4>7WYZ>`1eWUoZ8zZ|DSknApbb&-*A)KdMdY;^cCY*?6IV37b3^@{XRR-@;ryU z*PT3noFm`Wh<9G7BKmZn|2$*J(bTrn+L4`DR~P*E-&9_lzkaR0emzmzv-T?Itz6|p z{e50Nwq^e6PviJ&OKm#~rM2~G{;=~q-cypeNO|OWl+Rm5=MOofc|Ee7_-hUXTB8>zB4Wm`3-*>cN!7V)x{|$&hou@#mJ?`LXjR=i~8SrFo9X zjQ7>bMUUgv%Gr9X-f9}07k$4y7=P>cN+pfG&urUm>(_WO*zZ`!y0e|XQ)-X+ z#W3CrFz+c(wl#&Ek^b}DdYy6Y^`#_lP6eso#xL4#yPoV{EyQ{`8>Th_U}&uhNxw#`ZiJ6p2ZG|tK9$7S)(8a($q>=8LXvbvm)>ub}< z`xR0y70RV%xnR9{|3mfu)9sb#ny-BvM%S?8k>Gsjr;+E=rCo{t_;|=T{`2Gf->O&j zRNo6wJ&85$dOntZ2G0le>_2^8*HNr>@~`U0Kc&~P+9mca0qZ{b`u%^s+|SGHx;5Vc z>|xW8GugLu=K0_LavaoCooCgjSo#`k-2PANfwnu=I*Qfql=LK6Z*cwn|MiN${a439 z{kF!fS39_ zul)($m(cYm`(|;DyjN-7H^BZw@VxHlUhl_Ira$xTc*Hr%gZZzuK5gIs=`=V#*V-Pv zE(n&l*NZkz{@v_nSo;_2`Af~$&FsTQ=a=iv$oIZxeQw8FzmKG;#>Zht&WD^-RYacb z9|g;YoJsz?X*u(Kv7NTwkn`#!NrU~k)^X5$!SZ@PS>A&QJKy{7jm0^w{r6$a?``0^ zELcw0qxg$BXLmi@uXrcVI%%h_vtYSc?NENLX{>sseAs_~#E#q5j_viuBJ9WS!uwSR z{8aY?&G%-(!CZ|7r54W z3+7Y(OWH4;w@8|F9OC@%AXt5y^qFmcf+P0{QaN&;AaXxRzMBw-eDd7{J5T!E1Z|Jb ztK3gX{=QrA`x?9P95Zsobj_+j``_ya=?a*})cHVj}>9MZw z;Q9&Hzh$+Q)B8vo4}5!Pg!|v)vWJ}S$h>M?8`Q_7_sR8sd8C}SY5l_W#88JB<5xarpfO^V~R|N08?Z%=d5~u=(Pg5BxOwcly*{1;5`X z_j|8BKktkB_X(1JkNV%OU;8iLg*5L&-=9&v(|UsCg6-3B>AYRW zxv2jffq8G(?_WH=x1Hdu$zjhY*B_M=D;){@kkoIR{bO)@U1Z}h^#j58GqruO>iR-BF#>Ia3){c+bMSGt~{cbS5mT}y$*Vbpg1K{tq5BVrN$$5iaeD@J)Fv z|NHZ!{VR(1j7$0YX70ya<9o+Xu8{WF_ol4;)=2gJr-L+x4zrJeUaD{0dE6CuB! zRv+|v7tKH3e-0+(^!1+`NZwDiKej)1JyiYLrtyyWhmiC3SS!!GM-+A5wpY(LxkcKe ze(DoH4mQsj7=LBs%TlT2{4G;$YVMmIO?s@)ap?Ktt)<_3>^zw?|JuNDs=3kUwOwW$ z{P)SUzmeb5vGIxh{=;y;UwS^0YQLl(VYXlUr|0!p`>Xw|jqmKMe%NC>FL`|Z3psM$ z+~+sXaoO=!uKOgNlFU;|?f&0AziqJ0i;P3;c3MAY$LCF4AL_Vis^g>S@kLUvd~ey@ z-$Yz>_2bCAYJHmO`@nMDXug+-=biL?Fx7P;abCn;?T0F}FP(>h;gx9N4xv0XO3-pP&l^PjCh#OF?-3EbKZ{A$d_%oI7iFdd{^TtU-ykfP8j=0wvOn( zNV{}i{?)W~4$+(7^A`udw(=t1El7HN^|;+$Z;5{iIRo+hqg)q8zH_e6X=ti`=33KW zeX)+OwZ33JZI`6_{6OUSlBDNT=K8xxDrcAfyA!eJuRqRf{t+|mJcQ_9Rep58qsKiZ zJ<#}V&=qSpBH!bPKDP3{H0N!q&*$hq^oRa+u-Rwx{cVW*)cSqf=zP}x$nOiq&adr_ zRZh>xN@KOd{ywhNUt7+$<9s{mPq3b~9j?xU`C=XG_j&aCKbY@Go}~5b^BJ-7KfcfA zkL){WeX;WE`80l3qR< z2ko@#xwQv&zx#4c(YLGfq`!Y=_bDWQn7?d0PRB{VH}ij6`%@aHVExufH1+l%KTzNIVeavEpIPck;OCL%|c0&cX3!9Ct%{IIo2XdROX^p9mH!Dj({NuXt^Zz! zd9U2uce3^B_-Z=AT*up<9IPb%Jl4AX(2t`{{BEAVwef)JyYg7&l&c-nV@?0l>qXZ= zP)}nWYyM!W@7R=S9!- z_YadE>-AyqcikiFK=&tYJ8k`{Kbn@seGb_Nk38S5$DeLVS}vGh>(MlLUXIQ0NiC9k zg7v0!Uh9pO>i(amN$eW>R888ySaPn$?Y!GRyRM^CJI-<+ z$H(S>l8^h<>iciz`|JJJm!$~ZloJ5A0zO}+Ox{hS8Q2&bXRbFb4Vbg#+N!u)q@r+%od`Pa#*@APt3 zhaNQLA2k2n+sPm5W9sPRlnnKEs+oTSoc^Yb{Y_m1oVuZhP1!-t{?H(kjxqVhn08Ke z=7eUN|DI{e&vU*B%`p~?cQ`xE zzr)UM)Arpa|8D2Q@CVMv;SZgI;g3u!_ZYU<1+I&V~zy7~2ChcJUwNG`M z$y+9Lm-+XEb604b`8UcbXVUVaap|g|?Lxn%>ST_48Q$OI?H?-b3^D1W=HC?aZ|eWY)%V9$UETkmd->s2fdL^YqH-^! zV%G%0Uj9OB-!k*fc41QPajYZPZtbniiFQqD>cV zy4a@sP-(H!jkfRedCu!4=iwhbUeEXQzRv5s&g;C+kN0`M?y3dt zvk})D1Er=ZP#3?${;i4MiO+UI=as-`{ayk7ll^<#d>*(a^d)S6iFka8dcK7HuOI`# z|KRT+{(^x(a2Wnpn|{H;p)tV+LSusu;qRas8+-(ESYRwZ8y9>mG!ERj;QjIAz>Nbp zA^3D?0=NmmXCa3LCV;yv_-5!baF+$&fgBdN4BYhKRrouIziaR}EHEA1?BMwRv%$>{ zPJ|p5m!X zySP3a{A|Es$lnJS5Bz;Fe&A8aqrs$sozUro?uU?nf{j1p?~~v?{XT*2C&3><4hwt& z?lb)T6MA0+O~O~eff$nzAM?<_eld>>48=S#uz$=`0|(%5V9c`v6JlN(m>BcQz@(VE z#H5(VO)~fkV?H#4W2*7D9Dg845#0?BRTKkys>1F+1^ z0A^V%*A}0|Jt2Q?b{_amcpo+cc(&-!KJoAH{%{uf1;C#_WHA3Z{3VR>E0}x?e`BvP z`NG%W{&AAvFTOIS7?^f;6dA*%b^|bd;A>2*_j`DM0Yxl+1enY4R`4Y-Tnl^<_>GV; z4+#DV>UtRXF!*2LJE0qZj{;wb@6-MS_&D&qxdGECI>%AhcHrIMHEw$a?*hMH@Jqk} z{LR5SzYmhHwPfGz;>@x;5eL?lg1CGWBx9@`oF>}%XXa@9k6o|G0T1i z{aR0Ft%qA+ll8JsiQ7;)`?}TRiIs2jcw*(-J)T(ky{PL-ydN_s0pnfjo#)~C!27|o zzm^YlI3EAUF-g&92dtud=hvz{LD+j zSOe}JWXvRB_SajOU`v2Ifc-qLejW2Jc#gqk@OeG(PL$Q$I4$;>x1A`_nh(U9+r(GwaJk(Dtp23HPF0aHm#5>6ANZqX^MPl)V9b5s z6NJ~f&>~F#x)p(&B`z0v*3^4WCcdfwGSDfRO<#-9- zfwm6DJPA8<5LEUlUz<|@nH%Jbpu?Quym19^A#`-kK`NiaXXbea*urPe`-V3a2Vl3LFZPa|=nnj=Q{>1s5AmfGcA=Y-K0c(C{0&8DzJUm!u@ZOxPINQ}m@^mP zf4@!5cKO47DRC5K)ef&Qt*Fx=;?RW3via=ln`wohR8|t~;YIf&`|Y^sUCjZ0~C9!Qw|^41(724J<{=-DZPzS?gE=2}euiM1`~Jg)Yh4^)n)BVF!~xHg*l~!T*LURGSGzGAi`deC z)=RAQGOwo1aO+Nj=rBIS8WUoTRV(VfG7I+u*w@tq>sTY!vBp@bKC$K)_YA*^#s9K| z{WPhI{$vWadlg{%#(5(LSo^mSSo6F{^cf%Cf9-u3_lYoGDm-nLd6>MmcQ^0{c*gbO zc#M5dhgfyESJb{p0Mbt4|eLrj?E1ZHwb178$F)=%lVUQD{)*2e$#L=#xHbqy=Vs3 zcG2hgtK6JG{R6rufsXQ(9&Q1q&zKer@z0*by$S3S4{37e48*_xNx*ywelIW%x9xt6 z_}|}j_o2kQm$?1p0@TaB;*X~|{Rnb+GjJ<(G(N-;IJ*;k2k^^J;X5ucO#V#+EuyEXDuT79HC818|q8!}v7K#&0oS>gpW? z{ZF9N4IPaw>%H*D_`Wsx9$<|#`AdIsTY!wY36R+gs`3Hu$6S93sYo3tTwlsh zdUcIBDS5@VaO~3#$Np*XJQLekirm0wTsw9GpY`-RJ)2#?+P~ev+P}Kya&Baf(LQr5 z0G$EV#yoqq<2mOy6Qiyf)7^bRFLbo8iD{o_II(fgKF@FxfUm&jk^3lO&XYXPp^mN* zEUWP+W*>2lBi8c)V(n96<`wU`h`p?CP4r7e1rT4i!l~qhra#$MXXOKS15MqGqK*g5o_Ft)egr<6UOu_uu}wHV>lam zsC~ygR9p%jZ7=mT&Sf4Zul17mvHCz>eW1Sf3$gO$z*<*@hZ&!*VZ$=`TAaWwn`?4{9ps(Yad$TJsx8MB@ z$1^7O9sxSKx1>#$jbLuq@%|k2TcPuh336@;%rRx}Q-HPaTHvSh^qKpdS+o1*ACBcV z=(FwPkystT8IQYh&3ZZC9tPhjHaSmrdsyRBjq*Jn-z%8s?Xf6xq}1JOBmir_?}q)S z5Fh4q8h9OFnZP=}_!~3IM?JpK5czmnJw|RWK$9H&qr^k1Be7DE-*+Ip8cjl=e}ufp3D?|=0J{z*P{PuhjZ)-%vX0+1kT}GfO+OgKKD0Rr*3d~`!V#r z;LXT4)p;IR?fgM-Ey~jVGtgfvm^nkd{u+!M;kV-+txfoAVepsWne)8&d<8fvKC>*b z?g8lA6L@x@KHNFV`Lorl>n!jZ?-}L);wE6bYpgtXMX`=)PQ~9z;9xY55D6U&WBu%qt(Ww3ZE)Gbv}RIjZ??0RQAM>}kOVV1G9JoDW>)`MCi)8WZBxh!6ca2z(NI58^2(d(WfzL)pXN zw_zUp2e;F%lj(;2{5Acxs5;+3OZE)hS6{w04DWvO2)cDR4w9*q7+QPIQTsq^Eo zF^>bYAJrfF_9%R#os(jR^%D2u;WByl#eY%ud6eZ@O+NN@FJrtAFT>gFE8wYf06GVN z%e^?JHQI)<9M=~;=GGdH z-^*vZIM;Y~>VP?CHlxyd;2ZK>-V$p*vo5tm`}CRlumF6&cB~1oOzc8l*19 zu+gil$*YT4>r#F0QJcZ*p0fp5*N9eNo!_XhV_faC-Ztt;yV^Y+<*CyFUh}Hc)9(V- zI1sCEs!!jDwcc)E?MJmk`#s>*2gT&szblb5hrf+A7npmt#0un>=<_+6&S^J-C)V?M z;%l(ZE)xF93-BA*g1HA<1{{m^Pxm$CB{A#A#OWclr)HrZ&F=8|3wHq;3 zJe@*d)hPnjz99SaBXz*qcl91_@Gz!7tHZJ1C_MAI$;0$P z+eKc_pUG>z#8Y-)F3E6n>xoA9?2>!1{}oJ~S26Fk!G4+4d-^+QH+aVCtYD2*GkC4H z1z7u~6Rq#+>?!+mU;^ zFRhh4ym0~cpU~0$d5?#Cfw|T*H)1iC6gPs`m~$@DcWD?y)#*e`ITki9!a9k)`Z)|< z@0)mrpyM$CHdUt_bF|_#k57OP=XeiidN{|!Q4e!o)%epN#q?8gg@@^n^7KJ5eNdd{ zVfvsv{a0KlJade5j`BPkQOr0fuJU|iTa;&86qkAa)Oj`=J^5G10wf zk!PP+`iCA30{Z*3f^QW7$T<<@k&- z=iZ~j!~Qzd#LR!ztC%`Ec6>}79Xpn%eOa5>N5te+hx*Ff zvYtNsQT5r6iglcFy=%Zcpktr?rSn)XK2v?>w#JINO`c^H(;vn3A?oQUrVgK7@*e-p zEpE+X4F3yUP5mNwCRPKieMDaSv`%b}S?uo3bFgOFw#&HVexm_8+Fy+xZUWZ%s@cQj zwU3CqDsd)Xg*_Fpj>ju{R>7l-~o%L$&sU60d zKIr;Rp5uu5tU8QCFL+(=w#uBzwU|8H$n})=weN`4j`Fm_wMPBqdB`~o{SWcIPS#6* zG#}c5sl)gaYfNHsrm5{B);KV3s;_O~K7qWBS=!M&B<3?s>JV!jc6z>T5VKw`Klpu?-}6z zz!QIoGQbCcc{Uz6`%*Lb+j!}T6+@NlDtn>^g? z;T8|K3T7X%jaTE~koVnf9$$yP)?7Laoz`b?A9E+}$AJ$6{|mSs`W$Pt)8XMx4^v0; zlRAp2qqqxLW7UAUKy$Un^o+K;B~$#0@iU+3atCjGGO(S@nPGqUV$@YV9qnd8bi(%#JcWOdS%J$*eBNU z&Niw(vFc#TK36Y!)jy8D*mjjMpWcJ}8rWy<6_(&$2mPUE*L7aKyl3S7X81?A#}}S` zr{~gz!fUvI*n~ct)IUB7&w^^P#}J-=5^EfYH4bT>4zcPG zt4=1c#)Md7!n$sSZ}+0E9Prw%sD}$ZT;$G4&-ItNlmV_q#fY$JJ% zIkCpJ99UypBl?`jai}ve*b**Ut)-kmJ`CNH9p1VDOvp~dz z&mX&i&jK@tzW}BkU5km;f7;RY$m$$&b)7_gtc&lo(@%ZhDFDAeKZrAe2XQ6|T-WAu zgZJ8;!+9Rq2%EY`B3A#2wcaLR?NefX&e9(?sn7Yp8NALd#JVSm#b?SB`#je>-pf;8 z>uvKepPLN21v&jN&h~)$?E3{^`p@Szdw|gXkT-K z{wOA2^Bwp83GpSX+BSa;#`)8D|ePJB+Rb+zJ}Sl`oN*=rxc8G_hhP7>=LgIM#Hm}U7a zy9by)^I3K;u+IO)>~}uPrjGg?mK^vA>e`8UMxO~TKpxTuo~yIJejN;$5sx|i7|!by z-;U>uBf+yQ_f#doI#*vOxbZocw|s}3HWz`9P3(&;S&+tW&BsW=PA@tzT7KI ztYyo9b^n$xeadmY7k|3frM}u>%=ymkPw+XGGjk#z^V-i`OghnDcL7&GU)P0750lsS zvadhGS>M-w?)0_o)K?v1<*7gU3d|ehFejjmoD&wJEb+uu#w-;(Y!}B4%POA7n0)N! zE9N9|09+MxbZipq*d(S7?NkG6+iQepzmV54MPBP7R{x20uB!vqw$uaDequB3Z3WY2 zBe3RP6R^%9&A>VyiFG`-0Mic35~~kYqLTyv>4Vlw9qkv^OMUK1T0I?N^_f`PQUR=W zF}8}^JY4T#mQ@{=WxIGr-43knBGz%o_~;zSvOd;!u`Zp5iG5z}cYs&>#A=`VYO@QN zzVU2%isby$<6SQ4`zj^iIW9)N;O3Oyu!4G2%<+BFnB){{;`v~s&wY|hAaZmki z=o4Q%8~Y#N9@MM3#JuHuagU+iUhvvSu}OV)$g9nS^LXtS>S%uvYfPxGm^KwtN8>hl>>{Hn1Y}k!CR?2dY z-HKQb-S1)^1%DatNB#pH;-_$Pqxiae`>@8p5ITB3K&<{0d6;%8SL1(Lyoh_}^Y~jI z510$UFOZy>gZ`)f3!Cun3V5EmsZH|#{aL`2PQpF%d3J87$GhUtr_LSA0%knMAn{tP zSIng)%ssrr!)GtV#rPfdt)l-k(V;%?#hwFCf2tpI^BZyUU$Ey9emmAFo)4)0e(-Cd zLq8c$>c2Jt^?|1j@9C&R{+ehX&oeaTnMlD`n~BxXPA z_^On$yeFj&eV758Rl>9FG#B3w+27JbPxem_5x~%VkbG#qL84=&Lpw2qHzp)7V zx;A}J%KmwsYa`?HuEdu328k!}E{WkLU_BFHPU?7MTNGCVYaECRkTVaXF5>&PVJ!x( zg`JNc>l+ugUGSedsbfl?(LX6=8I#M$V2$wX6Bi=3v`_rq_0B%A>eqSt#5-eIf)nf4V^M={GP zE(2EkJDD4Z$4Z<@F+Oa|bYSXmj-}7EulI(;%2WSw-apK z{qnKQt6CT1#ynwLcn-`M>U!D)`?{XeAN5~j!tq6{`pup`?K2NKmKD=C>gc*m`-)qD zbuCy6pFjJ5n7e-E=D@sr@qQF=D|B=$tbmT*&$bEAJsYv^>xgw<*Dg97`yIf#PVtOR zZIVBS!RtCs9bHE{p`(3Aea!)4jSsQLqzhPM*bS`qiM1?sG!C5eFFAwzm)~H|0G@Vu zf7c7Fc4%Mc2l}Qtuoq={FE)G+VhOBuRUj^kW50&)tm9009dr_aeO})gppL$8pk<$h zK68Wm?DsS&OFRSfB;N()eRDmq`bMm6OgN9%dNVyAc<;t{^*6qZbsxOOFbb?~C)QXM z0;_#Khtm7dA}Pzbm3n25Bmec?Gh%(;jM(5_lH+BQr$ekd#HzE~!)36~wlJ^CJzU}8 zN?`5BDqw9(HL&jOiFI#Z1I%`D?AHlqZWC)v>Vee`vBrdDIhQ`XA31|EI!_*!e6Zsk zIvRiKYd$vu>pVlOJ~W9w+lXzJ2|tZF2YcUU@ajLY`rqQ|w|cnE!|lLYZwIj2p?&U+ z7ruu3eaTza+v%0f^!8fBngd6rjhx%NMPIS%kk=UY0Ba11HHN)Xmi01k^?T4Kfzxj8 z`|bu~*!xF!{=m8D*$*&BK}Tb&xZUGF5IpL6k1_tZmHk;-i>8D5ej@>I1R*K&(EbdG;044zZq#66<&Li1i#&^;s9O)Y@9D#D{HPC~1<(B+bq+3f&$)=zKC${ntaTBqpTvH>>NEMdFS>bvSid() zJTA+~3tBh(Ka<{{7DN`cwGJbx<# zp1;bSxslhtroPritb2fRk0(|ih|l?e{r1G%fZ2k1i)ZqGz0tLicoQ}VocDZ_kM=VA4kbsay8GiuaT2OZkyTwM>W_E}cvBHGlt z8iCb5vFZ?0hjS*^DQ$Zbc#U&2u*SIsSmR7y{inX#A=Z0FVy%~$_q_Z*1+lLCZKA_@ zqus+iZ)91{y{+K6&*AqGs6$LU9l#ogPGF5g7qHgb?P2a4^*-@5u<8((A`YyJ_+R63 zkM@5!I|E+cgWo#>?k{^hK06?v+Kc-_@G0Q8J%smwMW69uZm?eMyD)UBcHuXmppy&C zdf8s`w>^mcAozOh1JobxISan!?up3jGbuiYeeXHkucGhSmL>4v6VyeX{$B{5SigTn zocu4W8Q^>1kH)GOSYyR|&m8==5`XK1_?M?K&Y;hFHGgO)5%9{`OKJW zdx^ELspIpSd(_djj5>;^ASVBJL*KJ?KKJf}3TyLZ=#V?OEQ|L^`jOq+?HA;-n$Qx`ZKI)&e<-|ON^k&nH>0krp+67& zFM($Q(?0DS5Ipw<_YP+!+N<$T1J-`wT*v1Z$Dm(^vez}b`Evz$p5O9!HkQDCJ?u20 zAINXS{lUYZySmt4j{)a+HV?v1GsX+g49M*L? z3On?lxysyFSA?^8l+DL_7e?;g0$cFd3ANJYgy&-MV&TaU9wAvw0J1m>9&Xpy;`|duUbGP*2 z$YQrQpw6OIef(8x`}koi9BzdUpKI_p$`~uQh3CL5yBOcUu0_2Fr7617{T@Oic-XSnTF@ z;$L7RG#7Ij@gCrt1kap-SO~re_%^{`U?6f2K>b*Z9e$UAxa2oDi-gWuVD*Ri7Zt8v z;#YvVrV!tO@7gGS!o$tLTra5en#ZrlcTw*b`%eO|0q#T`v@YTcke~MVa~Lb++k!mA z(&IV*bYZTu{v%$Te>jJ9iye+@&LJ~|Ctf<-{f<))bo4%`*V8A@_bztgew+HtjXwd$ zraIqflluytpA$Sj&BMH3;Jm@}lT6{~z#g&IORRH64zT6`&omUzLm576_#WmX&QVRz zyE&7XzyGiqd=zDMj+%lvKZED0Z-Orb&v^1ak+`j{Z@wjOzH+>thwwabVl-gB4}JsY zoN31~#(*~gj~nOSaVmlx>NJ8c1*Xm~fXjfX!?~p#nD!Z~3J+HT>$s=_*8Hyq)_4-L z?cB4~0IQukVBMS51FM||V70^V`SQKf@do`T_-bIz|KzU+=KUJ+_ko!|#E%F*Gr#XQ z2MVxf*ed+&Vmw#)%;f|1e+ypw<$YkDGfW5n0WhEA@V7*c3D$QB$R8h!_b`ON77k#QI;SoaC!KYkEv4eU~%>l(k`N}k`BDi!@hqBKLUhu47=YVe7%_$^nD@AWW!P(OJ_mp#Efm*I0w z-9vIb^1Jr@T_EZ+4*Wd!g1$H~RvHKLdf(9wp5vP1Hvzo1mssmf1J>AP0&8qfqP=-& zFYigiD6eHp1n)x*kl&BK`)3^9hXej2a$b4<_FXUd8Q|#~`F!F>IIa;}VA>=<8~BM) zeL7$N9R5Nl2mWZgXg~F$fEfxtdLAEupJTwU@N5=(I&8nzMLT=o^AzZp08bp@)|Xn* z;d>_3e+vEfb?6X3x!v8H6K^SX-zOsGcRAaTBg9D<<5xkS_VxJ-dBvRb^}eqNzA@&H zLcbK4G3Wa*#M*b%9!`iH_htUREZsq(`sNH*TkA9eD_t?NAg-1v5uV@V9j%4jTPtf zE3k(d-`~yccb&pG8|UsF_`557PmJdb_eStn>u~*(cpd=#y=coW>_g@tX2e=A?^ARR zCuZFK{)N-I3vr!15OY3w#rsjV7<1<3;2YqZ_E#gY+HCSLc^%8-H4enu-ezEJFZETY z1z2N69gRaPFzaHR+dSOv>30av_A(z%w8Pgj%u(R=oi}2A=Z#qFA|A2Hy$i>f@OK(Z zkc-6p?cX_qc~4gX%$Ps-T^I9Cl+`^?H?YQpnC-Rio&mGH_T4jJJqPXu);$3A83*nY zdVsa<HIV8{b6_tMrJad+RjKe|bA4L8b#xyq@P;jYX&r zWzVAR^!Z`I3)bUoffzdLUq=jp=>z9#ejhmpnED)3Dag4J1mF|s5SQY+@c$H?iT3iW ziTr)HVr>;(=fhm^Z({xWH+a6s_*3{9I|#W1Ue6XzBewi4^hEGOz-w%Yzk_>$LEvfs zvE;tGhW`+IMbW<=I+p^|4!HE#w?pd?8{oCz)xP3qJiZw?Ds5*@5^Mhw zYyT2!tO|kIM?AkR0@gk{jC%R|$)&gO!#5&%ouR4{U4zcRc zj?S6XSG)o-{5$e$&S3mbB6#}KhBj6M?|d9N51#n%Yw^8u!FN0nFw21Jz-!)8=iRTn za|ZI-F7i_!bno-%n!^1U`RCUK%&q}A^EyvI)QGVtb^Y-k_#=2X-tky5#Fgc}F#BRH z{O5f8N9gEpKphi(o|#aGb?KO*Z#uWrAN{Qc@*`KdGa~BickYN;*Q?kEQeS^>yaBwn zk$A&GyuUOA>mBf)mb-g)V)c{w2S3H{Jm=!Q0^#`%2Jbif=is-vQI>c|#GOwvChQ}Q zePWGs4Y2Nq>9e-05jeQj`9Qq!X`Fk&W;HNBHLZ&EPeEsPo!_fVl_t5^r1Wa$qa^i#p5!@)!OfU{;Av`%2gE zn8ob8|xo7+6jvRG#F6t0{zI#BdW1Kp=?yC;R1$pXl z|3J(+gnN%pV9qn#dvpQon!>W057g1Qr5jlHC)Clos0Uc*t6pHuTk=|#G3iFGp3T7g zGYC%M-KylF4lhSO6IlHruWcc({*c#P zqCXmc^4gC)E6{gtd8W~YvkpFY&~uMrneLoH-xZ&ST;bWsP2h7po5boTvHBSWRzHc= zH{Oo~zv=Erc!tB@{yl-SJYt^j@izvMn)dhKz5q|m-{R))B^JVtwuM;pfmrj0Slix| z;Pi7bZc4%HZ*&vuZ*J$KT_-+Wuni1=h@B}#Gx#VIT_`3UqPNWZw6m3`pgYtjWe-61Laxw zPdB@{n)pXc`>;NjA-@NE6@7M~zlTp=bD&b{YC~Pb4=!Do=kV3g(f%SHRfTgK=+Nftjc%PKz6|r#eyO+oDOVTyX6V;`;rz+j z<}mGCfU(Bs7sMP>eAYnyYY+!M;~ul10z4xKcS6J&= zFL}N9A=djx>Reve=i9reSN;68#Gg7x(H72!EX(JEyoVyM&k^c?KY?%bhgioZ@eiQS z@kqQCb^_S}Q{(9ncO!o||2N{CnQ>;{QGeOfZjGo1)_XMS>s&#sxkRkvg;?`}_-DxH z4Ae`T9M{|jGC%dVSR27>|26?H+JN~S`pxI@XRwDU2H$cX|JAmDxe9#id3+<*(^=r# zJf1pw-$ATnpEebZ=dLdPdX^%oy^lsRNjEBhQ+Mb#A1-o)Hmids!Fvf&5Jd^4ebdqdN3~JimWO{g>|O z^G&~BMjbwb;4?B?*CBU*N?voI)x)%}JoOLPVO>SP5bHa=EUR-q>m~k&=;)kGUh||4 z_@$q?b)US(ikM@0Bg&q`i(Ff@jpViMs>7Hl<~&c#Sg~J}e+Bcg;xUUI<{0F&UDf9t ztC;&$#cVIJzIR8g_eaF^nfJflz^jlC%>N!>Z7=z=n8)VkI9_v%I+{zw8fRjSo8_-} z_s)ztW6QlwY`U{=_YWTC^9#u4on|7Z&U!Q50#?Bm{$R-O=7K!SjR%O=+l0UhwG>#Ib08{dpu&@%QXPs zva`NilL`bnGz9p1|jvmY5lV#b_pC)V~7YkP^cy~Lc)?|?s@z*oYbb%Hk_ zR*L_Px)g7R5A=unFCd0LfqoaTjtlZ{A#U`O_#cQZ$}n?N46E2=&=U&SS)!#}p5M9p(Ue_6vEI)qdpti{>GD%`0LZYs5M)bpunM@g&x{ znpn>ti1iGaSkIYxrlDnf!24M9lREcqb@v_nMW5s7An3lCsw$ zAK>$Q*lfdnDE|*p6j=A_$1zuIy9#}lhqDRrx}PUjopNA}VFj?puo773^D1C%?>zX% zvUkIeL`0rDEFdQbl}Y^qM9 z=re{*g8BbAnt?U9iFG`-0IUC86Jteu%OjXuuXcFsVT?oAAwN%a5>S`6o%gUjV`SMJ@Y=4Zr&B1HbJ`Tx z(X#yiaM~{N7yK0Onp}$K>&RE~-vpkGoYd#Dwc!66#{CcYQk2!-DP4fFKkOeckAkNU z8goA5;O|H61z+aXwF3HQ=LXC%@U_4^pA27yaRtnJ_aRoq`u$4c3ud@y(1$~~Puz&- z4WhsLaok%1)8{YX+brOnz)v8zIo}dTig8v+9oTsfI+eiXp8plVZ(__+pP2o~`|(Oo zr^?eIR-N6j`8xJ=DMfgW0Iabk{>?CO;H#mpUIMUj8vLDMyk`KOzD>bA+KRErdK=2HKLmdo_y<=8%xl1B zfv>+@-lGG)6=U!qc>d1w0-WFS***37ThQ-{{zLGA@nqRU;^z=tGbZmtXE*R##Q!8P zeK>&i?la*2*k|%tRq%3$8HbQy>ip{a$Pd|zF2Ni%L@>uJ<1_6z?oXhf3x4<{7jxR| z2fS8zeK(Bl)#s$d`W%z`OZQ4Siy5BeBMz1a`=0<2lU?;4$dOF~GBdS7GkS1D*%Wx;VcPUyaQn zNRBq{sYY*$wKslxvr?^Dp`e)Quo^bz}u_=nFr+yZ|X&nKYM3as&@pBhi{`X1du zl-0HqFF_9c24#uAKyEO%S$3(!mYA{4#r&{D+QQi04t)4aocp6JarM{m`-i}7(5HQl zg?0~9#|%Pjr(*Bz@!VT(+KuS7zap|AUp9>Jd@7QMha zAI1*pd+xjwWd~h?SYL&6b?_Qz;{Ho;jwJkPJnP{-0(tJAuLr+L>Sf$$Q=fs-=6}aJ zn`dRe!8p(dwNKv^r+IY|Yh4>)=c94h^I*Q9o#i*V_GUtd`yb9NIlzon^lChx1=d&* z>mG79>@@7bnJ{#S`(quf5?u2Q7xTT~xlg+TJhAR|slUO~;qPq!Ky-BPK|j9~Kk1Lo z^M$~ApWB4}gznjjz-!xEv1g|LDd_)F@algqVmf*y_N>#fj)T{>@LrW?s#k)i9bLz1 zNAH&xAl9|FVNOPjnUnkBrUD?ME0;c5@pc(~5P^&W2UaHEH-Fn+aPnmoQ)F#DZY z`@O~EiIrzxC~oz1h?P(9aGR$?tUBx?#jzgG{#Bm+tC;<(IL*^<2iCsp0M@?i^!P51 zk7EAOvBURvVjgh0t@tJ6`Du(1_E!R)uW(PO`ycA?`)l6;zaO}^2lr9H-CkWiUS0Vz zcKAQj`I`WGA5E+|AB(+)`rPZ;NdT|^JCaz}KH|ai-M%^vSaVx3_Y9mrw=TgR823MV zzrP7M)6>uKFnN9dfxM1EV!aoR0&Cm~1+y;d=-f!GIaUPBezEV?0<(?w-CAIsKUuG? zL1n<&7Gm{*SbZo5)-|X?^m)HY%({3sUJ1;)xNofj)-gz|W0^YY1MRC1Y?qEx`lk09 z#C%tf?IqT|akc1^C%(GKt?9&@E^=#qji*xwynl_`hY+8>%iSvxU-GEyQ)0D49J3np zdIj#?fENLC-X<;ww$F2c=`($(2iAHUJf2wjDV*Q2XM6$iISKp&JzJetEWS(I>f5e zCOVvN)d#*$(j3<}j@mFr=o|TV&t`{*J3ZXx;cgH2c(~WYvDjN{+Y>yT=HW~a(2pF3z#UYKQvjXTo_{eI~Cyu)kEl z61?UxdDS6*A^MSfPGaTLfVC{K^3=KMZp<|~Sc5#i-NW6mukRiZvn=N{>ZqR`z#2DV zjVI6ZwJz$b51qgoLt?HSg(Wx>KpgoToafSA;2A^i;k$t~-+F+xt_JLHb?-`E>m{#i zQ?JLz;`yQKBmip+iB+HY+`H4j^ufLh4Xi$70;`=IU|oO7t8YUR`t0*8Mb9wF^ZoFV zD>1(V^ZawH;3?mA=fnA4Sz=x9h;s_ud0-TEssDu@F782cVy>ESA1mgTxu?co|=&3R(&Ut;x*zXPCoLSFleSaZYja!*0~nj5TFG5`O9 z+N=|vd8nB8K@H%y-yJZc7hosry=Ze&4n^`Y; z#pK(OW9kq2iJLK&)?-~pS=CoeePY#VK|LB1V(Q!9CWTEszvcVJfjd<6AvUUhfUtgl9fbM{N=_|GD1wun|_~{m_BgrrhTx+wci6wf3#iN-YLk*H_)cj z*xM2F8O@i#CBU0-E*g*La|?i3whb{~BAD^NUGOC6)Cy+*uJ!N+4{!4DRuAv=@NN(9 z_3(ZVCz%`*!k#S2j5gO=88z!6L#EKo$Nni~CYu}JU&zdo>vFlCYo0~Frkbt7?-SW3 zpC7QFhfKT4#Qjys9J1%eA#+6ZIz{i8=yi$S+<>dMBH-$sA7DKpvsm~l;g<=A$V$>(k2=Ka?V)?vEKCc#jx$rCHx=ya^<$AqbH%Pfg%cq*n^7$6| zyh%Q97JirTd*r%RuG^&i>%t!pzTK7&nzw^7SvF6t|3Py&7-z3LYg|XdZDn)Zl~(SGn`325+)^v|#NBUYYuv+D z?u%=*vMp}Al?USXS=k=<2P+T7{msgbxYJf1iAx^KdOG7qS$Qn(Qpk|$ikkryo=*SBF9>pWUAvw6|nsB_$#ceiT{q3E8}-qSr`AZm22YPv9dn?&sMIF4;I=u z#9w6P#`x)0HpVZta!Y)pl}+)lS-B(rgq6+l{U@;8uK0;ow!|;Aa!>pNR<_1(vvObj zD^|9}e`Mu>_{@nc*B(FH%0uz@SlJQ36B6yU?FpF^@oxU=jxU=;{#5*ID+B%5{-BBL z$NWvjuRyqX?6ea8^n0zC_FMXOLZ+I%^7%n46V2g%@0Qr|A&!Tj=^vtnb-e^kRh|7zpM9skrxf%b;vBWGH7leK)Z?Nt^uqs z(X6mCXzm^0#^GwKmuS|?=j-J24f6R8vDZA{21C2M2Dm)mBYdmy`z#N?2RgrV2VOCo z?HFsXLuP@JnF5xhC-u$dFlU>q9&fH(7pt;tQ7FAbdmO>$pxdkK1~a%*Mnnd%elp zO*ETDZWZ~wl^73+|GJI!-G#e5$`w|ontPL6yeE9k$tfa>Mb?U3Epn}uA#+zU=aZ0G zVI}f0nfVYjwaG3o8=gN{L@XxiKZh%EpvJE4QS~gbbOjqTiHqv*lY-V%AcBPfD_t z2T~?NrsCV%j7O3=l=2v^gXVY&`#IJ8Rs1?B@?$GQ_$IfD-x=|%SK?+;UA%HrUH;5Z zb$XSlF3yWnU3{xVZ&|8~YjvuN=kin+&ze*h$2uv$M#|Sq`SntMqkP^dpEt?%4!Ld? z{avEpBK7T&`dX#FeNtars@o?XNOk+f_EfjN97=Wj^H)IVC;lj_!~Uh&gh;NlXxz{M#+u9GidoX`&!#MyE8v6YGD zcpBGVE{XwpL@*o%2v_UTZneus-d_G#PqxKr}zFg-?xk4#7d64UmDN?Rzkn5My zK`su{2RZ-C2Dx>1)}Y55=}-BfC#<|)%2kN{`Ep$;^;8MJO!#WymrMOCgkLFob#lE% z^y)=#o$w8!w^6P)i`*i7lUz5;^)9*IBiF5R-6q!uPg8G-=oO`Z9oLvA(!XbAY5F=V zr>8etS(g4gD`%yT-^OyYrCfRX0$eAVx#{;?S&_cm%K7Q-R#v8;wsLX$$R_Glr59Ma zEd6pTtJBv)V!ju9%hO-5d`sZj_Rr}&lT0$l(wExL zkIUy>>5p0dMEXuEyVDO?c`E%qD|^yUK!(geMgNR#Uy|ufKWlk&p&O5JB8OO+gtw6g z?qq#=7p6moOtE}kcVX-Eyxw)88_zAm@43*8=hh23o|DYW7b4xDx9>uZ&s4Kt^j{bK zgZ4Vfyl?eV%~ARML;3u;T>n$#S&?6f3=DSl#|?J=_m}HLktxCtlItNNhY25+>x)F@ z3ZF066GToCSuCNqb~8SUgkK_ZsmR+!t`oW0 zN}Sus^)n)OTA5_x@Z_Cw?{6jULx;Gy6c2H6DiQgN$er@}^K!jguJ_9I%X0m?TpzU8 zNoEM1e$($^8MK>d!Wq?g){8exLuPCS^C8vbTfLB}61gnH`BiN{51Hi| zZvL;yaP#=e3^#w*Ww>>9O@>=n>oeRsw?4zIa}618e&3kk*15(Ex6W6XWRiCoVTSuXl>hq`fJG1T?r{GqNN7mHq%=q(ey<(9{O zAEzpebB+Bx$=oZ~wNkE0h#cG!#$Xn*oBY2Pr{ zzPIK2q{zT+1=^YRhS z|G6Wa|MNw^QuM1tE|YTAqQ88EYgdi4Z`w8P&^bUw#yXYN~b{&z=JMHJlE4e-{vP#SdxSqD zyvcI$4Ty}*a&Zo2xi}|exj3iE=Y#F%IRBFCERi`OLwr;03>PxG@}K2>J9d|I64 z@~JAz<h)${9hy2_44_8;Txnqjq>?sky}KsN%VF|`*+FbE%Nyu z`FyYNt)jP2?6ygq4oH03MIMqk9hPz(60aj7J0)(%M0QF1PDuQ^C2prg_DKBBNc?&w zeqV`w6LxWo4ZApo!Y+;pVHd|_k!fKU&%t504rPYjI+PW5>rhVEtwW>3ZXL=EyL^j= z-FlQ4cI#1L*sVvC!)`q)3cK}as>sr?TaTuP-8d`@yY*;R*sVw9VYeR54Rbw8HS?sN zim+RkZj|f!;RSXbsSLaIX>r)CPgP;JJ}nEo^{G1S)~DrRw?5T`-THK|*jX8N>r`FX zty62lZk?(RyLD=P*xj!+glj(Gc-|PUwX!k%fR$Uq4_nz3UT@`&@Z(nQ6#LELC+zjE z@XxGl2{&1}C%n_jy`t9|e!*Vv3;)u}w(uS+4}{%3)gFG?ULOj-YGp_GcUB$=w_ABs z>~w|?+v{WD-&@%g{=mu;;bT^IhyQBj$D)5K{11EG6aLi7GvOX9d&6g~G?D*U85@cD zlhj{MNdB~q>`veI5Ji>$V?I`WW}%Oj6kSrcina%E(b zm35J)tXvb>X60J3Qyc`TA*WmhEK$`g^HR(40StUMLD$jZ}VwV_A+~2md)QC#dB_v z<=OYx>$%zYSy_=?XXTCJ_k6jo%wB8x#UiV+AF1F0Py{sI!zX{9dIXSM}=p46S$rHX%%1xGX#llY&St_4T zm(OPkKTBk}d_LEH9yIfET%OIB&nxBgD!E=J*SE{{a*;K{*U9x7dmS`ub6j55Oa1HQ z^Yzlc25HwuX;)*8+kfq}dU&oR`Y((8P~>SVao(TvyU!W#{ujA;=8BwQWvW?XWzg)s zh<>D+i$+o})#O@ z*N@Bf#*wFSon#tE_FB1RWWON3yJ%X({+^MppIb+|e%?2d{hDNYMqU|1y-N*-xo#glH`nc>D{|dFdVa3kM_1;$ z{omqTxBsikb^E_%xo-bgoh$q3T(|G5$#wg_mATyap`XS7x?H#aTa)Ydf9oxuXg1`! zbM^*l-$rR)qitWJ*^>J}ascNvO}URkVjjqK{nRP#IA+_KWIoP~L;qtQkotPW-Wh8z z$(+somi@d};%3IUxW$ffaZ4EE;-5Um#XoI~i~ry;jB}jH9CJlV05WS#sg*fnuCj9U znCVtV$6N!6`)^ww_updgN^2+8%#iDHk=Kj;xuQQ$_zL0Y+jkr!mqLRLT0^u-XNcEwlc|V8M6hyzl(D^`Mhb&?{FP5yQSV=3BO;i zUzh7cV}6bAcZSSi;olejD{D8^1TJP=@t%p5IN!UNaZNQTay{f?#yiyvlj~f$9xLTm zi(D(8*Na?l^+Kk>+QYv3V)hf>*Ae}#B6o^>UgWSS{Yf-)qs%+pAK2?8QxOdh<9MDQ z9dBi2w8YBA(HpF+ir!}BvS@>q)zRmyTpsHt18w~w^LF%(1nPA~*W1reM7LVm9p(4wlFX^-Z>;QzzGCH>=wT~E zV@pRc{yAgWu8tz#Eq+Cm$><;>k>B~ow&r!N8OjW`RLRoZax}4 z&iN4?=lsYU=lm!f=j=`%=j;}Zb9SeWb9PI|IlI%x(QcB-%xjM_URil(t<1?AK9<*` z^NOvE=FPVpcIH=G zc`SdYm0kHAR-VX@n@s)g{K;0H%3ow+ASer_#=u@$XvMJN{2rnu1TQj4cS3@bgf?Kr0gpGObK57-eNz!Bi^;7tFIVvtXH( zSp}=D%qiGt<>-Q)Rz?e6u`;jVu$6@c$E=)O@TrwW1^=;fYQexOSZ`@Tx|P!lF0!($ zAkWHK1tnIN7tFD8ZoxdrL^H2|;|B9Y!OfPRU+@DfD+}taTwL&^l~o11tXx*W_m}V< zSiyVtdU-*&l{E!lTDh_ygx?v!^S*)$tz1(u-pcxdtF2sLaFdk{1uLxFSkPc)W5Lg? z+)}W^%BF%}TDha(RV$kd4qLga;Fy&y1)p2Fry!}6^|cm+t=v~I-paOutF1gxaFdno z1xu_vR8VhaM*+WgpJa{{G+EhM@Cz%C73{IHtKeNLPZS)pvb*4)R-P*OkCi|xi7eVf}7tqPI%}B_E+PCA6vO)LZg*U!ta>y46da(!T;8}IEC-FQDV(T(>Gv3F#mo2NP_x_Ro@L^uC+ zO?30$iHUCh>z?TLW2Yv%{aDXLw;ww*(e1~2C%XNZnIz|NljJ;ZlAOm)lJmGpZa@fI-1%E{k~@FPo8->l3MaYqx5<;-`CHK>ckVWI zk~?>sKFP(YY?6!9%W^$zGI^}$Q=Gg=OtsV1)2`4h4d&q3w$WZ~0_ou7k?ou8S-&d;o3muES}E^kK{yF7~)yFAM)W}e|W zrr0SgcKJ5B*yUSMvCFrq#V+4Ui(S4=FLwD>R?K|E^D`+wtJvjTd9lm8xy3H;DvDj+ z%`bL&S6S@xZgH{8yQ*TBcgu=h-c=Vf?}Fy8Vt1dqyx8SmO|i?rmBlXq>WW?dttoc- zS6}S%Z+)@LzlLIW&bzVLJ$Gy@cIUfWiru+xQ?WbO-BIk$b(@Rbx$drFcdpw~?9O%f z6uWcX)?#*q!GdDt70&9mVcE_eilj&+ROB=efs<-Fa?T zu{+N_QS8ohyNlg9?x|vTj@wi0&T-Ea^BfoNyW$t9nHPh;?($)XmAHSlGRZuS-6}ud zRN_A0Y$cw*Oyf23sMN{+Rt8OCsf%w)Df1@P43f`>hzwjQ-%O1qve*9@!Vl8{zJf8nIkNj%z`#he%VL<+I z@W(v<_<30UG^ktK9ykxHPT1p#l`rvlV&(b$vuW^wIk3RfAy%DQk0(}szsD0Rf867V zl@G*v{sSu?_IP6DS9$y~4;zd(jYACZoUP9PERWCj_^BSh(BlJ$dp7)I+hc&We+K}6 zOKgTcooryOcZ{b~;^|BSz8E$~4#aQPc{&R`om)M=*5g+Je<1!m<8rdp`nZ zUG$%LC}Q+9HjIkj29D|HbiM-47F>;Ws{~lvLaZ^LCOW&Iv%u3KR-J{S^Eq^CJso1z z+3NAc%J27hV&#u}JhAeDP@kW~%7;CkSoso zRcEWm6Dz+BnD(Da#T@175Ub8>qH_#7$2}cl)%i$tBIukzf3NMpsuT8jV&zLbo>=(> z9#5=%t;Z89&;OmNIkxRQ%r^cu4c|`%|82}u%)R}+xGXyx+seJRYYHuk!e<9>3qi$2}atzT{kc zfz@W%Yg%3goILE;UJD&rqP7E+}Z{wQ$!Y=}UQuv#| z2QdHA{_WuV1JnN>fe#D65_~qW`kyO0PeG?dbk;#?`dKUdc<`${eyi})z;E;T*F2n! z&(zLZ-~z0PAA#5g%>EiN-eK*poucy@boO~=k9nAriQ3Haa4s?gQ8Opo#Udj8#=_Q^AWK6 ze^T_fL7$}ZF&^#@%yzv5oh*+J1FOw!kIxnTx1c}O<4ZjKX~4R-nIZarg8qC@e}SjJ z(9>V(>1^@vHV^Of@M|7E=HZWk)dzz$43~DU2mrIcE=O#L*~U4*F`iDAhr^=(J?Icq z|9;?XV9l$kp8hls&-d^`53lm@HV^Of@G%b`2iCqYh~v3+1X%ejj}LqNN{`>-VdjMT z{~9pIi``p#I6K|#Gv{1|dVs0(An-I`jpuw1<69f&_*J5R!~f^%&g1M!>+~U#9C2@N& z2CM8*X$KcXC5S?mJu!4d5pa!<GL}s$Gt<| z!$anmMfYiOZq6dtwjTHRc&&ual-d0v{`G;^v&ihuR^(5K`{`3-8#jjI6ka`NDA8YL z_Sl=zU!fzrvxoea@QdPk?SELteGj?p{sHn^=l%zy-y)aYKMMPN-8T2{bN{%Ok>5V& zZ-{&d`fe4W{y1LSRHcaD&6 zGP`pUxsTz1j_l5aTy}mD_Si-I>x}EiA?%L+)rRh+RXq1tq9ePr6V|o;qn{h!KQs6L zZS+^@%kJ-meIMJO`_GO3L0I!V`tz>Yog?&5=8mrUKCJH;d44%^S*dIorV3l zxS0F*cv9RCqoDa;G~4s*vVB;+V<44{wEi zOqO%!(b19J*+FMFtoa`k^RMQ<&a>?P9{T%p|4Gqb&;6%HUv~ch{lmHc+~_x9um1?0 zGX6t8A-_Pr6+Iu%9k_-Y_z3Rc9=5IT-RaR8(2?C4kx!Z3DdKsa_ji%movp}yZItLN zGrPZozMiLg{sZK)kLM9O9efhj^TU^XXZ)-~zei`t>@}RBKcOSLbAirXVc%O8@%+zg zmfhb9d(GS7J7aBnf3ghwd@aKsD|_tj+}Hb-6*{ZT?(fh2tM@XJKCtvY)rhFNnw04}DksESvrN@ZG=rTCQ`d=fLOD z(J|C}uw&TAZOZKRUqtTh%03se*HFZBLC>?u?0L2#_dK%akv)&>b(Uezxy?`7RV%Ymtp}#kGe*abRei1sd=c&;@nET)M>UceizU=RHdu_5iec0E`+1!8dYvcMyUv_^)|01m4y|&S}-vIv)c=3Iy z^NH}&X6qP!>uj%6bLw&WvvV#_@IAi&+MLQO`1$Y;g%`VNA7!8St+2Lx&mV|$7xs2# z@9#3~<0E_Qe%SZYH9E382Vu|Cq9eO=gw6&X*_{(~dUVc^-x2ovcq2N+gVXv;xPoiA zfsex8#}4@k@*epJU%;2>7Y|A6*$Vsk+!*%Hwe7ICTcW>%{>|v`qFvKTEhu^24BEi zpOpGL@NU@0`5^2y+#2>8wmvzHU1fe*tmkQqxTi(#$ByiI*67Rbx5#DZ8|1R{ld$JM z3wwUq`SM?-*QZt3kJ%bN2)o}PKSJIi-(~`~v-*PfPi3Sg)y{eM>y?y48dA%vNdG)?7y!D8b?}j!1 zn;si~b13Zl!2X=SHSz=WZ$;;D?z}HLw;?~C^Lsw7D7vuMa63AubLUaf>CqW7`#7H= zzaxC#*e`ufW$IK&_%N*F@Uf3CiU#=+d^~p^8l4XL3EabH zVV_@F&z&CjtMOcG_H*DLgx?9@``1$c@$ie`pMn23{NP_tW1k7X0{%64>&>bE>G1R5 zABKMqzWaF^dkg%H@ay3}g>U|iH1_H655Sl3C)}C(&wyV8za4(TTT=g?znT1c_--TcBJE{MA_;cQx^6!9q_#fcM{BG)h zBmC3w|AHU%w$xw4uYunNZ~tEEKNfD`H^T3SKl%65*sp<4;rGK2`-9YfF8q4P(Kx59q>mdJO)zPHQn9Kc8L349jzSlMGQ z;Jd=UzimA-t!F3f{vLb~_MEaiNAL+cvOBV`=PB&tAipe*$sf_;9As~IFYNvS+=lNP*M@$FeuVr4K7%j9*N;h-dkwpvnclI9M63n=M!{J=UnfX4q@-F?EO7MU-q2mVXgm$d&GW* zJy!PE%ekY^tS&w)b!2z8!#Zy7{QUUc6FRaxJ7Mpa>^1C$HTDBBb`|y-WRKkod+h$) z-~NL5>;d|+`}*4insYZg$Fo=A)7eiB4`FXh_Sgxz?0oT<)R&!aAzx;8X9xLiSZmYg z1Xsvq_xF(R&;8%i{vwy%Kb$-Id})i0?9TDrc~8u-y34zo>A0!k%*r`F2?QuCcPl>T$6|NA}nq*41`Gj0{zF6VdhMg~w%g%R@S8xqCaGTll zbjUZE-8qT;AzN|(`tx`#iOvX5nLYL*a=kC0>tXS4u#aJPmdIu2E9A2Cy|C7E8tbXi zS!Z_tAo7#=S^Cf~iPxU!w3*#MLcc?2liB?q`Hx@?~asD&(uo z?yQl^&RgWN^P{lO;|3ksodLP*d_pceU)+NI4LdK9FEhJSAzx*7r$*kuM`2$N9r8_P z&(k9xGP^S(pEA2sJQn+#*_|bF+4%~&?0k(}c771{za4JSX*0Wjgnoz4CbRn|$a{Fm z?EYEgUeAcml-d0YgKK!5+3Pt#-oR~U_m3j?@$Aq!LEa-DGJBpg^e1#=cP`Lb zJR$A7?9Nu$>nYJ$W_EuEc?GXByT2E?=daOOXLkPpc>}kZ-9JL!!6$GJpM|}i5%~pN z?5AV31()zn*z;7#_mJ1f50E#=kC30hXYd8Q^~AKc65fIL;DfN&*&*L#_VMhI51HLL zLq5V&X7`KFPHSFdcBe$X%c2I}P$SvpYwTU;nv`&L-?mk9^4N&WL=< z?9SqIaD2kfOXSPU?o`NEncb<8uQR)IfV_d*%k3B+u9QJYOkZ&@3>E9}ovl*pHv zJ+?x=%IwZbjncbPmyJ!uOARl0KIr`+m}9_WT|4O=fpake`OVo&mY+{)Ak1zWAcF9@+U8@?~as zR>)=NEpplU2D$9~B&_#Qzx)AS z@)LM>oz{F2*7X@b_KNLirQh$!`d!SoeOLU>J?z)NRoLgch8y?@?%)%+htI=z#v1hd zuMzp>oa=X6#X;Kcc3Ax>@)G&(oa^^fN61g$)48MHN!=0lHFiGdH{7Er?h4;KHmQ5s z<(yyr-uBkChDF%taXYN_=(~oN=*aG@kju{Zklzx%{@KN_kI8;m=i>XmJAUsL_VJfJ z&tX`79V=P=*G0b#yDz(c9QHAk)&HgFZ^G`&?w^L$*Rhq=|IO$RVfSVC&%^5LILqq4 zEBaH|ecAoXu=+awvik3f{^HA0Uv_^xtiH~RtbXx5as1Jj-QPjJ8`fBzFIi*n6=PTE z$R4|ge4W{y7P;*F2>B+nI|Fjr`5E#lvpW~a7thAA3cIr%*8b}8wM0jDX9xN2+}E|V zLN2?%KX>#PJP6+}4*wggMR7Rix=xy~&vl#G^Bm88T_+oKWOq)GpU!<iD3uLFeKyjXip9@)>*qZ#^${cHsT69t$sgMXWjOeL0--S4S=@*M4n%MS7mO7xrgS z>ah3a0B+z8?%^|dgs03ei|g%a@&3X^OPc*2rb& zEpplU2D$8fKrTC~NTeAX!JYwz~3el?CWp{`+Ac-|M}eindr;v>oGW?FS~zN z*xR}cYdvQ%R@PWOPK)QKZ7ni;p6$pr_K#z%tg(9hmgp}td+hE!_I)u{*4P{F5%*#A zW%u{z{tf>r9*8KW8*rG4He?0d;CHk`ZJJH{uFS~y__a7I1 zS$%zvVvqj$+<9trWOekNiX%FgbLY9yk=1!oxcHiM{I|n8&bLJ_tMl4$iO%la`QgZA zb$%|q7k)@s&wFKG&--)#P0^9ne{1+~&i`<>^1lrq&w266YgYa-;giTc=jq(Jf8??{ zpBe7MH^ug~h9RuyqWV1Q`P_eObY%6P6&}&QoIB5pTvq3$;o@u4oZDd?pYM%aR_6`j z5}lp!Ln8mIXD$j^pV9p)_;v6l{0VjHKO6oD_!54=I`zLC{&D!v;D)gFD&X=rn_xA8!Sm*0se0h8)eAxRdYyFRld_SzQWAtT>eP8%+&i`(<^1Hn% zUMoeeIo}^+WzTs$_iu`htp3g6P1x71?8p8|So3`R;i8Z|&uLie`NSA2Yo5o1hp^|7 zJbSl7nzc5)b{H^L_Smy| z>@Ck*6tdTI9@acBjCo|uqvwrd_@;Rur_7%7GIFoEcwy=LYYmmK$?XcFM z?`M;}FUzpkAbXyju;zK~SH!svdmdSjuR9~(4QtLm_DlAD?S(b=@n0E_&9KMr&;7SX zUshkwL)KxhLG~IB!kS0lF(`YU!?3pdXEBegd9GeNggs9i_B=;n&GRj>9@+C8hc(aD z&#bWKxq5vO_B@-g=Q#;$o;Sxlr(uo#u-C-35!P5eKN-RvD|_r&SYvm-YEhhrHTFI+ zR@PWOSDC^dD|_rk*lWHFYwROptgNwm-m>_*G*HFMvui`T_rU(apW`zw2%qp;@rqUg(>=Qyl=e@*PWtbNzxaTE4D zvgbJoYo00Qkv-38So7$yAZs2y9*3~!kv-2H;p^{@g*8^!{CQYoubxYXJ$4Fv>|Nn| z$0c_49$Hvq-*`)$yRgRUx?OyI8Y_G37V;azUc+`+^ZZmC2U+vzx?Q3#d!8NSH-|mX zZdmgSF^{ZyuAXC~zY2SvTf!c@AJ*94i?OoCUOnFq`#7w_9(!xpV-LgEuL1O1^lwA| zIIL~ye979D&U+WucY)sHk@4OB=%3Dgog-QOtMeQ7+J>;#c1PH2I}dAHU-H^GzhQ4{ z3VW>Vv3G?%_A;!o&yTUPx4Za;G*ZA-6TWUq4-_Bv&c z-9vs$*xTI?YaYF>k-g?M`m*P_HSDp6VU2xUjFmN3kA*huHOL-&Jooi_M^<0g{|0^8 z{nNSs2j3s(aqjCnAJCWGKSO>V*4jQ8V`Xhi*Y<>t?6DVN|E_THjcNU|J4@uU^Bv@? z%H?9}Grmm2$=KQRT=j6z1cs+MC_5gVUx9A)p z@8AtOC&+tvKxafgAuqlq?bjmg{n`rqvri>DOLTV7snA)Wvxm+a`2q44`4RFB@)P7| z@C97_`?N1x;p^*1Ucr0elQ<9hY*>wa9rkf(kautok8tr~tN||JO=e$*19I8cD_a~J8zN8&Ns+q=L2%t`Laptl%21T%g)!xW#W_OA2}68Y|&Yr7Tl{W;h3$r}0boNK>&a&Nam=XlPw-46Nb zoa@;3$VYez@5Gw*Jo9qy>zEhEX}=a>cecaV_Ys{XIxBb$x9|q;!+!h@$VcQ8^5Uhb zU&71GK5jMgb!K-OO8Ic$NG3`qUS8xrlGkeYkc?b9K2p6xw zcHs)H;dSQg+eO}HcBez$!y{aLZ(7eH?4N@r@+I;L`3m_OZqR9wcgQ!$2Y5thLSDps zZ8ya&RX@jv@W~>6?;nr*Y4)##`&Xupepl*#@v7t!?%@$GUX4Cn!8P2#9o)m`VZD#1 z@0J{q7q3a%+77!@BCp^Y-emT9*Zbh!ciH(E_ItAvI)=N zEpplU2D$8fKrTC z{SCXnL@qmDA(x%+hdsaS$M}H0?EZvYcD~qPJz?kDVb8xrM|Nk0Tz0-jE<10L%g#5* zW#zoZvWw-CrV?o$rRVt;fZ-R_Ms?tdYykTja9y z4RYD}Y1nHX(2?Dlkju_5!ydc19s3w|rwsew-panuEHk^m8@cDK&{<`6zmDAV%btIo z+5HB2o7tTX`6jbFr;&S|vd?vo{*c*YN90pxcP=CM{Kb!?{aR#pzeK*w?9OiFp0h${ zmD&9o`8u;Zhmm_u*~g(lzs>Bi9r8_PcTOYMW8s(I754>nhRp7t&;7SVe?(`>?EdB4 ze@FC-AH{Lb?EZG-TF-l;U!t?j?0$uOmD!yd`8u;Z4e~a#JI9gxTy*GcGP~a+A2Pc$ zBA+t5Q~Vf?TV{7k;@g#odLP*{5-6&Zy)2j zLTAeC{^i_%Z}f{F$MMPReuKP&d-y!;^~-*&jp!7oXbXH-#pYViu?!TV> zlyE!yOTwGk&khf>Ul^Wd-xj_Mdt19d@&C8BtIr-EB5%UJS9kDfSdXvfB0lqjzU=jn z=oha`^K6GbwnSdR`(dwB_MA034SXEdoHyPT?+GJ64SOEh^YrM9@Fn`i>(iP`xPte? zo?rI-H98G^jQ(Y2kKO%A%$eDpA#z<0-96)6hxMM=ujpLQ`P*kJzX=-7oT@Z!Iu*C|VQ1+U>2 z-oOJq!HfTj`Qa73hFf?8_u-v?S`^>(ggAG|$FR@KgnaQc*e<++*KiAW;hW;zJvIK@ zAn%b6$VcQ8^5SRHJd3bj4=&*qyoOtN0}t>7FMck~Uxsg**S|zwAzvY{k*|?A$Xnza zxJPF|J|drx7eAlYwg~%}l*pIJSMVBc;SD^%W7x-GLSFnr$`|45Ye2q4ULjv0uaU2j zH^^J$9r6wG9{GTLL_Q%e{vWJAe0}}MD|nUJ_lGrd*?EgxcD_L_J0FnC&L`xu^Wu$I ze`a?|U+nr$;_yc4tIBWp=0d@7S-* z?v%)vncb<7uQIz+BX8gi?%@$G-jvo^!pqFw)(W}oe2rXo-XfQsZ;;E*2jsHz;+NAp z7n$8Dkyr34v->sjb!K-OD%gpXn$XA)&sgbWUyVD|< zoo|rK&IjbO^9i}^d@*2u!_Jq;W#=p8vhxOco7tTX`6jbFJ@O&5J0tQbvpdDFV81fE zQzBnxcBewV%Ir>!yv^)RhkTRSogVp+*_{#jl-Zr)4Evthof3Hkx0&7FAeWsF$YtmC z9cc{>+`&CO!o{zqu_au=HQc}*+`}VW{92lSk=g4nk*_klvqmmEZ;{K+H^^n@>t9dn zk)5~5W#=98O=fp`-QM?PeBXGA__ zcBeSU{$_S(iClKRLM}UBBbS}G$YtjnW_OCW;5cM`sMzmD!yd`8u;ZEpplUaaixCz2ZLcdIufZodLP*{5-6& zuaB`KI+t_)=E#fR#Bs>%dA1|hJY)1rbe5UjuaK`YyHg`yXLhGS-ez{EL%zxEPLF)Z z?9PaM%Ir?@TR2vk-6@eTGrLnEUuAY@ja+uVK`uKVkju^|q#=Nsg* z^8vZ+d_pceU;GZX8+N`#E<10L%g#5*W#>KeA+tLp@+q@B#apq?%dyXiQ{?zG5d=Nsg*^B(z-*_{#jl-Zr)ZD~D= z%>${d|t=zLCNlHhjlM~>;vLE zOlIq|cKfrR9{J*ru#aJ{d5K(hzCtcLUn7^Dx5#DZ8|1R{0lDmaLM}VM{Nro;sLx@_ z`W?;H=a=4v{R+FkL@qmDBbS}G$YtjnD%gpXn$ZNQP+wgshqWGIWey4`KhevqI>}?fO zT5}0ka1A$b2lwy@7k`5J;Tmq>O=hoiKrTCps6IKI+l&oD&_{ozuDVFQe0keSaI!m)$>~`}z#<;(w;~%kC_Z z%g)!xW#=t&+4( zW_L~_zy5g)bcV1y=W|E@hH*kicIR^L=-)Fg-izZ9c4s@RZRy`Omgp=qyT3d4^&LJH zI;+g?@6UaGZ%>WRI)u3}c=lY(s4*BVv>w8>#5y+SyQ9Bb{I*!zQ)9_JIzw2`fxkBH z*|M*hb9h95n)^r5m(|zxa~byh#b2ajvIuMbS4Ce|U)NHJ&T{U&Av&@;x|S+*R&(dg z(UH~B^;4s>&g^|}khhuL>5y+SyVD~d;o^PR-^?DnL@qmDA(x$Z$WO!CU;VrL9{Ks4 zUmgG1I=1_NnfhJWpIg|3Z;JD;Ye4q42IR8y3AyZi@%}WA?0kt_cD_O`J6|K0owvwk z=N0Sg!o^>u`AfKhYq)_sxQB<#K6fMXDYH9wMecvAei_#Jy0|Is zKVj{Q9wWtHr!{Yf)zRyESsk6P5}n<-^MHRI=Vk8b+*Ro8&z--0PMnvyqw`p!b2#TZ zk1cZ9>%T4R^&iiDo!<@mvirA(^*Zi}L)@Ryk=<$kChf26{I;;SwLwRA=QOP2qwk`W zeGJd%T*poJal4%Jw>~zG`J7)}=YM-`o?nmeOx_Nwe|4SD`4su?oa^`;&bhWLd%Kr& z{;c>eV%hob-(8#koezj}6n1_%=kJcb?EHAn|1ok|xz79PoZmaXyI5AP^`FoACr2(T z*ZMDKUp+Se{@NOTG&=iXZ|l~ukMrT&dB8u9{hB-4?(v*IDstJ|J)QHXL@s-~=X3u2 z$YteP!{wa6JaSpNuAlAyb8T%Wk;}?;e<;K8@VL6?FT?s=?!Epze$IydZ`F6hx(0qR z#>yJ2=SWr9V^?8~y>E<_J$65=v3JH;Sz~oSsly(-4r}aAjFmn1Fs!lfim|fB>bX`E z_SiP8u@8%}vd4DlZ062qL`Qb#_OQ=OkNzP6?r}V2^>uuT|C{!4JFMgIo-c^UOIV$&`wu$1bLa1)Bdc?DpFwAT?yTc`#btG_ z?kDIR&K=#uWp%FZ8|WO*od?F6Wp(s=twZN@?z|y7vO0S0)}wPicY0k*b4Rb^MszOc zj=q;z);#)~8u5zf`Zcf}*712#bYyk(cQi_LcIVD>qa&-Mzo}87vp;uqZOiKD?`zcP z9L}9rMn_ghe_x|P=XmaXOB`oeoo7X-L+5hNUmE%L;#%kG?`4F2ZST&x{uV=pyoL|M z+IL;UvbLqiNQ2Jt+<8wt#$|Q%xaiP1ojV`WePHhBvCyM)K6iAlkTuWM&+$3e^?5nx zy5_gzv&&kWu1Q&aUE8~JuE&(DT-Wpdoa@??mFpTlbRORcFDut|dpze4j$Br*YxQ!r zj>GoWwOsqVJ6rp;KU?cRoUJt<&(=D-uwUQx@DM(k@3-e+?W6A7W7y|znmdozV|?!D z`Cf5j>MX)1F;5+x?Xc$gFR?E*I)`(vu?_O$IlsCu&AwZF?|mQkn$PE4kJHQ9`dwA| zVX3nl)^XFZs*vx`xsF?n{BX{73>)OfbFSmrAwQjS9oruH2w#S^?@#*b_!%+xpA#F=OK~H>O3`kJm=4xt^D7IPv`vGXDk1)@cEp- zVYc!=2w%=~zB^oeMB2v^-VJMi-xD2KV?QGPopJ|a$YsrWulU;*BRa+1(>&W@b#(vS4{NOcZiwu>M*nc`=sB&d&ed~NbdKlF)$`K1 zqxe%bU=rKRV?#+`t{&!y{aO zOq!>GJGh5OxVUc`Tf!Aw!wuZQJv_q2{nGp+TzqWGOSpo2c!Z08iLr16*Kh-Oa1W1g z@o|_R-emUsnFDg!`Gj0{zWDewzwCTuUZN9rXNg>PULjv)cBe+Z&V0Rd|Fqo(?%*CC z;bJF^E#V4Yhfm_^&J*7s&kc~bbNY~T)^5iTB* z`XyY!HC#P3ZMTLyxDR^`)h8y`^e3fxny~vF{mJP3D|Fx<9^v9)sb9jkhVL6M7ysY8 z#orta-#h%_9~sXB!@lR2pORdKy|y8&)&9$Ytjfa@l$D=(M&) z*!wQKb7R=Ye2I?i&dp&T|0?YLt>G^0`G?G&XF@JJzwxuKt>JF*`_h}k-qtGYIc4|P z$Ytj(a@qL?x$JxldtWAWWOs_kq&1Xq1=sLcrT&!p`kbrOS!8x+iClJGAzx*7r$)Zc z?9PN-cE0#GSXq;<;9m&j%3E9A2CI{b{ywa-PY(UINhpP05a z!o_E&yo8(2Nps5nH%D#wBwlCj-7`MJKl=;A!|Yqb)9mMmi_cB-uQGdWYvi)?Tf;ui z4LWUR_iu~b$Gk&lliB^-BlrA0Izwjn?}*&0{Ia0mDB2p3OI^DHuZ{U!2cW_K#&tIY1y z$k&?%*CC;o{$>IqR?=*A3jkJv@Yc50t(B5uFJg*`4y=rEOJV z&r`z<+`*g7p0h_jWOipnK4o^NcxqZt30Lqcv&YuR*O}dEkautokFdU=(Z{ERSD8Kk z8oBJeMJ_wAW#!rm|0of4fTIcD_a~J8!~1r!6|NJ00>( zW_NnzLuPkI*_{S?o7tTX`6jbFJ@O&5J0tSq zi_&pu!aheG+`}VWd~xdE_$6`8f5x@vV7J0e*pI0;v)9uh-(+^DM?S*Em!|nkxPoiA zfjhW|N4R)qn*YYIpD&fjZw~u;QHA^#^lRkn%-+XaBlkQFI&Eh6JLH?p?hMFf=M!?- z`JztyD?48zmz`J0Yq$?TV}4FP!o@nxU&1xK&g^Y9$lJ{BbjUZE-RY4JncW$YPnq2* zo|X1xk=dOka@qL`x$L|~zRv7UgS^e`PKSJx*_|Hw2p0!wze>1*Yq)`Xc!Y~vF(+KX zHQa^$cN}EjLwa~bM|P+9vg9W0>#&1+c!Y~*r+x`na1A%`ZDHRZI^;cEJtxiIz#Y8) z^0a>0=c`37JKrFeoe#)m=M!@IdLEy#@G+di&KJ*3x$Jz2Tz0-fE<0Z%mz}rBW#=2@ zvhxAC?0iH%Wp-!rJnT!@`4YM8yh2{X!e4W{y26>y=oeudXvpYTVA+tLp z@+q@B#n<5YWOk=SzRc`Sg}jCvxPv#DJx`B($n4IDe9G)j@wM2m%D%gpXn$XA)&sgbWUyVD@=;7wTnUhEe>AwH9de3eJGh5O zc*^X3DPEkmyU6TLiF}#aojP*wV*_u{m))O`%g$Hdn${ybZ;*Fz507yCBed&}wGwXN z4({P0v)3>ppV~CP?9SpPDVLouk;~3k$Ytkiye!=k;~3o?)$@>!?xdwFzZcP8Yr^Wvpx4U5d~l*pHv-Kmh*a1W30l-ctXFT;8=yHg@xW_G7S zUc(LCX7<<)c@K~9l-Xm8m#6iYa0S=!ID z%gpXn$XA)&sgbWUyVD|P*depWj>xCX?krE!JhJl@a@qMBx$L|}E<4{K zmz@vDW#?PN zxkP7~+5MX%zy7lWof>Z7HnZpHkWZQ2S^QV5KkR&oTz0-fE<3M(CcOq(hkcF7?zG5d z=eLD@{EMGW^OW#1>^WtRT_Km9x5#DZUD(&H>^V1?-S3ePncW$YPnq2*elBfmk=dOR z`7*OR74lVPcWUJ8%HQdm@m^y7{uc1S} z$?Q&#e8}w1hqN^~lbb$Ytj%(W_NnzLuPj-q#=PTs0^EGnW`3AY{d_XQcACXU)-6{S%)|}a$68SQ-I~DR(W_N1j>&)&n z$lJ{BbjUZE-RY4JncbO?%gz^X!Z8UuUm};CuaL{mYvk+9?lj2T%e3{vu3i&FtJ8R^!^A@@6e1lwe-Xk9} zyE7u6GP_g!3ida%J0O8Iez! z-6?(z`&)&n$lJ{BbjUZE-RY4JncW$YPnq2*ejUdrvpXg7 zWoCCOjoiCBg&R57~=QZ+mW_KFo zZDx0Fi`?%yY|xS28Ia4)C*-p8_>$l2udf%G-6@eTGrLnEUuAaZmdL&SH9E38EpplU z2D$9KM?PeBXGA__cBl9a9EZ&Al*pHv-KmhTGP`q2`sS# zli8gf`HQ z3)UZYzC+Ut#A<a@qMBx$L|_-ez{EL%zxEPLF)Z?9PaM%IwbKcd)-<=S$?W z^A&R0`5L+GyhScM-yoNrkI0L+rnQZ5@w+K+;12G?&)8i1S) z@jWMS`Fm;33a;S>?%*CC;o|qxoF!br>&!k5J@OHrGP_^=LE6?LvpXg7WoCCO;SpZ_QJP=&*fnz5 z`K@994zWc?c4vcJc0M4Nofm(c*09LzPKmsNYj~a6V;kgcW_LQ|o6PR?$cN1CjL4_V z?kwJg{S7;BkazIyVIT85!hU^GzdOy-z#ZJfBV3GW>@uwR_3w8o~Gll61nVrg<@-lvlrhP$7l9E!|m+* zg*USw7#?OnEIiGAOnC9$Yx948csaWcuVz0lyq^8ya1-{L+c|$#1$yk8ts~sZ+ugT*D3A!96^}#ou9mxPm*lhex>h`!r7jcW@7n zaPyC8YzLPgNc?nl=4L5KH_wWc8TbLgnZ@l69&&T4!l1sRO ztB*+Y)NliLa1W1g{BgeP$DxF~yQRE`N4WUN)G6T#uHgnQ?v8zgE4YJuc!Z04q9H^b#e zr!`b?4L9%zclS$Udw7J4k4>E#u0AfUxrU2n%1gL{Yq)_sxQ9pj<}`ot3CT6wz#ZJf z)dMgV?%*CC;o^a5YzbFzANKR!5iTB-@)EA#8on*;*R$n=({?Mkh8wtpd$@ck=7Ecc zVQp{)7oUa>T){P5{%dRt?!(uQ4_tgY@<*nz#iNqDM<@4iS0RT-xLBo5v6o!JJv_q2 zE$G7)T*J-dQ@?|Ic!Y~5q<#rka1A$b2aj<4QPu1Bq6Y5Z9vva1A$b2lwy@>qGcHR$cfbAAapz^zaB5Uz|E6T){Qmz#ZJf zBV2q5=7+1Wu9>g#(n%2JYbzF1|GNOSppTu#ZC%)^_jl!T7!%yo4*bh8wtpdw7J4b(+6}dw2}%nEb|H#ou5;UOX%9Zwc3M z19xx_k8p92)>Fb2T*FORYrE^O<9s2nZ%u1!;2s{~;>%LMge$lX`y4fJ2lwy@7tc;( zOSpkMxQ9o$cn;=)E4YRmxc~Arc7)>(W?$d060YDHZr~2?;SnyLi}~RSuHgpm;6AK# z{Yn22|K1Jx2p7-8w%{u4`(E`G>0Ha07R6507y1k~B{VS8xqCa0mBCX`T@-ZcBLyS8xqCa0eIPfqCEx zuHg>u;SnzWLz=UPN4PvroeJ*Y9xh&b?Vk3ik60Ad>?8SQX`U{85%)d49_iuo0a07R64_Dul=BeTG6)CUa8gAeY?&0x!Z@B(l z3i9=P+J8ze!~VVn*_{gR;2s{~;+1J^30H6pH*g2{@CX;L!u)Ur*Kh-Oa1WQSPIFdp z4L5KH_i+81G*1I}a1U3nP5qkwzLYoc_=71geki&6;p7_b;2s{~;v|hN;R>$d1}=Xj zjjiAY?%*CC;o?WrJSAMgHQc}*+`}VW{21nkE4YRmxPyCmgo_`?{BQ-=aQRcIU%@rp zz#ZJfBV7D+nsfa5OVUi^(Ni!8P2#<9|zIi(g9a;Sp}$lsX;U!y{b$ za_X0G507xsqYqbb4L5KH_wWdpLz=&WYq)_sxQ9o$_?0wg30H6pH*g2{@CX-Ym>;g- z8gAeY?%@$G?nv`Da0mBr@vEs{!WCS@4cx&!Ji^7VV}7`TYq)vy4aG@(F66TjaN zU;jCHenWBpcs`=rhWzY@CX-gOZ^hA;2Lh=4({O*E`AU5!}TAe zyn#Eohex=0dm3B96RstMs_gx(k;~2-#Q%58@Pjec!Z0)(%2HN;2Lh=4({O*F8(Lxhby>wZ|Zb#507y1=c!-9 z6EMph zXP5^r?t>hz;2Q4X5w1Tv&C|dg+`}VWd`uc!!WCS8Z0b~S4L5KH7ay0#mT(O>aIr)m zuHYJO;12HL5$^7v=I`MV)*syQAmN4R`Y>QrzIH*g2{ zaIu@_Dd7sP;o>3bUL=427Zk<6_^h}Ohfl(K4_WrHs>8mo_7A<*Upyv0;~jRt`$Tj; zDUBWB;*(Qe!WCS@9X!Itze@Ae54*N6dX3qHz2*+?;SnxACG|_Vf@`>eJGh5OxOh0` zr$06268CAbVAc< znoierQqvil7Tr{uBB6AursJAUXgXcfNlmBsQ0XK!oz_#?(>0ycbcUuyFBLvj({W8F zbp3z|pVV}QrbTZRCRNkveUv@XSLvjtQ~N17=GJG%W^b>1$dHRQ9;06Pivwq{3uqT0E@mshW;!I-%(_ zO{Z%*sp$+&rw&r-$2FayX)#!ZN!4^*(+N$dX*ylgNlj;HS`5+B*K}OdNlj;HS`5|V zYC5jzgr?Ipov!O)DxIXJ2nMuj#m^6Pixbbh@UKn$FO)7^|hP z>A0p7nob+1!l!FGsp$+&i$_%WR87YA0p7noiSnx~7ww&d{`&qNT6txTX`DPSbR{rjwe^ z(6pGU(offPQqvil7SmMtq^2`8EvBn5shW;!I-%(_O{Z%*sp$+&iy2z_nvQEaq3JYD zr)xT?=?qPaC$;o79oKY1)A5-qd_vP{noierQqvil7Eh^kQZ*gdbVAcP>6%VzIz!W9u9m*0(>0ycbcUwIJT0!KT+<0nr)fH==?qPaC0d%Aj%zxh z>5Qc+yjZ4ms;1+bPG~w!)8c6@uBPLfPG~w!)9IQ{YC1#HV!4*SrsJAUXgW>P>6%Vz zIz!Xq87+NH$2FbMbeg8qHJ#LShNi^|EqzVLHJ#9Onx@k=oz!%OrbW7zzNX`vPG~w! z)9IQ{YC1#HVx^Y8rsJAUXgY0`3ZJfNv0B+vH67P7=GJG%cRf(${oc(+N$dX*ylgVy%{rrW2Y@ z({$=fDty|@N~dc&b(^xsHJ#9Onx@k=oz!&dD_T05PHH+s(_*^{pQ`D&rW2Y@({#F~ z<2zJ32~DSII$hIAO=oCY>{RKbYC2<&DT-$k;@e5wpYx|4(}e5Mf9gLpd;UjNn2MTi ztmz(_enis?HN6F$`liZHT+<0nr)fH==?qP$?$y%KbjDlCF5Xr;Rnu`zr@y1ZBsHD7 zPub&|PG~w!*ZWoYbWO+KQ}%?WlbX)Zbm9XQK26i7=GJG%b#3 z>1#T!>4c`!G@Y*Lq^2`8Ek4!K*K|_T8JZTKY2h^;*K|VDX_`*gbW+n9niijH>1#TE z)C`_makPI;B%#?I?c38dyQ6)3QnSauQ1*nT(=?r~>7=GJG@W)#rIW7dq^2`89nVnV z6Pixbbh@UKn$FO)IHA%>)pT6b2~DSII$hIAO=oCYoYd0SbX?O3O^Z`ncumJOozQfe zrqeZ@)O3cX)4o;dr)xSx)8dQ@ld9>srW2Y@({#F~4Uvl*3Vt#T@yp{vTrz*;FCN1c z4{*!DM_^2tPh^A7!H=MGM2N0@JNr23ZVNF9Tmf~XLbL`ig1z8VZ~@ec3Gotm1zZ8M zxEEkP*bcq`*TLO43DFch40eEyS%nx2o&=9jp;gVtnqK3D^8$}L2GP@Q|*n}a5K z$vw!PkH0~8upRsiX5=Rx*b3ePUxR$Sz@{{40{Vh^;361XP>5}ydLhyQ4GIg<608S> zZxNygNP>63C6HP~i2h(O*a5x;SAmr(L@97bQSt$5af8|0AXc34U=&yb_JJNHgy;jN zg1z7ZC|HvB4S+{VQU2gn5HBM{GcX8@0W-j9@ICkyWGgE~E6@i#42FSu;0QPgE&%IR zA#MS!!4Pl_6e=e~32-~O1GEMsz+A8ltOR?&hVqmhFmI<^z{B8sP`rW=ZNO@f1m{4w zq7cu6J>Xk#31q7zL=`X&Ob2_wy2?Tv2B(0mB1Be@AB3w3Q5Ez6PlK01>FS&l@I2TL z>emn=S4|;$f#F~*NC(wxkq1z=wh%9X&%m8^g=hzQgCsZ#eg-$kIZvPxs16%phs zTW}5xttZ4B@B(-nd=5^7s~~i@5GkN6r~?{-mS7mz1onbM-~>1Weh2j%kRE8?kTL~_ z8VNDEF*dLtTmYq;2=O4851wi&#AZ;63m?xCU;3!mWg;3GM?kz^v9nEC4$}vo_=zBtX8l94nXv zD%?xm1q*@64N50Fa6axAVmz1*o&(##>);qD(vkWC5?~m31GMiX#G9Z^XTpLnL8&hE z3*afR0qg_cfL}mLS0QSHiQo*l4C;0h;(joyyAZE|LJ1-2ftlb}u)imH?nU_C)WtrW zGf*AGK@+eLYz6OvkHJ^q9JmZ3eW{lq6_f!LKpoHt3;~aVRbV}M8SDk`gKGVV)1Ue` zK!|%l4=^391h0Yq1BDn3wt_4V2~h(K15?35@H%)890xyw)Q8zO=mh$K*T4aA0kj=N zS%K(a+A-({1`VOShYIn-Fd<3~r;dOQU@q7PtP$)Jv>H~{2W$`Q-~hrksO8^>{i z{o^@i5Sc(92wnyGCUOj55||CX0lm_McotNi#4&@V;EzXzxb-oP-@#=1OYkb#3*H09 z6ZEm53Rn&{f$QLoDWnHhf?Z(hRK_&$IY><7J~r?qcniD_egtwlV;JZTMuN%UL-0Ac zdj@q23~KsWFRmn>Gek zfHQL_V{irJnaeo^jX;@s^qHXKBFbYqeHK{x40Rh^0Z*);9|jfEh1jxAh}`QruV5$%+fuF!#8yP3S4JPXRv;o7wzd)AF^zWbv zSOzwL55WlsFOt_Slm}P`c7e(-QSRU)5Zg-q2gAWCuoKuX(`SOZpf#8RR)B2V=m$VK z&;X1CkAnH&X|Mr&4K9MLuTWl~9+(N{f!*K}@H0r+&as1jU=mmk)`9Fh$RBtJi~|qu z;#{HUqJa?)X&|V3$PWO05`u*e!=}Zfhs5HH^ERa1^fZ>oa9`CdSESh9ee=HQ`o^mup7JwE`yfe zP=CRP;0nlpn(_t4x8w=*2h)IkhW-T12g|@Jup8_L$H6b4*msQWAPu|=j)HUG7m(#F z+d*y60Q3eELHv8_1DFHe0~dgOj`ImxgVo?$(C!D?C|CiKpuo@M6TATqf>xK<4wix! z!8hO+VEsZ^P!hBRFM|A+i3?U@;4y{fcfA9Q0aI20x%K04n6^AK+fyr3$z1$z+>P;u;>r^r5n@% zP)r!&Ht-;r1l|YP4MWrcBf$x2h$74!`hnX*hUfsMfhFJt@DWG}vkfc+?}By_-iHeY zgY{q!Ff2pd4r+t0U?`XjW`e`uGAPbFHEsg~!8Y&-$QL!lE#N-T54;YJgVW#&$QvUp zI0-I+$|;6u1loXmZZgDqAhH;u7^n^UfLUM@*bhDe$G~ZD4n(suTLNXk!(blx1=Pr9 zh@N0NSOb#aC$KoXA>Ibxfm?F0U(g=(1|z^!un4RJJHeMA|ILQz1!jZgU?X@3d<4D& zxpGqG;4aV;oB$%1AtE3bC;`&JAy6te@xV!N6%@(Cae_7=l-CfoK)rmNTktcmc}BPq zSPXI$FhqT@9SkgJh&kXAFblB_v;{N4X7C%Z3LD~!TPX7)4fcT_K%r*TL68QPI=HEe zA?^lEz*O)ecoTdBa(6Yvt)M1&8brGrq9UjcUg>Ly55c(p^cMptL(uwR>Iygku7a6E zY181AQPfk=3Oou9gPTXwF2QVY2;>`Mh;m>d_|?&44bd6&1_j2^c0mJh1e^w;N2psM z4*GyuUOrpMlkI{BO>Bq@4 z=nj@mrXGMhpP;aXgkq+nx4uezRUqDRf+=G*| z$d52YVPT3gA|%R*h-fY>(L&hbJ`oi?MT&S>+$4sHEV#&7#Y#eN6uHGAkw;t<`NglI zsIZJ0BCAnTlr(CIaz-7|-iV97Mm;gms4s>Yjl?LUiFn;;CaxRJMNipEOq8v8HB?)% zQQj+ll+H|T&$s-Y zoBW*QcNOt?@K$WNm6-|okJa#gv;P?`<_9G4T{SsnVCa4QJs{O+pc{W_~ZrNYeM z*zO!(MbaC|-{biEQGO2)-uboo+sXgK{O$Z2{QU&>e*EqHepZ%>!0$NXKgDkm%4IIU zYY69*!*7IhehXJ;rj5N6;WqL65`R0tHOZ&@H|aR#Hvzlz`zqz^{AO!FOC_9@7aPCx z@aA6U*D0s3wco#=&%giO;?MI}bR;ue&Oxy|nLSu|xi!7t%jEZ6(pyA0=l55(JHOrM z;2rYY^LAzfgl|kaJAXUzoxh#<&aac7!~Aw3oo+Ve%5gcrM+oQqI_dO@{(Jq}Ot_!1 zPp-@?EXItB@|nP|V|RW}P#(^&QxD!u@zQyRUuS<#eRY1n?X>ac+Po0ewO3n z?+c{s{N0#*@Bp(YU6Es>9*iOUd5*)ezmNs*o^a0Z=PgOsRKMT9#Y`b99{Bs-@9&Gf z-}zI#-=)k%o$b|5dcT#P@P3{0lG!+4)HCPzHtI!$-+CO6^Lq#7=KNl8@}141|FDIm zw+M+C$8{4bD=IsEMGktFo2jEYMG;oMQ|UR1ioAHb1w?UCP?QjbL`g!FB1COM+)ar3 zglNdtW^8G}mbUau_lmOOe)^_P_{Tj(MbTSS68*#-qQ9sr#)~_}L{UvlCdPF7t{KFd zEn12>#9U4v_KfH#RuOZx=pvpKUBw#FO*|*Mi?yWmyyz=l6a&OoF;Hw54~bp$bxAQu z>=lE>yJ83)_%QLY7$Lr8Y38gLC4La2#RV}=ToMz+RrdNXktS{ke0Aed5i+LJ3(gQZ zj3-5IW2VSw%oZh#xuTRYPn0$0(=#p*w;2mXd1HyV-B>Cr8p}jw<7rXNSb>MXQpAlH z#ByV!SYd1u>x|7}z44;hWNZ;H8ZV1o#w%ivu|vFJ>=b*A*Tg%#f8n4Kk zjh!;Lk(Bw2w`G1~zbs&UAPX8F%0k9rS=jhY-eMe;sm4iJ)c95wGtSE5#(7!7_)(TJ zF3HlyWm(3!BFh?A<*mkbS>|=yNeT|e*KO<|X zzmYRE(8w1WW)ut!H;RWw8KpvFjax(GjN3xvjoU*Lj4GjtM%7T7Q7tsds2+OMs1VXO_UG&YA;8QVh78n1-b7_Wz(Gxmhm8gGQ2 zH{J}bGxmqp8;3%hjL$+ljiaI0jjuyV<3i{S->F z{JC*k_^44n{Do0D{H0MP{FPBPe9Wj7{@SP$K5pC{&M+E>PZ&+ZCyf^2Z;aOA(?;9y zw?@108KXn^d!uvsoY5`(gOLcIH+qFH7`?+6jeg-DjsD@EjDg{wjX~i{#?bID#_;e} zV?_8jV?y|McZr3EwC8h3}W|g*(a*!ky%SaA$ci+*N)Q?j}DD zcbA953He#Lhx|O;Q+^p9AioMfBu|A0%QN92@@#mhJQp4&FN8>`WYNe}Sv)dLmWs@jWg>HAJTh0_6PYhtMi$6c zktMQqWSMLed0O5Zc}BL2tdboftL6QX=VZso^Ri21o$L`=FCU6*l*1!ia%8uBBJ!quGV->Z6M09@jqH<)Bk#&3k@w`%$Om#|38< z*0An2Yg(<%T2@>0F6&;ip4G{0V0AVdT3yVRR#&sV)!po1CCrXiPqVA_fZ5;bV-B$T znh#n1%t6+J=3uM8Im#Mjjk)H?^{6?^ zdd!?_O*7|N)6E6eljaI*rkQTdHlMZTm}{)L=5y9ObE7rieA!xHzG^KrU$YjOuUm`F zq_xC+(^_i2Yb`VPTg%NO)-&c;)(Z24m2RH3R+>LqtIU|a+RS4=YZkWGn77!^nZ@k2 zW*Pf=^LBf^*}&dlHnCqYyW1Ph9`+`4h`reyYHu}%+uO_$_ABOSdxts3-f51vUo#)I zcbkvfd(0_z(wt`RHD}mwnNQmL%%|*k%{lga=0f{`xx)U~Txow|uCWiBFWR4(+wITI zSM9IO-S%;FkDXz@X`eJdu)i@6+2_p9>>td}?ThAD_%D&-tdQYHuvR*Z5#CkJN^&&~ zpF+32TKeB@DxBK7DTrIRoZ&oj&%f%Z3a&a5sID7WxpZv@3ga!hYM{l;%~kjR3lGax zmsxrE%2kt!GDmUMm@8ZYk6X9~X zstV~9bX5;jF<12@Tv=CDyPKK4t4=-38lmcr3FO7AC12I|b9!?bKfSwx^4qk=FTb))Sp9d?yZsJ7?w$Ks8Fg)c1bLbB zm7hw1)4rPYAXgyWa7Hh#+nCM~sj`xN{2XMwhR^JVTRwwpr>iuBXR+}UD&Rd)AzIy6@Rw3OoRdJnidC!f}jkP21tX^v6t8Uf&bKWH{tA1{{ zfvi7yb-Wbk=(KB7Wu@}6=U&!fXqC)88u>@GXCN~s*QUzqE7n-N{oZqp6>E2^N@aC3 zzjRfN4cGHyI4#eac`!GOvbyW4J=JI*DUR)Mbym(?rSfqJv##x?a(t6t8Ur|vT+BpRC1;CQbK2eKZ!NskSM{Irx2~qt?sH=-AlwL7 zeL>4x;Hr(BqiwEQ^A7V>R}FoWYb>s6ypF3N%wM<j<~l>^l(@<#mfTR4a-#<`q4 zM=xD7&5LESjl(BH?VKrP`6cs)om>Z>fJ{8rnQ6*6z@@nGxvAb+Qh za{VW_lY_)?|Hw-g=39;V9owvN%rjlJfO=ovRZHT2y;5yK#l5dHYj1AraaviI0!}KK zDOHlHbnm`Mj8rE^g5J@K>(tmDt}PpR8R)8dpL6ZYRjRfRUg_7SdMNLBI|i!9^V~sE zLh$b)#-28=8uYq<&d<;bc>T+N8cvoOgEw4yfr(B$^C0+H;>1D@P@%Cn(@s&Cssvl~_*3-8-Dfi%v zdVBllaR0pZ%X|Dh|8si(oXS6Mt*I-|YU-kztY&B;2M5XnHXDE+6c{LPGr?$*14K6Y@Bic{5Gs{W4HaPQB4Zoqg3a+|$PIPY-7@vOD8yUHl8yi}6_>so|(D zcN%}%)Yz?3&NJFSN@dH;&rx2G@^~^UQ#D?WE@_DVwVWKBq6~((O8H3Xcv{WtI9q!X z?p{~*`whRwRVrnt4@+UByr3xg_(Tt8y}SY;x;qk|Xz2nZR}_uw~X` zzDr4ZF`mLt^wb9r_^L9c=Gn?~-aPf?!`yr4?)ROb9wyu)jtAP@RmvlqIMUD6k9GXEdak9P z@{M2ywE8jMru;QECwU)V&zrrg^i)n&AExGNYFuzU-!e^|oDL2d;#*fO#Y6U#WBWC* zd1_Za=lZ@Aqh3Y7zBIgx_^xdLJ?ltU-5Z?!w4iQ;r})P^8&A-yLrxC5GKLo^>lyh`w71LY;%Yscled>RhT3O!d_2*i_H2 z%D}1F57CC1*9ZQ+T4__I_TR6t)IH*tL3Pw7_YA5QZ#4BwqgxP8&4gcIjDG-+2|tg% z$s3u}*~$oJ9jeAU?dU-_#>ygon{i5akZb!kf$#0Aay@-jeu3{9weRJxzvL?H_o89? z(}`{j)r;2sgDb4A?fUK9vEi!qo%CM7me*Czo%+RHH4Wvh z*z_lr3a)J!wwkUQi{I4DRTJ6T!BvOY+RIh-unlt6|CiQEGaI8mB&UAbx|+( zsOMbu9b0$0%EtDAtDKcFuU=Kb!)wytDZf~tocU2p*EWJ)uS=@4RnC{B8w%d>Rb+Y9Re+Bn_>X5gVyM{^i(g>iw$!2>#WV?aFt4VZjk3Afl{@- z3$s~oHqi;s*XzyHYD$G=%s<@pUZccY4|eu3xuTy_XI0XhO*kv0ZO1uV-{N@hcU8|^ z+%x8?BA0jy%~jV~3+nGGo0amxuBwbTGSXG?TbPr$ii^eKQCF?OQ=RIn<)rtNtE!^r zyQdXYJtyS3Q7w$yF_O(|TO>^()*l;;Ob=$+xRoGQz&+ zs`}q^|BtKQtQ8zDW7+>3*B8`P1$CvNU=x2lFZ3DD0Ju3VL;g;>>Xi%J#o?-#nMGf7 z)u?k^pK(>|V4P7cK+W@1&B-jB(*qBVIA^*xz2M(hyVYDx%>q?=yY4W=#0gH^RbNw& zT&1?El~1)_)dtmwsK&B`rTkW*M!MYiQr>(})nerVsFk(~ndBg|ktsJl^4UpFo}C%# zMOUd=sxybZ;@Z@4E#8@;X9 z?_NLXw_lY?W_eLtU!L!$H!n~se`N!ISH)0Uug3jzq_(QPsc=70FT6afaB8blE~hzC z{hX_Cj)zx?Qs|HDm1 zdA_RksH6PjWuEe-?-fGLT=Ua2cq;@JzL}@goW$8%cQ-~2`ul#aDtNbljtoX|@A}p~ zggftUtytOL+L!AARi1M9MhSUW71VWu|Ne^c4z7uK`BPVx|Ie^-cX0MewIjAKw$)6T~PS*eV4! z2A4lm`7KCqP>{;SKrIO3eiEn;f*7lVRK^7Afj~VOs8g-{bGeljSMRK<-r`Kqk2`g3 z#yp}4)laHkK&?uCHNvm4s+?6@E!EK9`k%I*%+^oU+Gb{}m#XdmKf6Ae*;=mZX|7Tq z)u`c>q_d)Q|01VMomD1Jsd84mk6P{d@B6yq#eM%Tvz|uvFKWL7XrXf#JGsg%2g?6* z?t1o)=u=vmH}lFl)jz)~z06Wky_9pOiPz#)jQ_N5WY)80c9j2Vf4ZGI?#X(7)uhe+1u7NMR%8J8ACzWqmrR(OZd@X&C;-fl#dR5=| z)grD!dHw0f^!&Zu7<&is+@q_eFq(R2LFKAbeXbR`Tfd-S;2(J2 z&1(7s?3#&qeW#kK#I!7v8r@$9=yz63{ofzu=7ga*@R{Hf+*-}gUV}&|z zvtRUW%*=s-|IXz;HsU$T|VkK z)OvPa%JTw!IcrjZ8eGL+d-&`pKUc@78?)T>ZoQK?ZBRBm%N>-TDpOT=7eCB14Q@EK zUscDQ<9N*7x}2xUx}-YksTlh?)808M-OX1$f_ks^rgBh}xzG~4UwuW>S+gAYieuZ; z(N9H{U(0rWsg3wUJQEWSMzXx7;^!A zAAj|NRPX5J;B+xR2P!?a2B31Fa`n&Q76y4y>*FfMKj%gDb!w$tUHw;A57qT8wa%}u zZ#k=yTmDwg2Z!>6_TOwPX={gF+qD$mM^`yjW$^X&oMv{VOT~5Wm%8Z2{idtm_W#qk z?=<=MT9Vn`RC;eO;e8{o{9l5Fp>MmtfBQiVtTc7rOxMXmhNnNY_Pw!>Q z?7pSK&HUa`)v*8RzNH<^s=T{)J*DoyQg_cfE!11PR#zicsi_iDTh$q?!1cjv?mj*l z%#{mQsjVumDl1izs^+MA@X!05+1Nklqw4v0RxhFcTzJhnO6NT~*Ie}#YZl&}qZR2b zy`HejL-cU&)``vh74;{N`srO~Zs7I&YQ0^JR(Brc?J~QaeH`LGo}#WQ{4UR}x$2vJ zzG_5ktn1pGGI;KP4A&>Puc}}$O1}1?zmE>VKAfwi-e{$iN?F;|wc~%zeP(%9N2GH9 z9sQ738l3}`hf&zGt>-G)&$L|pMAS7`W#!EL;i^x`vsdD6gSaZsAAG_SvU{AkwX^e7 zxT{*Oidw3SoO;33bj<0vawLQbx3Ny;$g9YbJR`T~Ywu4^=acW&Y zH=Od)w%7E-y|#^e+1;%we^cJ!4NBBYZ@l#CP_dxKs&I?(fv5g0TnpCLH@LR%xz^F( zi@&C_bTun1t}Wh;d!k)6CQxRuwOOD_1xoGr+8RIi%BGasdKfRu^Cum@&fA;g^?Azi z`aGrL&ckOd_~l>od^R}p4=ein9naluUJYA!# zKLsjF!ndio$^$*Xg2{!@Uvp&zxoW)=|NE=IZXHtB5E-s5l7qV}{Q+_*Adn**g zP&TJGtNQg{Q$A9`_t90jOw*$@X8tX1);YRI;ngeWEO^)MoHOGo=WKbZ|4V*Oo2L5Z ztoEyvQ)7E({57YUovqH4qIW)i`vK?N%Yh2# z^u)+y(?ujrH$SJ|fC!W&5bW^+;*<=QektC{Vi)#H8{sFa;GE%A-B zH+LPKlza&);thOHfm3JD^ z!oB;dym9caeRfNCt8*o!ldII#L$!)?m_E#VdSL0ZzUn|9=Gn>yYGI(%l}9ye`|nfv z=zZQxK<(C7Q}QyhXZhZ-W#?HLF9+&=TjwtR!S2>FZ~13c9k06jfA$Bz)L!lEhwICH z!5f{k((`+&##-ODEodpVC~sFTyj_jB_1pFD}FR%g^RnQ9;E zPLrqU1-$#SbCA;u)EVBwGu*F}nLWk1f84+4KwZanRuW>&mUs&z$Clky>Y9VIM(b^L zt{QpeQa(_xU-E708r6;7ey)}Ws%D_B25L&6ng-`+MiD>mnn3+f(O)V8>l4t2l^nKhCu|t>X5aPF^xos!c0D zr+5ednfd(fc)w$AC+_s$xl-n;GQD^!ldJM~^4p-A?=R(UIqxY5btOq%nO8nT{-DoR zeewM-@V+Ky*LpASwCo{y9a~wB;SpCY$;w)@tJJ*qEZ4xFb8WkreQt7Vb~*Y^PaO|z zd#Cud{YK!IWIycBv0r}D-`bX$oL6Hz5v~hsNjy_o(m&pztZ{oOt5&Agkt)9IpI_(9 z4A1Yxt-8Rk?QN_1amUjCd8s%fu{X0(*FU}?74OcK4=VaGoO5)+J+{)pH6Z6bquy1c zXYp>lp8x$osq^cc{XT`9yu1uUz^C z$97LJ58d2{JNw-ji;GbsT&12-y!mJ9s%vxh=9Q^)KZBnyBWt_K3?4zzRHiQ&fClm{#jMEQ>_aOO7&yZzytU8>%8sIo0Y3; zXzIFJ;(q^Xn%alcv;I=t$=`0)4}Npi5n5=N(GITX=kK4x-Llos!4k^U%S)|vU)>$l zlG}qb(~UfPb?scBCIoe@A$4Ven}d7kS-n-OH1c-bwW(UH+Dwz6KC3Hi>Yn^F_%2;I zKIXMS3s7-8{^+-;Pw}d@xZx^w_iNbpplxLqS3Sv~+DF=2zx9+a>z9GkX5M3*=WV(t z{1)&qeTjE$y$1NtJ*m;YT|eJ{IUF)X>a9-PXGiniymF5E7(d%-_zy7^+1X)Jd<_Oc}{?t}yobJH6 zV-EV5AJ-`ZZ%wA|C$xu3&ekq(aqZYurAfuh)ei;zTs0`Id z_N?rrcd()Fg=aQTRz0ga`|7%ex?Z8KVW?{p>Z(F!?-o(dcPp>uI3TYerrV;^e^VSvNMIk9Agj zv{~E9m45z0rG3@^g0IFfwj~=oG4fl!%1KRp$F&`y9UXF2@lL!o$5rPS`m3DF7x~`9 zwLneb+TGbEPI`}Y^Y_t%`s~fDm*2prY3gkKqc&?n_c$uS5qU9`n$yvb`&L(9b#eRy zdPv|>rTQ0jhq&_V zqR%s0ajis%qKu7YT;+^H67Xy^S|1^MI>Nsl{F|uj;e1^~HPeN?gr_ly7z_D8KW;x!lyt-`hvQ)(ai{+T`3zz1z)! z@-b&pzr7ZvuA!+h%IQJ8maOKr!-Lszzd!tEew^O>59iQ?dJafEbD^$jI?u3;rd9D4 zlpg*$uS;C-jZZ3913wZXwTlz38R|YymGqB8#n^~XGtk|dve<8zYHyP%UGLatqrBci zZB^dfPF8HZaG(B*5~4M8f80+jA-k$KDSJi zs2xSQd-ck>tG#e{XCEpr6^i?Dos#tK9dyozm!7g^rquQLySw@&q~3Rv*&C25?BmJ0 zeohWldaAu;_THoRTr1n=#whV6zL~4mVteCnsi;+Fm7cnKsNM~v-Uy^}pyDc@7EWF7h=Z_QSIaTt)Fb0QKgZ z)5ZQNJynwGEU0^hGJAthUz?|x{#L`(*;hW7I{RuL%I8w=)KT7?^0d}xQa3X5@RV2e z&pw>;|J1v8{`a@*{QWMK%#K_grMmyBb`QU1tCGy@C{?+rvTqukef1ue%=WA5kh*(J zweHOBM$68O#e2g1ul367%~V{cC-kmrsc=fEaGB*^og-&N^u|yXSLIoyqWYoC-npXU zb_qPjV<~=pR_{Z}?CmDMzvK6(>U}8cZ599hdnVMIQPeuHdgFw8Gs;~h{QRkwto#?X z3agGyl}l!CxXLVCX62`z##eVBs`t96x5KD6#;7|f)tg(?dtFq$Qcq8*H^F4~<`z}! zW;1en{gCPp)H?#4H@tL7b^0sy1{!sg>W)EWQ+Ly=o<_Ai)%G)MZ<*CoHTI}JN7dbb zezV8NL;Ux7sCQl*J5Ap^!Rhan=k{gbHGiA%-vXfC2jKW{jnbSLcU1AcmVwQE52atQ z7P;sf-*%FjoaYazI;7qaQ2iGF4FW0$NAC4UgZ+3d-uZAm#?_BG`z=@)dfZVbSUdH6 zpWWFQ!C3j@%RKY~L)@*72S3_X>iP3`-t^CJ&E@{g#<|Mqd2?!J=$t{%1HBseR|g%R ztHoqzzZY4LIPI$5Y(3|y)q(2M&Ocr!hIi#crKft#%%12|?NZIW)D;-DV&kM7d&0@f z(7=CI&++A$mkdF3Wj}#J#^l zWcFN^N=22QIy0)Bt5S3N=SI_<<5lINX0EH4;d|qUTC-G70H_sBwcdVvN#3A&g5FafPY3yX zhZ(+?gX+WmxT?=qS5W=)VT;myNgzA#{FnT*%3vBZRfDrauVaXQ;!dUu?2$lh` zZX(25Sn_p(ELiR~vSO(Z zOTJa$d>JJVY>P%lcI=H|$@dF#V9y8J;!)#f?2o~cZyMyp@`RBK%M@7hWrN&Uwi$V_ zyaG$UbC8#~MPQCp=EGhFmLipJsc`2iY>P3n0QRx4y#@U2*C!jdm8l*2MLavPRuu;lv-<*`hU+>T`iEJbxu z0n0Oyida^_Qq&+vws_jAjAc12MNRfk80!uv3@k-0_GWXv{!T0_VaazPs$p4WRmZX# zmZAO0eY16HT#H zw(r4G1(tllq8XOzc5^H>U@6-1oeEpjv|D1S1xvnF(F#l4ZjGfLEJb_K2Fu-cTP*cq zDLRl6wb5>er7=Q z`3;tQ{bLN4YO%3cs>70fkHb3rli51xvduQ?c9!OEFwb!_q#>bSxcU z$uZ2pGBwMSSf;^}7X!@1GCj*vSZ2VIqnd?fYnItqUWO%KE1833Tb8+4UV$aYIS3o~8EQh6- zFE(R&Cf|!#R=|=Y--6{-zL&6k152@xZ^78&Qofh5`~pj{SZt%OSOVK3cm7wf=Yb{P zkJ^rX8ElKZ`FCK?2TSp^*ol2PY>WK)U&USkmf{({F2gs%U|TfF{~GqDuoUTHH};jV zE$+$xI`(F;&yn}ryEX6BgANK7qXS~3>*bl%`>=660?}TmfX5sg+?}erKfG^58I+pk(1a9!;){Dox=Vj zY_snFfOAdEQM)YMTcN1!p5)Y2#n8T{D#g7OOegEj?NBCk<++= z&IRMc82l4?Ap91?MCXI0$Zv$v1z;%(8Wy?`jK^X`(YL@-lr~b(Wnd}F8d=b{!cvqo zvcb0*IpFd}PVBeCcoznH6IEdR0V6NEI?T9lr zenvg?WSAbzsE?ioOR>mk2rn@jV_6DIv65Ht@qIp6inT^Fc)igA-e|OfUpCsn+l_k( zvjdi5r*R+rs?h<DUwE4^c%1g$BgdiuVE>!7(L*tMlbl9 z(Hk~pU$%x|YLt8s9f75=`J9Lq7oH`N-|)J}@4R+==c7D)21LSV>Kp5{u?m<5cZ5TjdG#%diyN7_(Gn6zm#X;ujD!Sm^=@EFE7I9 z7KHCM3&ZuzB5(t< zDBRpE4!1B%!Y$3xa4WMc+|eutcQVVvoy`hxZ?h69_kpPcW)*Zlm^xrqMfZoP17o}O7Bo?teFCz_4nC(Nd7odVOd zna$ACU@2yqEznQFQp_=1p=ZN*P-YwSA{gJvycb?#-Ulx=J78Z1Q!C7l=yX_$)n;e- zeX}ck!0ZlxWcFa|Ay|r!&0gqFU@4B8z0qI5oPDz|`YV{TZ$60r8kXX?IRKpj<71c) zp-;k6d}j_qpM|L><`DEbn0jIkL!XDGxL}SzUxcOj(Hw>T36|n#a||2`jl&X#sVAZF zXbYyd3{6BwVS2*QBy=vAo-p(noF_CHOJ10|5t@R|4^uZn)6fNB>PBb=x-iW8Kxihq z2rNZvXcoFC%sN154!SrjMTyWnbV*o>QlSOt(y$a|LW|I4VJU76EkT!q=|4lu(B)zJ z&(Lyo1(^Obv;tiTrvD7BL|1`X0SK)|SB07Bht{C0!OZAGYtc1eX6~VN=vpu{^w0)$ z9hjMTXd}8VEJZxD8C?&i=7+YR>%-Li&{lLqSc*oWZRp0Z6iq_g(M@3~?g{NgH-l*- zp61hI(D%c%tk8aRCzzHM zdLP{drcVwXKzDCZ#Iq92Fp&qKeVpMdGlL)Xz$ zVaXTeZ=k2c%(Fr`i{eR``BaFWQalCI_CsOxY?!tmve0v3$$b)0^n94PRVW3$5XK7% zWkD~7=?y~J&`V({mW6V_Pls}1Sq?Kx3gw1Zgz{oZhZ!|O`Qc|n1>v=!!tnY~5qM*$ zDExA$IJ_-X5`HCAnpoRmW;CI)=$$aWQ>Ywz7mV)|Dv#a`<2!{ap!dM|PN7QZH()8= z3{^qzg{62aR26|4!Q)4#}clKE(PN`hwGusz zJm+vj^ldPnbGR}3c36rE;il+{FspXqX6VW={!F+9`VN>?xUloZi92Du>u?)%br{b% zd@ni<)3U<%p&P=qtZ)Z(W0;l|?uc#*)3U;y;dbG!@O|O#aEEXY`2KJ&!gPcgcf!5V zond_Da9?y+7>_0VAi6t@#}XcZ?g2~DGyD*`7mUvw9t8Ie4}trHhrxZrBj6F?QG^)@ zQ=h|Q;L+i6@R;y;>|sq@LKd~n3@w_hdu){5`{OQ z&%%sE;f?5XFe6cTGx|KtNEF_Jz6diCg}0)Ag7I6z+t9zlQv55t9sL_D#qZ&r=UH%0bh$pYimN8UzfgYkJH`_MUHe4faDbWWHN zE%H7(4~%agIe^XwLkVj-e~Sj8Ku|=t?l&d*lTAE|@wWIfahHc=D0c=(}M&tH>F20~k*}au(eP z#*>enLpOo(`fBbU)#VQPEiD!MyNZIAql?g3NVBfp`0 z!PNH1b+~Wj29|y>{eQ#=(f`Br{}B^C5T^f+gwYSf^#2hHJs76{k3`W!VboEFK6WfCmKqmkU`$6(q^Brke0jL#OykDdb4b|MAQGhzJm zNMZCW82>y{1fCNq3eSxchv!8~!t*1g;RTVh@WMzr!Y_jHbKw$+q0qcC$%s~MbP zwSaH3TESVYHgHz!UO1a|ADrFl0Ozne!Z%x;;ha`iQpg2USFP@F9;*kO*Xjl5vwFk% zt-f#p>p{4nH2^MTJp>oF2En&jL*OFTFgVp30T;DK!NsgGa06=`+|U{iH?k(ejjc&= z6YDX!gEg7G-w#uxtSNA3YZ~0eViXl!VQQ2$6WtxgudrsJd&2k?)*SQ$Fg>R=58Vf* zURev!{b1^qwFuoGre0Y~&;w!Wm9-50FigF&mZJy5)GKQRdMJ!vVXZ_Dhv|o{)##Bh z{jjwLJsPGTw$`G@!qh-(9r_WN8fa}mPk{0Nt&QkuFtx?njGhluTdXbUg)sHR+6phR zw!urS?eH>dC;X6vnH!_M*Rr@#?L&(HSsa zy|oX05@xl~+K>JQrjNDWN1uV|ORWRw^Dup>br5|K#*?=W!I!L0;9smG@MY^W_=g&c@78Jfx^)Kr!#WG!u+G84J`WrAMOfND!KQr)4%wI4 zZy2VY+gD-B{uPT2Q@8Ek&^N)CE6XG3+B8;d!X~coR?@XbUv8#677vH0CQfVec?jU z2eA}}>2sn3&_!T;yy!!4_2?kDMsx`FnlOD%bQro3OrH}Sfo=jbmPJRw_e96Q&7$M5 zH-{P5qT|u+VAe&W6XE-#ldyDz>7AmFp*zFWkmzK%Yjg^hZZQ2)bQ;`0Is?l9n5$#a zneao=S@59f9PEQ()(xWb&?8{3MnxCEqoa$kjDhjxqD$b3(Pi-C(dF1D!;DSQ73e82 z{Yi8sdK%1Cp6F`y445%1x(1#ZT?@~Ru7ejvH^7Ud8ws-nrdNq>Mn4TR`b4*&pMkj= z65Wb^4raa(-G+W1#wUz!N3VzR38OpFFTl(rqPx(WU{rBj-F=g6`0;8 zb_TX$XR+8Y<7w<1It8Y;iJeDhh3P9|7tsY_>SXLEbYYk}8M}lo0y7TAE~AUWjDxYO zaPio$SW3Wnbg|#iWnnzJ*mZO{7%wh%1FjG=B6x8z6Rs5FBkrPd%z~@LqVOHD6u4?E z3va}@6K1tGmJO~Q%K_gX%Za@sObv|XMn42|9X96N8$AeSEQ{qwkAxY^Vg=EoVaBpp zVf0v-@hesYo)9aFWg<-P7b}jQ1mi))O2X4(rLjzhne)cV!cWG^VVMczHO0!KXTf+) zu?pxpFf}t)2|W+SYl>AtFM#oyVpZWqv1;(*SPghdtQK3B!qnSX9rV*M^)^-){S3_f zB~}le4m0PC)rZ%|8e-W1OYuUiF?u7+EGE_zy%}Z}6KjUv0!#5ytOa^2OkIt<`ha z7suM5x5LbfWA~zW!pw_f_rZH(9pJZO9pSfQo!R;hEXBT9SM4+Ev%-w*DJ#&~VMg|pm2l3K)mU=Dj2$U!(0O3Sj+C|N zd@y52$~trbn6V>e1G*55znZcUz9nTdmLf3iG-V6AC`>y|*@`X>vo4Xc4P6RmT_R;W zx(xh(NIUcRCd&11PbSn-(Ql_pF(@ zpJkp29mXXz8(@3QMtGZM6YQ&b2i~sP44s-Su%Bit%+PFunVRj;rP%?qG&^CoW*5xS z?1pa59(adlFYK?`2Xi$CV4mh6%-0-(9?fA`pg95uXpX`{%`xcJdD^_8YSegmYw(&XVcLdNkl9{eWA z2#jU`d{5(rTQo)ReN8dksu|2OA3$m|%~1S@klIXhC;lTy8?7n9e+;SLG^O}YAoZK3 z4F4&le$)8z|ADmHnhLm2Q%Ph$q&Cxx!hZqzor9(te;Cq&YijXFAa$B%4E$D8PvkpD zou+BTABWUwnsN9OkUC8>9)A*2r)ehOPeJN5%|!fZNL#O&1b@{`hUYa?;cuF0@PcMK z{9W@9yr`K0|Io~2txJ#^Ofw6A8PbD|o{d*QYP0A$cr~Qw5Iq+k2`RbJ^YHnQk{dlA z7DO)~G5}I?qZi`6kdhm{7#2k@fdiwL5-*07+~{Ta!H|+0y&OLj(&vm`fxi=SM;yHp zUjix7(W~&KkTI6%)%Y^V7)$gTydN^67`+x>0r_QD^g37>y&leu-TrG<>uXw`T<|SV*g<6@O>R0%`TM8hjk2)zfP6 z36NG#tH;|Rt)A9^cR)sIw6XY`Afq%|GyZ1C*rV18+iBxrd#w$oXcL*63h8-jlki<3 zJx^^5e0Ru*y0#_0C*;mc+Zx{s(lTn>;QK;aMr}L16VftjJK!@QEu*$0-UVqHwVm@*1?Tw!Xxj)kO!B2ZGZeqNIq-x;0CP+zNsAmH)_4ieGAIcXWAnCUP#TR zEynMIJYCfe#(xRPM(t4iSCAS^dnf!(TSDY}$nUJQrT8BpPu{g<_#YwpruE~0f@GJr z0-n`Y68QyEUuj3d3)*TTzeC1Vw6*v@A!8}JF;Ldk6H!BYS-M7i1f;K}8waCw1F9AK%H(P5j`Z|bd&G~NWSSN<6|M?B)X|EUN?0<#-~G`tLm2EdqZ+hw-nzOl5e_YFhjSTNG7EBrCR}WbSsItA-yl%DwwBR z4fAztho(wvA>)?1jqo43O+@a5)Y7_l@FkF% zS+^OM>9)Xf-B#j$NX@L)vv7{?9DH1Np7>nI=%wxgT%fy1nuU`~1#@_PoI8vh)mcchEJKMxs;&_&{3fQ&`xqVO+5#v*hY{7aCw zNvDM`>-0ocL;6cP1AYypEbC(NYawlu&Wv9N=`ZQ5`1O$fk}e*<0aD&|HvC3Ne@T~! z-vk-K&?VvDfsA12TENY^mhe4YYvNlVJt$op{8q@gpROH#8|2rXx(;xkt|Q#9>kJR* zQs6;d8vIh%jU^93`a-%M_`{I9JY70Gs_PBE*YzQO9MVGR`r=POdOkWQ{v@R5qszdb zg0xUN7d)fOhG%td_=~PTbI(EgKe{~pZ;(EZ&V&C0QlIDs;QxfQRyr?U(tGi;z6h^| z)GGR7d<3NDqaTcqgw!zlq4+3B>!!aGuYt5~`Vy$smqMMs4C?iM7^AO%27M(o>PNAB zEM&A+Uk%OrS|S!m8>b(GkAu`g`g)k4Z-h4eIB3_8hd1jduuMzHb20ryd`CzPqn`xR z^plBnh147RsrYor$ccU${#MA7GW~S?ZIF99{X_WMA@_9p8TfvXnnXVnp9#6A)6c?Z zL24EKYMh+KxW zn=!{>WXuU7%^_nEF(>iSki3mK1$8l}iRdAD8*>(KfaGn=IeaW6Z)48m&5)-IF&FSw zNb4DM5g!j31B|%@+s9lc(gD&A#;9&2Cu7vGZ%o9EWF4gCiiyN$Kzd3sQFs@mrxc^X zXG2=C7%konX~kmnuz!q!NG_zEiiw3qF=iqIAvqRf#SenySWG;A2&CnTvEly#X`x~g z@#T6jzsI3p&F$Rm*Z@tAJ-MH%hZrY(B_@N&8c1y)n5c8^#l92^qgKOu)B=^zaQ6@$DhMt1(Q%-vY@)!(@CXNbk!q6`uy_eHo^~ZieYZ zxhKKMyA$@+s416z0mKtW_U63p_%!2(5vx($FYC^*tIKVKMNFikW$}kV_gJh{; zK7JtNe%7!6mKqik83xHf!(#j>NTwNW>^L%8I}{d4|0!SSOKRRRuZ`% zl4FKd_(vc)W>^g$HLM{r3o_blSc{(z>Ae`%;TJ$zde*QWE;Vc*@*HG5*{~763ew*& zY=UbI?+|$n(u*)`h8ql9h`b4@-wa#v??LJ}!#4PlVLOo>kgPN8fV&JkiF^v_AsBYy z_d~MIup1sW>>=_sq(5NTi$4m3K4Ul{@eur-kso9Ljq1AW-#u-l%kB79j##4A3q_s7k#wS9?QH^KuNsw_= z<2if_NbVWW<6A=RD~%WMts(aU#*6qikf#;KOZYyJ9=q`}zAxl%(a3+CkerY@(5S{| zK(fmifph5r8+^{#11>YB!xhHf@I_-ExYF1czGQU5RmKeXve5-s8?)gnMmJnz z>|fkmcmbr zWyE(u>OG?$zZ>#A!dQX-3^Mj&Wb{}19Fm{LQE{R%0>@@gD>~uIM_8~Yob_RSdb|!mV2C3m=XW?If zoUz#1_!l8(EOrk5CCJ%`or`}BQrpMQ!@mwWGqLmW8zAQ-b^&}Vb|KspyBNM7yM(!0 zA?GS~DSjKIj}f~JeiXYLejK}k_)f^Vid~7{1vyu-tMI!a=PGtJ{4#b8kwcI^eC%5M zVMw1Ob{+l*WW*_UJ^ourt!~oAI%bo`z`) z-V7dA$QZJ zz4+dcUa@H({x(Se#B>0EJEVp;9mHoto?@5|;qQRd`KH78T*y7O=?Fd_QvaKd;s-!# ze$z3$4^q#YzJq0^<8Y+u1gtWhgtewqaJ1<(9Ai4m@^z3qU(-2!1Ei#z&f~{I%Dd?T zyxVjUPBC4AQ%#rQ{U%j3W!$8Wri?=}%@l!u9FlLQNcKLaVbCJp{sNXa#6 z;WCq+$n%h#G#T(OKyuO)i+>T4lO{9%B}geYS@ADJO0g*({|clOn{4=3A$e;`#J>i~ zTT>GLbx2t@wZOj#$y-xP{9BN`HMPdS4e3Rj+Q4n5c0@jeiYh`AEF z%%fnIxf*7hYhjLg40N08;T`5i*xx)3=9^Hf-3o(AtS zPlu)Ehu|>t3|MBK3Cqp1*t;Kczh<5dE6j7?81r1>b&#w!&%-xBvf4Z!KNgbJ<^}k> zA!9h^g>aU6F?`Ov1g0NyRdA;s~}?qmQ8py zWURpQ4n6`hR$$qTkA#dvTDIVCgxqaew&J59cUzWicpYSvz_J}50~sZ-?7$l#d1%>* zH$k$|vI}p4WTRy_J`R$NmOc0cNPpO}7jK7*Ra*Ap9gwj~%K`jNkXq4l5Z@ZoT3HUk zHkQM%t>p;uc949w9L0BlWVPiOz9S^7E#KigLo(BH9N!y~nU)iO#gLwl#fo17soyN|_}3uyo5hBI9g??}MEo0&ytO3Z--Oh4 zmKON8AopLEmiT>;p0K4g{tL*6nWYW>OUM|zr5*k&NG4l4;J=34saZPWzk!UNSvuqY z3mFZtq~L#q^jj=x_*0PkGD|mj*3tw1Vo8VREWP2cmOk*jr7!%=;)FL^GhnpU1@+c! z7-MxqgS9_2TJxaI>S4=vNWNJI;2n^BvwHD2L7u!=i|{u?#ssXz_*RfHbL(JyG9&}7 zL-B1PHLCSae0xX@X)S?0tffSHLh3hb8O*TyVUD!|_P16tHy1KSZ5;)Rt<|vHT1(sy zsf(;*@D-4eXKOva64DA=8}XwcZLxJ6z8Z4BZyk@Xg|y4o3HULP|KV$$h_8oapmh?w z$2u9_Yn@7bBBU+0PQ%|1c}8!Yj(-3$=4^ck{~+WUy>$luVaWZObte81$ZvaVo{F9L0xOEQxDM)YJIv4*mq&IGzhhGF~)2;LI&p_IA>jM0Z#+F?`v& zg!pPmPusc_zXsCNwl2f3g^VOvm*dw#p8i-@;P*nZ)VdPC57H~PuEHOH{9jA!YWzV+ z&(OLCe+beuw64V;hP1QRb@-nl8E9RPKMQ&KY~29=v~Gl&xJ@uR?j7c8A^pR+&3HYe z?-#cP9}l@li`$B~K{73F8*Cf59k!3#LA(Q`){5JS?+B^2;&$OXL)zB3-S~7!4HmZt z-y6~z#_h%50U0BT+lT)Lr0$A4fd40CY$xs@{$G$fC+-lO5qFr#BamkVaYx{sxTEm# zxMOf`+;{MaxZ`kM+zI$(+)0+752<0|PT?0oYLd9q_$81UBknAGBkmlL4UjvXxbygp zkY0V<1^gz+D1F>T{5z0Q`nXH@&5%+0xXW-$oJz+SBV-gPPL2Nn(qhI%;6H@)qv9g* zA3??#9_<5We?u)a+{c-W| zK%5PJ5tj%L#wEco<66K&aV_CjajoIuxHj@dJpAgY?qlz4-Bv zx+1;^KLL_U@x}0w_`z^y{80F4{GH651*r$(OYpNHc@g z6<>j$59!0lSHkDwN5N(B)$sNBTDUoW49mO+$&L7W{QHn=9p8xm1Tw}FKMtOZ9}jgop4p$hAnA z0UZf5VN${@cvHe`*dk#Lyg6YmY?&|*wn~@}TPG}l$q5T#n}o%%ZNd`RE@3HbpRf#e zNLUVUNmv0pCai>=5>~-339H#}3Z(Cyum+z78Rbh@3%e(*gFO<~!=4Enn41piQ73G~ zJ0UGj!X|tMq(`0b4&DXnQ73H1XG2<|ge`bCq(_~w72hAyqfXd{&x7=+6Sm_$kUPYL z9ryu|k(`8`crRoeJ7E{T2-21%?8X;EekYKy2R|5+V+nilqah=K3H#vWgahz_goAK; z!Xf5921P1j=@<8-@(Tcj>FjrC*Z#mPQp0}r&#xKNZpli8vg|3 z9x&l7d@6hHMKqab~6n;x%*^u28c7-5Sg zas#AyZ8PI_kTC?C6>o-&9^2yaR!Ey-v*F_*`DshU+aQ@{OTxE-WSXr7z8xgfY%O7D zTWca+Aa#PR4Za_wPO!DZXF~G9)&cK^Lt8z59;C-^YlI7JEwb&v{|R|IZ`+BN>^t!)`!2i;d6s0~jc*3I z*Fo-U?1%6%koy|@VZ0G?uWdhqw?JwM z`%!4KA0uLiv|sk`@D51(Wj~I;3DSPqPvCEcv|sj<_*Rhi%YF)<3~9gYr}1qe?U(&5 zzCEP0%^bO=kc8&wT=A(z6<16YW9oxRLE0)`z3r=NLyyVjPDL<%j~Kc?v)@< ztn6xhFUZq>dj$Sg$T+7x5`P<{Z)1>7MONNr}<;xi#V9J?N7*$pt;9!oq2 za*uB}!+g7yhzC-S+2ip8AoZBthWA41F?%Au2vU#Plkmlmdd%JeKN#}+H+xI`P{=Qf z?5**4LdKo!ZSW>coBkTG_9M>x{nnaC)}-JCrIUk$0{>}mK~NG)gY zh93i|z5@KN&LOYIowNLTWR62Apnp!3XWx z#2)Lug5IY|9xFU3C(X~FGf_!l5|f_6XtMMzy|ufV?q z8O60%;$Mc;d-hTIS0JOf_G*9rlOdKKl$J`yuxo_L=xEAT^$S7XC{}Z^S+uer=ya+(t&%OkI3R2_Qm*P)D?so0V@Mj@?Y5Q{g zIY>=tUx7al=}X&J;x9m+VA@yVFGBj&_SN`Hkbbp&4gNBu7nHabuY&Y~64&9?kX}&Y zdVB<=K1|$zkA&PeC2qt=LGGIpH{msq(Z$4fpeb=P5i?|5F>wowOWaB%9?~yL+=jP7 zo`)uG$0tI1?}L-x|`VO5B5Q1L;#G?!~u*^r;f} z;X6Qj?}-QS9U;B<#Dnu3a>bk^Z_*)_M zRpNK>xy0i{mO=7A@dW-wNd70D#J>c||HM=HmmxWycpCpIq-9S$i(e0E*%Qy zMB+`5{;DGiZ-rbJhX%&;<7UQtAuWzW4{vrDU>ip)@wSkb#bL&`hqMk3E519VMR3I9 zdqK{U!-l^VGKS_zgt?9+B6*OS+|dFSI$9F(Lh5iwYkU!;4tKP{7enfBM>{yg(E$!~ zbcAJ&&de=`^gkUb_~DSTUq>2#1f<4xbiuJ$)sTLp<4*h< zNWalhf?o^CC`T!NJtU(XW%vz{jB@z#8zC9xsK9T6^cx+O_;(=vM#m`pW=L)6sK&n! zsSh2saED_I{Mb+(t%rOyv3i2$>F$sSL zGFI%EjQ<5PR_vIH{}s}Ub4-K3Ii|x4j)&myjv4TxVUy?2Jx2}!H)DUf;~ zX*IqZr2mt&2KG!^OC%jK%9peb-y8CrB56Io52Uw~v;p=@+6Xg}HW@ldIjSy^9i%%{ zsc@ibDt|BgMb$F?BK0e(zVKDm68MH{1${^Z9!STk%gtZi7!p zb|A7Sa+$P58WGinrA9`j!f&H`u>bF)Cad4#|Hn;KZ%Q=d)=S`*tq(_cl)q}d75{bX zZSb4cM~M8l^)jim{1Z!dmVai+&hnYor<-?{e__ea@~;qP6Sz(2cgH4j%y-L~T8Zrfn9 zZrh05&~2IYg8Jd~{B9f6_w{}fPU-y=lx}?-%C|nz%^D%`m#BDG9{T(SNjifrqWdFx zS1Q_w-bd$9^X3ddp$Cv7igy(w`9|LJi~fPOp+jgPZ{2wp9YxKfCH_YUZ#0eN=jA5e z3yMae(`cMU;^`#s;kEMqIJ6H%#_^7D6dBL)pc@m2qp#6i-W>WC%HX}B6Va1sF?s>5 zK_8%)M2S9?Buzz+qd$?Fw~d%kCR&H?=M91L(X%M=CP}&jc~Rxfl2ppuXjUT)Z{{(f zMzj=tf{vlz(23TP^asjGmZTR^R9i{vf=b%)UC>H&0j0I)U8Ws)mqbU-RVU6fdJwHb zAEIuZIZiaQ3-6ai&3Uhg4dtU1s63Uo^PwH+4AS@Dcu_}mD{4UZqq*okG_5CZ&_q9= z57Q;-EA%aj?!|UdYcv22K}V3PH-ieujqXC{QMX&!4tfUtfnxgbj!kqk>VaNBYtWl0 zsxRjQ4ZWT3hUTMp(alat>VW3;=Zxj)|l%|{=jlStcXac$) z%|uV2#pngJ2EB>4ppVgB^aDDJ9_6hhThM=zT+G`k(HL|9okhH;K?x-(%3avyp4CVOIO{f!k67~8A=NUbP_Mn72*)AG^-bUNdr%3ltt|wZ9 z-b35bKJ+V!C}BO+6|F$uqr2|n9H2V%DLR0@K}n_L0vbC^lAcD9Wo#Qg@8?>fH_=Dv zGxQa@gl-tlF(W^!MfanbC~5@nVnH{dvXSJ}C`oz=yxS3T2~G zG!ngp^fi<}^az@ZYHB%VbP?S$n(u=2bu5qWM~l!K=s)O3bVEJIfNn>3q5IG?=uPwm zI*L>c{3lQpk1|mm`V1XHPd9Q*W2pmChjHXEQr%6RhvHFNv<*d#=WUzlb(DCIB>fXv zCr~F(WW9g!wn_9j`V@VQ)RQDB4^^Xa=moR}9Ym)4I1Y3VnuTtfOgTY)&_n21v>ttk z9+^V^pu?!yRO)#&5c$!dRUT{pk_131GEM0M(;kt8+T?> z&pyg|Mq|;Le{;>|kvj_|=~c8B9Ya5%nTx1D&|BySG;W0?O-HtslsPmIeT)7;2`|yc zpgzcp%F(@OCHf5Iuj0KrXftZ|GT#%`ujU+}$!Pa0YzOUJBT2Wt%G*kjA2pyU=mpee zE!P17#ffPtcyC_CHd8qPghf)11pQ90#g7%lm(S;rgLQ z6mgESgNl(K)u4&!0rV=${FU#9Do_KOkJh43&|%c^Jnb3kgN~vO6(iGfLhW<)cC9 zEyiQ^@!sT06Td1$E71qM_gHHYzq4b+51m05Q9IuImWq<&#P2u6Z#5WwM0-$DJpZ2q zJ%}DdFQL!TSrnPTc|fz!QuHPIFFK8y+qf4+CFn4E-p*4Xq~#sMozT?dLQw^1j&j1g?>T$ zWJWB|CiE5h6(zJ`L=SaCF62X1Xc}6FCbwnu0}W`$wL!l2e4?r7IW(vPBOGWy8gvW4 zhCs7B@#Gja>&)+z(EI2JI)Peu;g_vw02+kuMw8KM^ak3Aj-q)f95d>{Ti#Ejh&0X} z>WI3df1tT&9omXocjdeyFRDeKA-NmB7EEV!1x5AZsSaw3QqcqGb2Pa(d4{&2y{Of# zJb6KPpoQp$K0Mz=qtW-b@ocg$-y5An5w|m9iWZ>f(04A%39@8yJSYcMq06XcHph*Y zqvkn$5A+0Dj$U*#o{hA3uszfj^+Eg5uc%vpe&2>hqZw!cx-FNof#&Bi?&#rnJLn+# zFFJ{SMVC?Y0S&a>*f2Q-;m13 z7%JL`-bXu8T`|9aK`jUIvhCJwX^e*}c-SZF5F?t$p zL?5Gv?<9s=mGD`^JoGVoaV*a((A(%k^dEE(9Y<%;B@{W1|9OBqp*r*~`UpLHH=}e& zKc4T89z|z_zlZXS=A(DeG4#L$%07Auy^c1c9Vl`lPiWAG$j)0YZ$Y=BOQ_Ezz6&~t zen7vVA(OcV=sq+JJ&m?c;hIln^c;PLzCo7zDRby^^c6BpQ%MC6@caq=fg+~!j1xIf z8`K%yjglYacplzmz2Urd@JMM_uxeN9^^{@oDx zP^f|#Wmf{%&TFJi3H`fv-mOt$?ZCfl=Z&P@3H`fvp5}kd`&f7VZQjykJ#}c~eBOwb z)L#i)yT9MMUVodXX)8nju3hh6yFzp5Ph&${iN&w9T$$JOSnPyzQbSAr^<2Nc^U8Y7 z!ds+YMR@GuEAy^Bmb~8ApLgNbzu#XcR+&P-DfI8!Z9cy*H0Q6!`M{AY^LQ2?KGGh9 z-NUcUYr0LbzYR;S%oAG;9og-Kms6L8{xw}sM7uu{n)BCV>D%J^>-n1G#I@V}Ao2R^ zb-K>E7T;mj|2StCeb+zE$6EgV{=_y9eR=)$&b)s8dFB73jEUd&hW=gq-4m{JKE<)P zsBc66u3fK1+t8e=uY*{x-EG%jZ|MIxXNUer`HOn~`ultGe{A!2$MugT`#R^-&8|)) zUD;nN-j$icQ-T4!-*FIcR2<4%6w9Pxyq9noZy6lUdj*H{2GUC2=QoD8>fOzI^zP=Z zcvE-}-W1+^H-&fIP2nwm5ArU(CwPb46XJOQZ-slB_rN_ZJ;Qt8o|TsHcDE(G&us~B za9bk1%Dd>^Xsy^sFG8`bup&(L1nlm+P~E~?w{GRlSjDP=sv*3WYLsfEs#;a0szJ4AG#Z2M zR*h55QT6iRn_aNb*lB~4YX19rs^%U34N&AraG$nM)kewJJoUY zo9evk0{R{Osk)@Pj3nOWqe9JOwHzfkmpjV0$era*a+;hfcayuyz2tPcH*(6i%NcS% zITN{1wwxvBpaR(=4?u-zpj;%E$;0Gw1bKd(`=ApW3S)s4h|$qd{meDpLlrMZ&GhozpLJ^{!sm~dWU)^`ULGlU#JhL527#8A#_^(v-+(1j9Qc& zH@^l6l%V#illKP7V3)8<4ZEKdC+7d8I!ymW{QEgPcfYLvDA1nnmp|+mnEQn+wkP}- za%hb&!?7>Jv9H3hufnmf!!gc(aBts)W8Z{hI`yz5v4=S6RrL~^I417An}xo|*sz-t zc0(dU%l#uFwA??#ZdiD3c{ny)abhpSBSObAB0@voS^OIrUZX1Fy{D9rR1;qJ@$lRy z!m%gA?y2zHMd5uc3h(2YaO|0I?AeH?*@f8ivk|I@z}`LzZ{^GImcLexPb~L!#Nerc z<#acsy&%?&;mr{ZhXb*k8$#z`WY|4Wy*45{^D?KN!Tq7yJcaw zJnUA4-O8Ygla3|VGx{d>dn~-)bK&Jqb`BlEsjxek5{jwQLM|fgBGW?OGAit>-9pQq z?G>7PF6<1wMXXeMpX*qrutI8igYdV&YMwtcvM1#<7d#G6@wW*Db97?v+jLsXqzser z4X}l313lOdsx?*-e^23uu$uoJBB+b!IhU$|GE&Via2ccG*Q|_Us}935Mmxk_#N0Wm zW7G$8Rm~DbM6iXb1*}zxZMIPLR=n79h4dJ0?4W?VX3qS?ve$|6hAU@bWg@uWx)`=eaAf3u#S7Y$<)hSh^b$vP29`<} zVVS}TN!wd^3#^dZ!Z|A8)74^`IVy48U25~Kf+8y_q}li~=_!RGULkerBbWipqaNu? zousflYQEwH%cGuGykL3MTEz=iOCKvd2Ir`1Z)f~g{uG{)D*B0YH5*n)OWDCUW4E)ZD}QX*NmWH0W&hHmsIL779KME2N(k z@AdMurJ2Yvn|!HAY+0~_N^p*HY^NlVrH7jRF;IMWk)P&fmy5*~?1RLSi*=n5g63vf zL@J~*cuLv_M=9CWL3LS)>j#UY6LdtxJJ|!enGKEn=I;w3TeV{ zaW)D^h@)*kQtD`#+NCjwS{WCGWV#$OG;dnvKA^UclBjTcC}EY zD83hLp&Fq0A&MWP_l_RL7x9EsmsG>ODc!kfQcplq_;)QF5RxU!KUboMch@ zUQptfB1Ng66D*1PNuu@;WvW^lh@UJy3+Jc=ZF2s7;<`VeZ~=TFx%p&z1Vv6`)qFjtp1-7}KNQoS%e_GHsmrC=diFH@NMajaK zNsm3i6C$O~ua^FJNSwm~4~vWy^_Wfmc!oGy!BfhTr}I0L**`QJQQ zjTD@$%snL)&*wQy)YgS!JBQ(JrDmqA;j5*si^N(_(sGwd!+1i*-5so!BG&MfI(Z+g zmU3Pd@w;HUT9kzjs$Ofw)&wU@f}?^Fah5u$#FEugk9C42@Q8W`ERzJ&6<-v2=j&pv zVe7?y#kuOH5p7mCjrbO$RDW&|6nSz){f|vzFM=&pU%ex0Lfg9{k^)`omhbU2Oe5%0 z^2E^WNBlgE$QeU3F-{?;iKCL!L_|&#Ia4jodS86&K;*uIA})?WP7_C7B8#&pr@4;` z&QK`Y%|*$g9;=p~`(AvPjc`Y#pi3>{C34`*h%7xN9s7~rRV1hXBA5+JrO|MdO7Muf zn)f=V6IS;>!^C`Zaq1j0M z^T``vv3A-;aSXfu5OuydpN3|i{VDchye#(85>A$Gfv0#oH)E$dv4v5pzG}wBrAJ_% zPQ0OPfWjh$Hd%bH3f?@a;>b_JUCLT>RHEf_sRgTf69OaS(k&XEd&pw@NzDYK)R%}* zQ$_PDXqCv-9HrmYMlbvk^<>6qs`-16jP6V8O^lOE@4{+nMI8TcqFI5BU*txLtCrSG zoRbbJamJ$5;*3>DpSEHwSvmkKq`cO`4}>oD_sL?95pDQgw>%71NcX@pr9@On741Y^ zl$~m6O9v7E25vWs?ax)R_s7m6(lJ)BT6!-<@DeQ6wr1p#v(a4ay}ja{uuKw^(?koK zt`79a7V$jBCYLd?Qz1=)la)RpeFL7yR7;Pg3vPoeVlVX)XVuo5|Jk7uERi$err5g` zzNWC_t>WBfLYwS^Ws+dJTCk-lhHh<`F5jV z2bEy8^oEOPRB0k}Zcx}5sDw9_^FD21kO>3 zvfM%GQCCR&dAj0K-^+6n>Uo}$v`~Gh@D-khv`~Ex*IS||i0dVK@+Gq9!@JZk5}BhC zy@+3W?YWodDZRuN&h+|P@!}5UOt0VJFTKP$kLfMu#`G3*W0d=snBHRUBzX?cQ>rBo zPe;<#V$11jv6pnEXPB-Q{hxGop#LL!HBRduo}t*3V{lqiriwElvb0PReU-t@;->TT zPu&~3)KlOoDR9TJnkOY{sX%UqB6!bYbKV{q#d|Ps*!FGGF{v-g;IGE!@s`6p*2|Y77*+gO zI~WZ?5((jNDDk>7Jo%X^U8F1|0(tIyF_jva3d~Lm&%$MPk@6d>3rfqyIw>rfA~}cE zG-RYS-5#?7Tl3`mast-p&gdU-#V%LSiJd9S4R$#T11`r?g!2U^GM$6jLt{qpSQ4m9;hDaDHP|l~|S~++ucVU1-Wx(^%~f zPkHLf{dNBGtAnmFje$L}c1CT@@XCrtmJxe$jw`KHj)6G?{Wayab?%x5f8E&9szQJL z=-QfkvBSWOCV>Lht}H8UP>y%}Xn(n9*hqg_gSWA+uC^i&W;bO_`fC~nH2NEr!|vBO ze7HEag1Xu=e|>#z-N4c+Wsb50n_ySsnD_`C1lCz5&Vz_$)K*oom%vnRWp!nPzx=9L zp}(};Q&Tnms$f=aU3FsOX=H~6bbg<<|WvE%yj zHD%&>^85`WYRmgIR#tIp#ew8;^raR4D-p3&eNJs~zH2PsI;XUzyehDGU{3)ru0&O3 z1BWOsp%M$7*J7@O5(r-xWlA|ooXxTRy1?u^IOL|&e>tt<5QEphp;jC)C%mGLV=nZM zY4q1O6p%aBel}JwmJH3r=aklu;7o`$O6%(VbsSk^m6&wrR@RHpzwD~GvSCC)U1e<@ zUy1p-rT2)B6Avy>xJcnMf)CA5_=v(~u%I+0Sn5)w(S9~j&Sza^nLkCMRQP?B)&9cL znhGXI*VVE>!}xp-Iz?(=#*o??+?dAN20lFgY7USKmm+cgU~VbJYlL(3=qiz-WJ`)P zs9$44gD{ol^?9YE14WT6sjW+q>Zu}vR@4^Gs;b&?nU(dTbTwJxZ^C7P^_7*%0twC5 z8rY-=`TTb`1U4RU4a)Q=|M(QCQIvbXtEPdImLicW%BT1qT~euLQkC_UT7${dR8bhx zQUg^|s+1{WQciAQDXN<*i>FFHW{SEcRZ_CX*GMhGggd*gwsABi03R$)sS@XbNu>_* z*OLTeD@AsvN?xUmmTc{N|oyT)wN^&O#)Sc zI2Vy`Od6&t$r!jcq)}saO=>996#thbWtV{@<(R^&)z*dA683NNGqA*8@-tOZ@-w(n zFh5zgA-Elpm4Q_QPUL5*M1H0QGg72t8o8E6vSp-6nfNs22*|mB5h8`<3LW@(=kn69hXc*DGt5ih3^O5au2pA9h?YjEpb#n!tjTMx_(b_2Sxt8SsyaITE*Ju@?1Jv#R2nU>PAD;K$=)0LXh zvAZ*~NB1s0d-O}|7g#*7KC6e<#*1&+t?9Sy7TkR|vHNby?!_LvOPSrJtnSKw#Zh(- zF4kQv#+;NMQf3b+t4A>0Lxh=5p}W&lS*ggE6)ip6@eSh|#%E@h<$!|Bqk zd9~$@RsP$g{?0t7G|*M(b$jwl@?FIxbcOO=89pYxCEko2SCiQpInMlSSBWb>qi}G6 zSfZrAYj9|x`pSx$(kij|@KTCDpY>FW;?tW43JQQT%U!4hoKkKv##0ZJ6qWk*Q%pa} z>#rGA+vpu#TGLN*W=L*NhE#~0z6@mnUu{EamAj@NlcE}KGAXu0twYUtWp>l)Y*qt=-g`L^ig|2L;FR-ew(CN-~=VzBVb8|h##4@s!O%8PC zx-*rpnwN(WUs5qFpD7mi4$kZ6$t_`)x4`3TIysnbri&hClS?QT%W->so5uqyx2xYV|_zybtod{UL6t344_S?oyt&pj%DMU zE_|I?Vy$4mhJEH`O3uto&KX&n&-HWVx_qwiWQH?8!<8GJ^fEWY7oK!wy01+5I5%7r zm(Ry#x^lHeG2v{wJ*0vs+s%Zt(3!_R3UOXry-cwVlvyuQ950cuh5;H zEmq@Vx+EoSMDDq>1_whvcb==nomGKMOK2+7ljkie%o4c}3bBh44%m}_B@{{!uRA-RgbFQLQ0U5Y7v+Vgl?uWW zo-8223&pA~Z#Xv4lUtPMa%lB}V2qzUWS1C|@X;A_>IP-5a&+F{kx+^LWq~}B%gLq8 z73ES{#hX#+F7SCvl#AGOMn5i6)9I!q+RJJ8xw73HMAJpWY1%YckXuB8UcDe+$?Kxl zFT6TDkQ3>-Cg#r1q?X}}T)k-Mu$%5F)ZheLlpK$bmW0z3C_E)@pDV8A%9@ns-7 zc?p$skxNwkPFl!;u2AJJDoJ-(3)g_|Vr-!IT4<*r^JqlglfS0YM=vU}x=C@wgs$)=lzuXA|HBEcMJQf2YI zf}0B!9c95LWv1!2LuICEtjQN>8V;RSB^F9iWun-fDfXJ-mh!UP^xm@FQjW{rpYQ4P z=D4NIJc5O8$=gs{HiCXwW!c?y?Ia&680ePn`1?XYKg3niuh8Y}FJ&Q7;d_0qf|7o; z6cjj+<>@b#_ZL|b>>QS`bgoFaPz%wo(9>VkRHBEMD}}16}SW4&L)W_Wx5Lk_anjc z$JO!C>e4=Vyj&}f*DX4#!I^<%Y8o!|Ff-FroG-cS^Gox^$d(v+5!UGmu0{U$FY#oA zCdF+~a4MADzQQ7xQpe?UmzP%pvpkY_d_DK=o!lNyGCcz+BM)6EA(<|w3kuk(Pb~3w z`4zqlED^|^fyK@ODwRUENavnHNU`wH+bNK;82PMe$np`XD=nwXTR^r9XQ;AMzsCB? z8opC*>3Dx#CvRY2vc8jtMS~M!d_hc#Aqh`Su=6Vd!BL5;Vu3Ub^sz+iQV={#m_XuU zl+JDmC9$YL%JU2qHzrOlqv+!Vg2Bo%Fd>@xe7ezrsX%rGC+K@C*H7t8hKgNaxof-2 zfvCru;m)P8@DyIz6HQ01+c!8E7$A9bL^H{G>*p-|U-{Xu$el~qKX|sq!lJ#T&7-$g zIGE~>wUKL(E2BvC>VsVSL(uUvy-Whqe?d7G3BZ}95|zoi&w5S&2~PwH1ld+73o zjy^CN;`5XQ?ybZHaMQeGWpUCZpR+$#ztEHAR+bOdBf<8NyG^Cyy0)DR$FIpB$<3IH ziyGI*m&{W>0~zywwQI`741V*_aR+W`S^wImFtps?*Hxjpp`M&_fkp4JKlc{OsSRh# zRq3E)N@xKpw9vxAY9??4>&z2HgR4b?al`AAlq%Ur10oeko_+(RoPmLvCGLSrPV*re zBt8mriJT>vVg}M_#7mL1v5sJ)-?>0c$7vPCvJpEQp&>M;3+9|WfqAs zko?@i6ap_J9KwaZXoi!UI;t0GK#?=wCyZ!>2Zy)II?f@|kRj4o@o=i7q-muqMoOKu z_R1;){S~FkQ7a}e0F}pMvB2Ey((3BK?0{qBjz>O(GKcwdD(gxs7+)8@KW*?R;eFyE zpt51bh|>{{@Bpeb91Twdp2~$MIWdL)(Y19A_2H2CW;qPs*Vc^>$5<@*M9g2VY?F~= z=h)Ip<>8_da1RvY)jWYI8x=l+z*ZOvuL>W>HK7s8e!@$*`!&*haaMQ+7T!u=!%jvP z$JbZZhqv@MVUF@|qV+V3JkJTN>8l&hQp!eX7XzO)rK9Ud)CMM;>^b1XX(*!8SudJe ze|<)2*$BTlALZhVixYj_nd~62PIz&#t*ehLJWFipI@s!&UNUNgtryAlHp0Q z*CrP-eBjr{#BzZHzIF~raBXPBmCR5!sH{AK^P-#+pW;L;(_a;g1)PXwG**l5ZRJ@1 zl~`V7&6P#N6JqJS(z~0?2~CtM+vPF_`XR3B(GBA>hlw%nex+rje5J!eU9w{TFi8yZ zI_oNiNxoX;BiLJ`!#$Q>cBhQmI)85EFsZh(yiD?r_Lqt0HsV%;%O1G*P@V*4l@6na z&X~6HAr6g8AMAkfyomdfvQR=?6BWyfhn-EAYC26(c2q!UEe9& zU!&~6+fc_LSBN4h_Sdhp9y-g%me!Q!XA{PrL`#h`t;^6;A{k7H{``2${Dft-eh(V@rH zfoL&LeuE2$=htkATc4_7T)@EmVDHq~$RqT+%6pWnkWpLi_p#lY&?3WmI6b0CB)7Jr zGCZ@iT7a6zE$P0+M3d`vPx%NnNqWQ8)!Iu z{<`W)svv3%iSmgyhMjc0Iys{&#Pkp!Wc zAur;=pD_gYSWrb&v{t5~%?N29nzTZ#rtR7Zd<*O}F?m#;QCfVHo-)58( ztZbQ0iE|j6nbz}-t;L_VABP)^_Aje2e;W|a&(h9%E9qV zhT!oizjFw#5%Lw{w-3SjN-hQGhrBp8&uD*5XgZL%SEiMm3$9;OL;h7&Qu~Bb?#i^- zjyKpr;j1;2au*+3?(gEtIZ+;o{oPz~6oD~>znjZ5xA6AGc9aS+cu1md44lGTjzB4n z^})lvDj?PiJYNf}81f?i|9E@T=E#=hOfUZsVZ$bk(_==X8EV5#cGU$~K=tjJFSINm zQAk!I6P=l;DvV5KT1FCkw0x5&i4rAJT(nXC5xtb7{1%kRs^~p03>qvV-*z#84$hChyjYLdNjw68P~M-ht7 z;ZBY21@gu1e-9rtOI!WZDhIgP5O1&VBYjL_IvlBFYS{+)Hi4o??b<-|U|b|Bt=vd|D3)re!vn>k&^a#GTbwAf!Vi?6 zABb|2*3@d))PJF_6|PZALZdp+tGpszVFuBiL<$V|nH%8i=_X{Lc_>OrvI!XI4rEda z=d^)NFOrIpY_l^PXdZ|;UVffEY+ruCK}EGz4dg5OcAHi)GcX*gRk^TKdOYPC=V3(U z4RS}1SQN6-F}iFrDapxmAWX$Qxs=;SI+Wy**i(6lW)C|^78qj^GKi>H55yV9Z1wWX z{PMoNNv5FD7)7Y<3D4CmMYUZ6!D;lk*@BefI$haUqOgW)-yVhnMOa{t|N6`B z>yO&dKTwkp3u?(pdz_q-ws>+N& z7F)%s8ds(it-AJ%Uu333Yxm@3AGHw`Zrir0sHDn14dTiOV;u%MEJhhj}4ptoUuJokk6 z^wRqr>YMCG_~qR*mtj%#(@`5DLU2-AdiG?M`q^gvnQ?tevc3<6`Wm>c-)U^%m#d6x zRpne`me@&C?PZ4T$uF<}tBLJeYi$z{H@%(ngV8E0%nOFd{OmSOayGkH#M}9`snCW` zc`cB_Dyok6iZI`0Pwuy#EmgTjs?UZ}DV{NuA8GX>sf-7GBlX9kl~gU(NWB-e+#h;z zzF3Y!`k2$z0ikkXh&6gnCy~Nyj64*`TZ|>!k>;VOHasuNBdo)beNoy+eppYL#o%j{ zawr6acN7$*Xf~K#$ZI01>Xe-+$tu&FiPRqp^D=2FVTp=NVmy)240keQV7nN@6j`Zf zBe7J!oqbwp(^9RlP2eW&?Oc1C5&*5GhaDApWeqP{F(T{Wi!i^v%Nn64G6ZJ(O?_Tr z6~z}XY(nlNelZv3NGD3T2)(C^-e-0_7YuJD<7rW5L;kz@;=_%gd^1~?S7ao>PNVoT zB^+sw-?O|_bBYftYaDaQJFPrrqG?vnQ519S_2=nzb|AK-c?`G^-!Roob-!31#e=M418mOgKuCy3-IcE;imGWMU3!MI<>bx-{@mrhRYeZrF z1&Z~(2=hbLHmt~{JZ@M@T&8&}T1nMrjMRIPRN4S!q_`g;_0qG9l*eM{JeWz25=1|A zFxjXZ*Dje_hz(Tq@e!$~>fOyLN01IjDp^@>86_BtH5S+itQ?DOSmYUkW8%`OGD3CDFQRP77F%r#jOCFMHQoA27k5oiLX1jE z&_<tOiz7)8yvA*HkIpgd4kWFm98VZ%^vqiL8qFD3XIw<^} z|B9!-AT-^&-%#sR1blq&buGLVf&qI3&euwOGFz=qHU!uUc5<(L^=H^YzFN$eXtL1m zz_tHIBli4;O{8q2)Zd$1Efy@84Qco^ySi8_q|LpzjNGHU%dkg>8D5dIIy{@Vga*@2km2{duib!Xy;%qDu}pee_e__XP-+@IWmEkhJP;+yq#5<%CIoI61rVnv7tD{cQOBF&Xxk@$x3oz zm2bzYN91?fN!~Zzdv8bW@^S4?UK#Z~wT}tz)$7^j1JfdzefhqryIj}cX1hq?$C8nE zlqJI&e}<@O+VG=}EhQIh8Jo$#pz@i2xx-mT>p&^^$>I<0OBgJCjf}wWV^hM{e3%qp zFE%x-Eo>=PagFLO-n;FaJQm0E9*Y`f>5ZaW{U{#G_<+W+9jOU1E^iou=CmPUep<@` zLspa&+|KUe1vK8AZTOYOtdW7!XR!i!#;VNcHH<4>VHERnb-8)CV|X=C*_TQ;j0G3W z;XN}RqCR`L<^$z{KpJo8LI)ZHL0?%4cG^saBlo=R9H)T@PH7L>`i(DwW0bL1 zY|Bde#H%88rG(bkOC9R^Ic4E=^WDt?hweL8KM-|H4h0_ZrWpB69eJ_zYsLF+rh+N+Lb&mOc9@tB6jxw+kn!zMzY zwyWo9zj{`WVF3PL_nAoB_wv3H3u`Lv#sC4;OG99(ld%_d-uSLU;i{mDm zP$iU(6-{_ONFxJNdq(4wA1+J{(`zGobQKoeGAJ|vl zo;eUupe>gEEf$+Y8HcQOrc3Kf$YWjtQ^oF%Nh%jmkugkvm|UiC!m759WE^WC0oR(u#s zN@eG@gd)&02Li86l~}1~2^5hMj6^iG*P4gweQPW8Y_`$pic>Wt2Ha4foTsbp&aq*S zsO-}%N*Z+-!D~doaSB-3FmCE8e1+SAfXBXA_$y3aJn?P@$)NdtuEjw@P^||G%&IBobfzy`V*sMxgK8VkQaSJ zorNMtHRm$jt{qmfmayMC7EPq4s9Zx4s}W=hrM}i~4|`XS)}B`vZgn zZ)PUg^Zy`1zk6Ro$BcDuLpW=}pWs~VBs78ZNc+=sRx+?5n!|DNr4|%0^D;k=lYtx| z$WeQ?p{X|<2#QnRd?3A=zrX+B9C-8}EF5AuXv)xWwseRW_;4S==Nfmc={)AMTgMeh zFPK%Xnbzo_5&8W(QU`Oqoqxh-OVz@n05%W_ysK5w(cqu@IgqLK;#=sKbtMnFmSaKi z9YdwJj4Rj~wDF^EKGi46qQ0CxnAhWSwAG6R?0{I>KV57Wu?AK%9EpDRtv-CWTh9E? zZkMLFid>bd!J4%zpy(BDY7jky8h&@jupugEVX`MxCD#yCeM!z;*Kzg5}b5NFK_3XG4Kd4F?!?>XNeg+pa{2F?8@++&Z=@1h>#P=`Vs6!wwbOm zC9KDlu&ot^Q9ED4y*F@?o!xJ5O8Bk|YaCzX;n!9;s^R(Ti!-bjYP32j!|cmwlLq3j z5xcD#%GG)eB*#}*b|IWGrP$EEj^p{|dHsJNud{;FRly?4;WRV>1XPwPRV|vnlAmSSpiW zsPa_sJ1j+&Ki$;#W&VlrLO)6GO~GxM&BAXfoM80~8jHSpi3q*H!3d=od8DpQ>~5?xbF!Y`NDKCsl-dEK~l8q9si45$0uK1nM49 zRv$W%Cz#bSE+g{y?1@>Y8;`P&a3|%5*8CzjT|_jp4Om-d>Xe3S5M4yRMkmRIuau}f zh7y&=2@RwDn=`6CotVDd55>n^oOM+ag)0E4bEa<&hGJbWxyWgnU-EC%)kym-L>V{sWfOeR=yGGsGOhGk)5mlobFn8mR! z&LhY^j}HzpI*fpd%kxmFsx&JTzWeh+^^Qu zgZJ16P2p>WCehgIJUJJg2BW3qGHjd7v-KB`N{M4#!Uz6YQHhwQ?AG}e3G)-(J=6n0 zU##y-hQEwnd_VSEcIj+&PX4@EiBm%75s%PMHusURqOn{)Uu@W5;t%_JaXr6$5U0=C zIOaQ8O&kBlmi5V7+|lVByl4~tQbnYeIVC6T2!!)yDEn9WN!t393NJs=?RLCeN}oL( zuRZ_+TMuo1qGNrUxraERYn;j=oamcO*5A-QXA(n_m@<2Zk$s~yxtqwE8i7_^=4Uvi zaevPRplD(B*L1FxhVRl7!IapB8 zlW`8H#Jx){3X`+@jfmd=Vu97l&+BC|LHe&~nlq*Or)Na16*|(g_T*>&`%9-0*6)`>mDwp=49lw;E;H>aC`K>~ zlx!f_qp z-$Q-v_?0WZ_&{A2&L(7cQKxJRCbWxe;;48GA^^n3bn|avQ+|q7Y zZDM%JjvSWa9L*X?1n!piEUj4fc78RzTt_$tu$7IUZ)Km+npXzjRjhlJJ;6Y+s+`IO z`{`0kHWZ4VBN-h|j#DjC5syS5={3Gx*R&qk)ntv+vhiKjl9}O^Pi?j+-m?>2w>SaP zxMRMlxpu5(VV)s6l7;V7Q!1KXtVcCf$0~NZ-QIVx9jn;bi&xWjEWHe8bvuUT*jVi{ zH_IjMjMS8a_9CjPJzFLx^PM&IfE=0DC-l7H!?CPJ_pB!f@ks5ouUVdJA-1Kc9D~Ok zc4`Y$D7Y?eE3YTgpF0k9(^%wHrF`ZSpxr;Dk)iDWjk{W2DV<;5uo&|d zYuk7oQ6~BCnMBWWGJ$nK5^d7dZh4=Q>fUuIk&fq3HEH86SE7@imv z^6;AXFQ=(Iy;@tJqE{M9q!XjXKL4TQHNFWoi~KpLQdQaUmi@zDVx?sprU^q0`5Q)- zi>;So`ba%lrk$FLzzqaNBb_DnDT_`gs&83K$8T86Ti(nysxo>fOWAL}l55;n9*$Yn zG&ZbBDsx?=dZLy)t4d|4#vzqTv6ol1fuOwHi`fH9UW2Iek_V#Fj#2z1?o=)9k8tjp4d&H(YUtOayI z+IqV+6Kk&yo}gCBUIXFEcqDA@G8U*ECL2Cj(^^uCWS>%`rZhPC4yP+T&AxBT6q5_* z+s+X2lJc93`pVcev<40nnNY>lWgi{w{Hlu`iBwQ5XrHdaucA}jms)q>%~JDTaVW|h z^$G*6*_U1g)Y17I=0WP6Ze^Q zVon31_v1ieP2XJe98gLJFjWWD%sPVD zYUP7zF}9(db`pnV*u>=ZO`N!S8c^N7|E7O&|E{ci*%|%cf2s0&&$L*w$10ek?!4TafksXyU>^^5hMLafQF9n$W68j3uErAm0I;_z2xVax4|CQGNwf$tkV^;o7{9QhI_ySur32F?~x zm*FLyXB3=6Sa4$C210lslb?QOM@zx?2Fq_AgH*xW-e!)=3+$e326uqfRaL61a+c^d zhY~&9y-1C3oQG144O2|x*Ue|ki3PaDDr3erO`vBF1b6a;sz9|kZzdP&9nRlsRq`U1 zIpx;4GK_4Js~pib$p$-Mdz=ir!MRK-m-eR0yZTUb&uPe7>ik@)bV}Z_(vR6%C-fsc zW$T3H=T=`%PR3?9!D~6MoN~+&Z+TwFR$r=4c5lDr$ZC#-P(`7n^~=W?*|`lQc$0{WG*e!5|2qUJB99)*2mpe+!G#5SJ{H1)zPA?{|y zmLsL;UFge@v_N7#EobBOTp6bhw7Mv@T zL-JOJU2^PLwKVn4D0LDyCqt3mKU%u_u#WwfZ}VyX-2y`y%a(5CUp9>s7J|!>Xk6%} zV+T=LbEjq}7*4dRCAcrG8yokd-1!Z%DDHB#kjp%O-5xO#8B00T3nz(7 zFR}TXlxaTAO%Y3bxJX^09}vIV-NoI6R-(L2+2Kky6=GY>uUU;mS9M!y)xa`>m1 z+HU>+{`%Ke^PSJ^3_}2?D@@+#4@DFns5lBNmiQq=eArFiO`lWXMVLN9ae!A2(uW#2 z5!f}IbNpK}?E`tkreR!b?wprid{3w{ij`6gT{VV@+UUj8DvEtzj(xpe-|5gxGt>j` zCe-wkZd0i{lMbsan1hMfI_7w{W4b2~eY1M9r~c1apvHqMpF9#tt2`wuPdsuGO9aFI zvFszNDM|-i1@2iZC%-h=I~z9yL2ur3vf7oI9ANdQ36dMM+qH*(PpSGvG7X3B$`h|K)+X6 z`tUa9V>#kPuI9n{sApUhQKmNeopg+WPQl=5CVCMK>HV4xNP??N6_&8%+Dm6E(biMb zv=zqTJT<{rm7Ei{#GJdegd>^s@MNp;B`kRvmDI?AKpM>`TXjaF`4`Rl>`9-aD=N=O zq673SDkW`3`D6C*+09XOjWcS_4=#nOgM}3iIlpH{mtABh;Lsc?Hr(8YAZksWNs*grq)-$wP0w2m!^S-O0(e0Fz z7hLqIH9%ET4gglb{yWoaHEq$Z^ApPevzgOPhNoemPtG4&h9{hSX@D6K`Y2xppWY># zw}qzP|L)=4W?7@o+vt!(6Fn@hab=;o3OeN&MAjHOGug!}ZzqvUDsPpjg!MDX)}_kz z{SL{m3ixuYM>@|`U!{q9v+)haliBsLvB5z47YKFS>O5Y7*{k~0NsK_C|+9zNh&%{@&c$Lse`mGK{=?RX~~~1 zxEI={!E(08E}E%__$4Fm^;pPUT6^D8C8wsL&ul7=u``Hzc%1<^F1PtN9OcEC;)HP6 zW9>zkXWAZF6Y0HaJ90J_8L1kKWmKwx&c&to=CGzQzLB~iqNa>mwXNE9C|4<3>`zuW zzNjs2q9`;UgxiFt@3)*#!kN+ayOg#_&V)K8=>Ee^i;Q>BJVPV$Ccp3;?v)19>}dbk zBKY+eYh4p!j%~~dN8ou<-b4k$H~50GPq9L)YdK9+%36?E9MVG1ex&S#IX?o z^gRQ_aCh2I3Vu(5oVZ1YWux)z46_Ys@OYmM$P>5ew##h^^;KTbUqn@CGF=ltmieQN zg*n|Ci>Ir*`*xpv%hxxLzl@#7@`%~Cs8`F`hmMV(oZnUBhA$3v@j^|B(0Xwmb$u6O z+!vQmL$uGYuKMTf7_LmwdcMAUU}gWNi}K{`c(a*3G<6N@XIH1IWhZM{V3=5S5xH>g z$?z_B__eHPQS>+cZJT!KqO8P)7Cm+ig z)xMA|;eL+KeNbg3Z#^U78ZleWowza=A(Li{jSz#$2wdjVla(&x`#7?zB^rt1M?ymz z8u;5qAO_!Ai9_UvdtJBX7NdsXKXq|PJYb2KGMdnFKk=INi&P0K-QgmiO76smq7yC0 zZs=GeQ4E=B5^sZ_^HYitUoQe>+#8%;fX@+aQ;77Gkw{v&`$s%+o`SXvsM$Vjwxu6R zqI{cv3^jt!LUkeVgJ??8l(sB;UU>4y-xAmy5sw3trHyNXR+kqKo~a` z%!gHcceUNb0~uJo)$jjhh$SmmKz+9<1A2GilWcD2UZ@|26dc?GPsDTJcF}C_xy#i z8aolE5lLIc-aI_wr(EyciP&<*5=Qyw$SHc_*R^yLpLD@7}c`b<71eH-v#a*F`ABpT^ zW}CS@(o#=J)j+tlDl1quRz>i5?CqoJPUL;oJ;6W1<2r>k2)T!KV85c`!lL@Nw|`bS z-JTTkT?zQLbQeMSB3g~8rFhI)4<(|6b^b&7Dy7u%HVQokf;Cbz)Hz2s%IggI1dpn$N!pGzEXrOAi z<^T;@1h(aE)n}Yu>->=U7wDS1oHiHNlb0JT#V^;_y3};HW@;M0j@bv-@C)eh>;ny? z9EC|Tk3+EN>4s+PUF@}(?RLHS0cl-Y@;9&P-MS1UweuZ5Xf{xy!}H^n;l(R@JqEz> z)`dB5<|TO?c-WtMEb(M0{A!X}+FL)Fy!e9^_9MB^PTp{K8!HJ8k1qvrvHRB%o2_C6 zTc#zY^c%_}{7|G1NG+P6$xq=NWr;JI&214YuLEMA5}?6p(L2K)r)PV1|BM0LmX!fZ zp8nPvJL{-FrPbkme)GlEosMmE<0Jt`e0lTIgu^a4-0y?IPIWMWS1J9%9lK~Tu@hfQ znQTzJrEwBntwgg-b2NOE6ClNWH``c4pE^o*%L%u4ijv)1iy*)Cn=#JfuD1dEj%#t{ zbEVfx4G!vfp|unqj&(|dGZJ|R0!l@qNBKAP*lxv4sn7At=-DWeX_U9eA6*_j4?wQ^ z-QHw$SB+B=Cf8%_5y9DQ{?o<$v&vx*8~y`7ncHbCWAxzuI0>p&wQ;!|^fpf_T#i#v za#4gQWu&Xi>GDXyEY|MnOfoo4K2mtaq!-9Zr)I_gfh9N@v$>xQpgSD6WBpAZOmCt? z6W(lfD}u~Ck_!I5RJzk`8c2)UK(OdzOQDb-D?#Oj;iBqlO-q@avCQKDKrwI3z39IE z7Dlm@hE~JM({ctQR%mkkn;*7R{Ipk2rZph9lfqoZVKh$vWQCK5zf0olTEgOrSJO1E zt96dO;0)M=Z>4w3@|=!MD;up?8OmXE6Q?IV=O;)5YiuQayX7am-n?Wvvp9>#ZgV^n zcF5$9@Yx~d@C_4hCwJgR+zWO&vwVz)+Bx1{g!3xMtr;35L`tghIV){+JCQ@r*QNM~ zCI`+O!pb)(7c;QU6wbd%XZCC(UbQVkKIS!oIIiF+v;&8oM;y$C4Rg}b&t#8^ucN?H z*T@w!)r2sXx5{3p`O1%s^${I~nCS-s`A7}~F^f#~^8QYHO+(>;KS}*L^O4py@A4DC z8@Y)2a1p-?qSR_rW9OSwp3nls2@su7dZ+h0EIyr3CATLu9$VNlmx{9dTq5%-&ISC2 zI_880i$+Y^pGbLuOq{nX!sP;pT^l@IT85OA zh3Rd`KRXl2UbEXT^UI*548$4YbQ>% z$T!I~w&JgYsqrVX4~qlhcD$Rv$h*ygAAPIG;-i4ByuD7mD$ZLLQ8##)mw2X^oGGhY zJo^}F9*$YR{{!c%#`PJ_cpsf><6;Kc`2F{3arzcD8%m-v%PQ8dTBiz^_RV3v7^7UT zl=}IVu_?WAamSs8Y;#Vg@^G?7M(Jo8tlC}0QAm4Y)rEMsf%eAp=z&NXFoU7iJ=Z=4 z7I_$C#5|R@K2Nl)kK?t~&nbs!g1Bj@yqSjGo67m6IRSYiU5+v7Uw>aLywCT=9L+G! zKwWapO%JiXr%LoXLy58;N*GNpA8Lxr0bYaPtLD!B zx(6eaLgylv9adF)PncCu#b%{bcE6GHe$A}06uZN8zoylc#VHY_jE^41t&sd4gB2pU zs+jTLN5(pLItcBfJzHojqRpH=MAI?h&@63WEbLVcEw*Y}tQ`k3@{5Qf=vcjYusXlc z8eJudGdlE%91|u}?>#cF#GcdOAyzXV2wLxz8)gE}H}fSPN}zd$JG`bf-EvEfQ|?nX z7boZp##VH{l}r@=qz-f+#(a)N9nP_k!&%;Z4YRk(gfkj8Y{WTRFwUVZ{y348hW>b` zNtGXl*~8Bg%~M*iKZ(1eQ_wvY3SVf=-1;@3r37CWVhLukTtP7FHr=6|*B6Tt^}jmr zmMpWAwYq*tJ&&D?ZQEG})>!OGcFJhV@)(D0_}s(g(eHi+^ZfFLJBVk?!2NJn!=8R% z)7)u+j){(wqKB^#{=;NLn60+l)_qIpTdfu>DIA}ZMK#c2krS4jIedTs6WKM>JVNib zv5ABrzkFuGuNPXFjOXh!2UqtVooiER#Fp;ZpQ1ARKfW3jm8i0t?7#J+S}+8TS6(Zm z5_Q#1FPR9J@4&J3QYx+;!JS~Wvv}vfa)H#DKPrk<1lD(W0PFgj&Be`(3-hYE-C6r&idyF5Uny()H_x-VACRdtk99Y7WMZF_+pzs_XzZ=bzL|_DvpszTG|Ng!DZ7LiKOKn!Xve@OnTGp2@8{v)zV0~-p6O;PGb3> z_-BLh89pE%Ic|WJb}q`nlT;_;@#Au3if~S4VuffR%Jzxj-uCEVG);qkzmaIrBD94T z{D662-#di_2XPq;$y|FYPmm&Os z7_+SgO-;|XZca<&#A#=7`4DkY?drE(8bb;(GZw3f%(L=4oV=dl#dzbO!QV>g-#=QpL*6x;`9iQW9k}3OR4)rOc;n%<<72^H& z#L*#idgrb2&y?{aPDX`izcVh;)UDKh*?}My-3KDOsFeg1Z6;-VgBQZPtcMbPyrE=$ zR}*h33f)-s?4e8l#vUT^mPPI=a6qnC(fndxdtVwaHPlpkrV&G-9=0dEiC(vqSQMO+ zk2eTL-5>25^sml#O5Jp2mBz7RAmMmPC+~SbQ=BRkt?PxtX^?ezYLiFW4{0xU{KO~k z^Wx29xs)|Sf{XjJA1^8%D@_UJIZCo6=iI*RCm)eFox41kIxdLMDo zMAmcGivo=chJuY)6>!I5Oerx!^qLiaejnIZ0wIoj8#B3|sK|l!iNlu9xZ>(kUfq73 zba2V>e(&@>x%bxG((wzaCLZ1*YlTMoSv&A6mWp5x;@X#nnAemdNa_ajYEE^X&go3xDy$x z>dl}QDdYTpO0C!G*l7H!;(2T1tU2O~MTi@Sb)}@IoEolcYU@1sO@vdEIm%(QS9&tN zU=oL~x#Op`5DDPyiFLljj7I!I{&u@{DwWEmEx$5j@Wz%l=V`NgYb=?}2pODOq8A@Z zt_JZ+Q7m05 zCe^%NsOP8J)jJTJ$y-8=+`ODR?}W2(n(kFM9!|=Yx|g1=MuMB1B6 zGT15N|D%ZVqr!DuV z^@_o692f62V;NJIjGab%MRBjT*Vr$(FY5HXj4J)~7ru_+^je;!ED7Op9^I z1M27ZQAG6SI*Z(pN?1jg2dT1&w@6h>_NBL(-f85OQu501D6d|F<=cgBFyG_|9|KpU zzmNy9ev)1RHk0KlBa$7(EtIE)1A54GTZ6RS+&fR5;V7w<0@*#6GCV4KCSK1T=9?pZ z+Vp5Rdh_X1l#92X&6)kXz;#^#&V??e5`Mk%#f`6KtI09uR4U1;pVsG>@9wT&Bd#jV{=Z>ghl?h>kNFmjvy9EiaXA`<0`QghAwqR4(|HER*@=|a6LEf z3{KtlRVD-H9G*GBD#Tw+J(!CqNEgm8EyBvpTRNR@E|rErHDe%3CXVJdP+y((sqCk9}nX0MK$0*$1LJxNY*BayOtw%+a5x7t>2gdO|7#YpiT##bMrVZFw28Fb1SP(FMhAC4#F zuhOvYa>)tfnRnw9$AX1^n@y+!+7q6n=W=<)2)3XPDf16!NLy*NAEUNq}6rxPAtam5u;dD)d8RK~xqB$c6g zsp0cBK`4i|*EsJpu3V>CW!BK#&zF@B#opmDly9;x!G)$>|7M`th{G+6xNXXy?a7>0 zp36E{T&tWTv-a`iU>O6rL6pk!k*KsD6z4^+4y^CAiq2!VM=9@?9JQcFlPrsJFP$D( zo%D+wk_*ov`LsG3ABs(=GMw^X%WORW_PO%T=@!^W=(oV^LsvudyV1Zk%1a;)$?p#6 z=0J_klM$RMahmhe^wH$>tf}$z*iPll+`xIw>p`&Ts@IJWqMJ-by~ztM%W$eJjkw() zZ&NR}pz<~OjByo4pW=en@JL%CkA~4t10L#5f0p~*hGGGX>gV5A2HAe-^v_77!zJAA^>io(Q$NEVLcIYx; zCn-PgjLB0&lA|5u1$NrXSDMza3Hb*dQ~c5#9*iiz%=vYe8+H-h8p&MKcR}(yED;)G zr;((S_S--^v9^|Y`rpZD06RNI7r{*WaKzMoRN54=6jm(LItuHh8v5}NErxZvcdOSn z9P~1_tK}Ino#Mb{xRdv*WQ>EZ7hk$ae26cI%580mFvm@7L$ z7@9gkdfd`ocf!lsM!9u7dmZk3r6%-WgJ{lq0^Js4}JT(pf(BN-y?}VWZyq*H^k5pZQaT z%FtWTszje-D7n?~Pc_~z(KvpL*yicHR#jGiyj7B)qw;qJQ!G1$yD;ygtG1HiT!p$z za3a%4OoiuHOPU<;PFNE7_FWQ;#-$bHuP?yQFGm3ONY4fEfk-97 z%SxjOC!B1>tv9`B8pR~@e=pVp4#z0Z!!hpP4@cQC zN2f-p4?e6C3x$uzV-)nY5{FaAqQc?0^X0|eH@@WMa6G4?@KT54H2-@f>e>V9aly&c zt$hF$W1OMZOI3vX(&4&DuOHBQ2_JHg^=+EHG-r&x=&HlUva&i~HcznFgEJ}NexkvL z(twN83sUQR&P{%JtyHxf198OP?QN54mZH&#N7^OFI`noG#qQ52++9HiRZg`94x4|c zbT}qP#i^F_k-XI13lbT#iHBi=rizE#;lfWJ3xpw#4xMn0%npFZ&z`f{03)c@HN49l z*1UAFw8ZgcMW`P?yMI`X-4@Z?Ij57kj2YcQS4>dYi1dLg@IJ5d{2B)tZbk=YF4t__ zqgk+TSt4gf*zObFuo!GDq2ilqbCYfI$Jc;8zq#LC>1nEUkH@}SebmV=F(_DD`C>hZ z3pCcnT@%Ukxi<_FdbM10+UI)WjdFCBJ_b{&Y-wCOA5A*3dCix&7!rz;6dlyc;_y~v zi-$h}wi3y~#4!WUzCJ?lthQ8#zgtsNg33778wWWYISz=l(uvN7@p+MErIwbMUK}Ic zs^PhVIsbrl5Ofva8_TFEJrB2h*ok+4KtIEs+&!3d?u`hOtF;h2F} zj-lMfpeUXY;Ow*Eptd+g<6xHJ!z~)?wz=A?s->`mCa0f(TSn8fC-b}F%&o8_dq>w%6Lf5CC>^;ds{-c0i0=fKj>?Ei)zg!UpAny)PsgG` zkynsS#t_dgomSE}8VItlNskjBu%jKjc%^o!hF!QbDhq!x?y_j@VX%WIIzXgg3co4y zv)E+g@CD0Z40jxkFV+d7Cw3f2w7z8L7z-E@V7h&9*v$*8k^PBkj#%p~mc?tJj5A0Vv9(43J(QL5C+n5^1 z@2@e@u{SoKE~6M$$-LPPr+)6F{EFYhQ2B^flqvfUNT{;w6K_X6uTwL2OA+nn%iFu%L%z1r zvNl=<@Y)KE753+acUaG%XTtvY$p)I^Et7W+9Ap87?X>=|PejMBPp&*t&flWEn8Ye; z`E^`n7gh2b%ifQ4PIN@hE&FivTT&w2oJxM!$XO@WF1V80P@-qPW|H11bnGl#t1_~A zRi)r6a#bF*0ywATOZ;Q(LlaPGm)+^LxpD#n`^mgrQZ07|RzWdUP{2xw{kL{5wQ9)% zEq9a`?=QC{d|tvlOMW*oc!sI&r#DCepe28PNWZ#h0gM#VglUNt|y0to!OfMbt!392f(6XbxucpZP!kw{uPyo^=F zt4?bqx*KXVi^^nMjVl~+lxCLl-L{#bo?*Xhrk~8Zy2tEG^UI`8e!DIo85>yX*5TE> zt}5wR;)tn7bguFdl4gV$l0@KirGZ^s>A~dGHet785gp4K5w@a@$ieH4yzensW!|y! z5m-h{Sa~Cyk5C&Kf>Ev^JYg@(C;C$Dkf1_jQf=^fh_Ii{ma}JTjZIwya>q_qv3Y=P zCl$bbsdVkY-DTDDad2|^nrfQWwrX;X^CvU&Xj1ZvFH6k9UOh9gJH{06@Mtv4%Ex)cs`7Ky?P`<#dd~eVtth>UHHHH zN3|zpQa9APjm0k|P%iJ(^2mMG$s5P~Amwtb_AdQQ!2p>JOg6+r2(HR#{_h zDT*14w+nIo6==|q@k%9Muui0(r8AwhUG)Lh>iJ{Rj=;EM>QWBX(YV zL-Wk3I4+XflBHp`#0kn{aXjF$sIGKMPI}MiVT(k04<+#%7|PNtR#%&bvEIp5GSnk- zJAM)A86(j+!*R6tu-nV%ZjGM{oAqpfE;**P=y3%Vbx{_g7zu}mx*cOmq_902IYvnX zb-mbxV^GN;Mzn=fnRDU}>`zjr9xs9M`2et4MFUnvEZeD!B?fg9n`Y?Hx}ec18aY zKL{G3OHWYfNHg_7GMXl<+13R;lb`XSbVzV!(>MWWoGOO0uRbv)k<<=GbOnDfUO5iU zgzag?1r#rq@*JtBduAt%QVJtu)79An`?}fs?MDoI*p^N7LHqjoi`tLa(!R>v*|HGo zvX`e53-$qVS7Zlb+2GSMzN`hazzDsW{DhnTMDf|1-pPieFw3IfZ5Ry^-gV)yc9$4^ z9IwyIKqS=@9p4whGVLk+P0V--yG^zDzKkg^v2^?R4Y%QC=a-gVo4i{c6GjdGYUI>s zP;fj*mEijQb5=vH_9D8nQvOfH@5PliM0V7@$TkJp?!^r!SbNcz<>kA&Ed#rM%vsmX zV?&X+Y%TCX2uG)jtIKE)kFvR$-~zh-EKOxt!m2za>{&%+mDZ^%g z6YLD5-|alEd(Vq}HWTfzVg8rT0w9Gd&Q-N?7gpTT_I5}7<)NrL72<%W&vK&fxVP4C8JU=Ush%tR~CG|kK^=eYlAI{oG z=?+GIlDq%B96^*Ryv-qv493IUoUKY&qt>lTj&jezW=gY)CdV|6geswD^P3qbgyokk zjO&~Xe}ib70;|L}4viwJ#QAa4qIw!eI#u4PUfC{v`A{R@_s_(CIH5#HIljbRUB#6# zLai?5VVh0Gzlec!$pE(HC!ZC5;3-j^7)q4$ySt%A&nrIL;R3c}6|0S64Nv0~_p)q_ zQj6A<4v5cN+ugu>|6fbiu@~!3i;qx?j8%tV$gqZoP;Am`DH@nAqJzVc#+jwEsfSLv zF=(9PY}RWTcdTM{Of&W56{FRrvnRato7nBgHTLCZlXWCQxr$^drlJt4GtS?Oe>)+~ z<&*sgZAb7S2c5DIvYFWV^70Xwn~gE@-UV-l7xRUjPB50#>VK)z^jB+^NNi_mQG3VZ zby4{vZjyN0Ju%khYL2OWlzyB|8YvqemY2ql4*BaMY%ODRIo=Mr4RkW~q~JirqN>Dv zKHt_4rx=C@p{SuisFEHWkdpH4Bl1DcS{TXVC=a0pSa8*e9+w6n*2cV(3lRnlmZ-Iz z>S{ld7MxmI_n{b7A7+1%Xd?#E4SJucT2bcqtyITN9}mkb1q)e0C@)6>=Q_M-;= zG?UdRLi3Y-l9~_P_jIoPODunWt*pmCAZ2EH&i?$!N@ZVH`1}iQ+*B)i+Fu3elUUzN zt__{?Haj%*qxfXqBYFGytN!D2UUvR!W&z#mr7i(T_UO{mu3d?VrVr!D^ln&V?+y~x zhoPiPT($BQ`N?R<0m*<)(QeZVi6uaGy&;a@1EX5|u@pWxr4t|Rw#TY^_AcJ&8}
    sO0&5jJI;L(ox>fa_EuCDLL>jNAw7qhJ{u(t6kI0p2{xXH^;J7K1Nv$)*)A(dfA zTS!k=yd)De*1)$Dre>zfxmusM6A!Yu7Lwnww9@4BVliV!X^4~nS8?o3xIbRSvwA)< z)KOI15*%1;>$;u$re5 zwxZ2sMWT09=2P|vT;c-4S911+1IruEgyR&Oh*fUo@q>tlY9OxA22$lR5Jc*M=(d0C z)1?^e%|1?O%Z(pCV@=*k+X+68I&du*VWBo@GxpV)*EDJk8_1;RG2BpXBs%fgdpeY? zRrxYt*lyBgTAea&)f{ETuA>lM&=`cQHe$Hiv6k<@#R!r#pcAd$3Pfi#Gs zO-dVJ>gF%4Y!Wr3w&l0{5KFX-Te!V$G{@7HVE_EH(+Q^Uh7yTp168MXx$@5&DpP8K zbtsM|a3~A==1?{pJm-D%k^w5mPus~%gYsOJtzKe9r{7N*GF2^J(m#X<*6&|>QZ@vH zQL58*ZFo#UImkNR>38hYmAO0Ns7!jh#o<1dvdjnQdEDsKJ+5)Pc&WGoR8};OO5JKt zw~yfr&LiAG+`V=UM5Ho*euLA+w@xXxw8{CQqxANK(v0SLpf?h6?pAE&;Vv1Pg~8~R zPNKvyyXXh6@$JeUwL$)SLgA>+2it+}0kO1Q>4uskQOOf)9)5)!{_Oa@ydR17Gs2g5 zkR(^Bs_ZsK>hZGji;QKZDDR$EhNb+slY3jG8}G*z--_6>r+s7YX7l&^XzYnMex<51 z!sMrQCAW5Y^_6Od`k}s&+9&seBe#Xlw@V7F`rCHd zV@4Gu`YWyc%H{*7d3Od4&a=G~=m#HVk%BDjfsDrheJmRA%2g=2 zYO7pZ4dO%yW$^UV$7xh3k z%ZZCnYpwSz_{g#zKOZW*difCO;|~Palu|?y!iw9{vDSdu{5)(S+mN%)uqV?iw+=Zh zYx(1OO>e_3BMgguLuG%rs~t$b!|-m`3rtV}V(a>={E5#E3lf80}o~S#%#1h0ba zdCIgYdTz`c`vBEsY|zSZIsGPC(q^cBsuwie%;B|{u#8K~c1s4cc;@#p8maQj!97gJ zzaQ-@lp(E}X01=zDttgsdpGw}AO7f_9@eP&mn@?=j3Sw=)EA5EYysjYY<|{-b5TuR zM_Zr6_EUiR78_Y5Eme8%OZENEvq|#MwK9rPHG9RuKvk$r_Jq=mFEvyg%kKR(o6fWw zgUX6kpAubAWtD%MQCG3B91t_TEUQE1y-u_|?8cl<}?O4Hx zMTe>(2qO{ZE}Vg?hNkKrO9FWd#IGUiqknIhd$Sd(=b@zM4}0Xd6UNn}BXA@If6n#Q zfmt!i+`-|Rj8^pQt>d_^+_0nQ@M9gs+v1<{P#R@WvSRp^Th;b*%VCHPCmTK{+h^j# zwk#)!tTheGOJAh&dT~9!eBdgi7V)ir-my^qi63tO(*bCfMRZf|y{4=h3VekRO)2^? z+(_+P+vNGJnqXME@(pNl`StZSO{n2hbOKElt!z5jG;uyG?121$Tg=F9%dgS=VVj=k zu-oEg+~nB@G(ptf#*e>9i^mqPMkH*tuq5n;zrz1M+5IJIzSL;X5YrkUO+w$~f!XVu*=DoYPJTK)D+`bAoQI#wE!{|@ z@z6+A#trk4980jTF$*Yc_|b(0uXPqpNFt=hAwod!BiUVJ)lewhRgOxVrW^l#%Ro*0 zRD4Yq*{N4<8OvqnyP8eMRk_+a-;zbsv$W>Ue|m24r+Nv}mZO5*wc80PikIzh#M}7vX|{|MV;Q47tWJuh zo6T!oA~hG`;=C}wii7d|9wU)n$f-J&o>zQM%zCj(TbVy$NFtr~0wQtJGEgfbspr;#;pc*h2{ zF#2D$%uX;X*Ka6F=MWW)$}$=l?XN4+6B!E{>mOii*XjD-FSu9)M|rO-sDCcOZTG&2 z-#j}NhCtc#A9!wyi4ZNrEJBLqp=qIPvM{`BW<76-_k{WhM|5_$v`dVGwbwUO8Dz_r zu00f5rX!I7wOsHsY^q)ERX$sJGeRgOiuOG1bA3%?+VU7mD5$nv*OFaug(i7jFMM=- zl<8O&6YWUz{5}#?=$vYjf8|{yOqKs=H5rSG!sz zycerm>(xW65S4$i($C>NAL;UP!O7f_2aBxdvrqcjaQJh)@kqpn(&RHA>4P^zk5+?2 zIKO6x(QvVxAYPp)zLqGKPdatl=->F!kpHdU?D*36$9i8EadN-l@U1+;mnMnX%(@pu z-R1{(*$x0^F7GQ{yL7yTw`%ty7G+sc?~{$@x}9>CKqrw6O~-RpXdd}Bwmh-)PMu~n z1)U&AM_eS*3A+|*Al2|+6|ocM0B9tYy{=PINy(+L6Jf84E$f}kn-r)r45Q`p*u~_v zB{AiVEv#Lv=T9APJ6!II>co%u6?7WIAyyYr+|N5v^B@ibYCGVWs^Uo-V}0Blx+LY) z(N3hT0UcZFVkgSUm1==j|6(H2#jE`&@S<148bo^-R0|fkS>w~qL$|A3Y0%GkKy#w>-lPLD?%4jF`^b>*QjY&TEsH? z{MK*Mien;?S)|B2_1g7$Bzr)CiQwqS_<^R%#`A~eoD2O*dV$=Rswwx^r*ap~oG=$w zZDX+k&NoFGox(Xsqwk7nuZfzh>$ZoYQQG4Mae7%7Y38Cw+Mx@d(|F^? z?YE&Vj>hwY&hf}!xzD%ApLT}h%nRE#bg)JKcrm8lW#${Q-`4W z5*wZ;>XKQ{U>1L`;|wAXcgvp2<@S7Mc!rm?erB?Aeyx>lR;9?X=kwH$eD@s2#1{CX ztj*6~dxX!vInr8E?A>+)GiKvrM)JC?As_MKw*9M*h!LrN*u}~~5=yYP#btkBCw+J2 zYpAqqa68!ymN4V(O81>ZY^kXh9uViULF`leT5)8nx7Bfwny&nkFZcbl$9uWrjTkh= zcBgogj#b^ae>bLEE}muZvzc|#dhaKq-^(-naLV|FrJc3QQ4ef0$1XyTIvDZt7PCdC znB}NpaXDtai!B43PNYW`s|!tk7c(4`9@hCZ*yV6o2NQz*WQVyAoPX2%bf}R{Y8`TQ z&5-^V$9MS+x2vv2F2MWwx)E^f4eH98OetY{-9wU}f;eqJb!}hp>ipsTR zKW(i@`%%p;XFrZXnr?@QBWCuayTf7jBhQs8a3jMvmZCCE&K5UOXYqv&kjEY^-r289e70jZE9U~KZ|YeXRsg56Aq`# z>Ck?P%V??gBQmRFT$Whd6Jjh7`)c6?vM=BG+Ub4y2Wn#e7WO0c>#4Us?nk>CluIG^ zqi@0}xF4~J#jfsWf7hD4et+%P1_J#spAx|@|1BX2ma!yr}#VVg&ol21V zWzRUj^YX#Ffg*R@5OlfInuo<&eJ4QD@J&WxnmN8llB1f*z>kYKg))FM3T;<8c? zm@3o#QWf`_5us1G!Hl4way-9}aLmpNr3H`TI2v~(BiGmrMmB2>3i0B?`H>XS0kLPCiCa!g^8(Yo()vXQ_KZa-a3;RFzS($_P83(;anNJ9ohx z5RbMr^}~$*QNyO=kd`ofU5itT20)7)J_fpJrf1&?#(E8>N9~PnIbiQGuMzE!OX1}; z3;4VE@+I6>;9uakSfumRR0$Q+Pk0K+Yi)g}QRUltI7DXc;Hxom!gDeoP{!gGc!lkP zMP$WRm!=}{*@0Z2!M#zsoWO2T47gU67mT*-P)$y9^@;lo0z>%q`c;R{y2Xt)6)3J= zl~YC&^NR?^<9rr9i=TN-WB4UCqXOnY893v~K7I?Yw)|1)q;;52vkZL4int{&;|7d` zTQAG0@bieQw4t(+kK<>_cIwO8@{IFgRP2*@J7bqPmT@ihV^QFOM>rp^MTtdEtQC}$ z=p{K?m>W4$plUe~XcKm)oEF8vI&ejZ*B*&3y%VY?QkjS20$%+k;DTOk@7M_bd-oI& zUU}0Mhsj2Uxl7z7`-@<@$Gap3nKArIewX#hkmSBeEk= zWp0X8DRG9;*WsXn*tct55qx}yi}8+8C`Y4N6dPWTclxSy*v21SzAIP;wH9^8n(us* zf@@n^t&Q93&bkgT9NcN7=vv}{0C1Odn%Z#!$eWA5NNdVtbAqY0k@;1$&T&>M``VL@ zzB(B$H@A;|ve_hPlAe-bOCNdhxi-6uzseiQFCS8{_A%#v8}RGqAHPru(|@T4FYGo| z!l_CxsyCSMGr!CrBCnXnaj|;k6XuY1yPtSDkF9G?!0(8FN{DA%aUgvBsxy4GQ;%4N z8uSu+RxG3Q(zJ!OtC}zV#BLjxA^pkN!}NPAt7-Rl`B6EXHJ=>m|%5 z;~2asi7<$>aK6d%7zk8W&+onEm&Fu1TAf`&sX5<+5l-PX?_ZC5YC?4&X*xgJ(|O;(ARAu_`6gsKC3LTTCr%N+fZbC7l6 zj!Tyhx>$Bk?fG37rwsgSYXCb|U2|q~P5YI>6HEnBUF!o}d6V0%GZ4*7GJ23tM@W9% zuzK9%vWZaERhu1#W2RD#)%zWei5%YZK_cYiQz8g#H^;&0eIk_W{Mz|9$?b$ugnSSk z_F!cC5N7lq`c2_ORig^Gl;~nU-`%`9KEGMOyk6XGOZazZpGz<@VFlhw`m)MDINd1| zq5Hecglr?^0*v6+NlQkKNYtPSHu9~kbm{%I7v!6{JiGj2Ly2`}U&cBE30InJoLqAw zwPJA)mJhztU_*DREiQ>7%(6>_b#F`Ht6n)#u8s9VnRR?(mZKviRB@4Zc*V(2-_d(Q zm&ck(3Y})laR$nMOQ!q?=)+#W&{j zFiM!X&=rtwF1yt|_#U`V&S^_IF=14(5BYO7eKD z(sHr<>1wWTlYZ0XX^Lbqli!>i*LK`6;-!;5TAQ2qr6tkfSav8sp5$Pp%c?w9FEkJc zCLMhqoLAIN=nH+Oei2BG&tx-CktwgS>~5q})8RCp-=-#OqUchKLO0;n42E$J{h~|* z{?2dCUOZo8G@&gnqVtPAQLo~U*d@8_!b6io48A7cUYun}@H05nY{m&re!wd+l~_&9 z5H_U@o!{1AI~=!pId(U*P5fmvr78|wA-PyrJ_e>~+tAsIHF$nLDsI+^~1w zWqB|{DGqAy3GeLj-sMRTlNTfVE#=l$(3h}s_fNJ6^lAfvH2gJ=Ig-d{sr&UuqL{2U z6_qA^frgq*sH_;0_1Sdn&>^miz(cp=W-Wu4t51u~IxwYKT*qquOcmhhXXS&JDVd|H z5Uz%0@bET3b#d77RY;j%=Q?)erP{^iQFo}&Z)aD;iqfGS=;4OoN#v*5^0zO3=XZ|s znAHDI@=wfVvhk~a+R%Kg)r#-`I`R|!-l@j#^?eI5(}%m(_kQ}*pEy<;e$|<_U(c^N zXKYu+^*^2Sf5i{b!Y5-9J2!@(cd~IXwO8%dh-bpK+VtNFbOG;uG0odOj?By7xDXxn;!FfSJoK_<_Xzf2+|RgZ zjB#8%bFP)S7_r8-p>G!O(=*JJUS~6K1F4cMc&~KE8D61z!suwN)Qs*T@Mdv!XNlgl z>=A5~CJ#x{Z>6Wo>6dNI1Db^Kaq<+OON0It zC7hP&!gh6`h>GpHUuafaQQ`^aXD^bcdlp>P-3+C68L^baqv)^f${lGR`TYm^f6ok5 zo4F%-P$Y>V9FHQ{64hHXCD(MP6ZKCXaOOr2T~zeR`qk`ARgG89fZ zV%KiWD4()a(;<&FWdo}PS5uh*O}m9CT)_}8hzD~I_zyfL1m?}BJ;L2g9YYq!#p_8tPr04pRZ5-aQcfn$7b?E%( z#6HLV>rX3tuS?f~^a)hEV?J9PhgCFq12~*TQ`i{x)mZGqv#U#Ge62sr@+_@ZO&Jj| ziil?p1bV+T82iF#GRMBBHTmi?T&~(U6s6?CMxDMaF2VK12Ew~uf40z&HPf54N-g7}HN@osS$0&yE}=iH1*Q zBSw*(&F-pWZMl_#pLT9jz&y%ulNwjl?;ekO;+@$ylMQUi=b-XPhw`vxKVHZCQXWeB zp?7Fm5@4l&bBn714bv;VDAxn(uRf`lQTWNYX|NQFln%(O8s&CH^~M&erP&iIulSJG zVZ`5Y{4!K01`-w^hJss$w4FxRbU4QoT_yjC>si$6TEb98Z$ZT9%s`8~^pWe<;`X7; zezV?voIg0IH^Akj>>Zr0>Y41W=8r_tzGO+Ne4gV=VC~8%aL$D?RRG)?-jyVJwoY`T zQ)owzJbSQ+C*(2`XA|ruH~Qnnw#nIjdD!io8sP`)=c6^=!spE@y@(CpGI1~FpX9X> zssfP9PDP~8JQ7K(w3$)=d*#D1yL>_p=F^?&Xu7%sF?S$8r><4zTc$LH)x$LTcsO0X z#C#k&0<|a9WJuRV7h|UGFZxOsJlYQU{Zj~?Qwfm+k}&S)B6mGroc457oywe~dq5Rf zb@qPldzZ3FEu7-y_2B_-&1)!;&Uf?W<(eN9D+BXY1whrQ=it}GdqO|sez#jkLVx$h zDmJ#=*BmLaYstA@X6|LD<7EEHUsL|vxc%Z;>TlGs9#PR}a!CS@j=i36 z1CKsuVdt6ikHiUQ<8YUJxmtdXO0$mG*U;muPwGCezL6&Zyo&1Eq`3Z+U@^3Jq8wP{ zEJ2ET;=XjcWrC++0_9V(^LaS@YCU=9BKAytK)iD#+pkza*VqTAMPrN~xgVK9N{3lf zGJ1`?8)4q5jzrR;Nm+G?rW}k`1ujDGh5vS4a%p$5rKG4ia*Iy3oX3`L*$ zj=YPGrgAD)(xL<%kSPIUMp{KIvMc&Cu9ZSffB128ARZ@Pawy2>RfOK%1&tT`g2zYr z_%T&siZZ$7zoL}%*QYv>(Z~K~u*CeR#bi)1AykP(NHT8(ZrUy4bQyC1K6w-B18HI~ z5NhEwRFrk&l)cvZg;6mjEID<|Ytn2yZVG$C6W^c{3bx!t@acKesL|Fi{0Afx$d**_ zJFaFat*9UEEqXp`NW{s(9p7!0##dgz$CIedp&HLOI;vAPkROyb%DPW43CDDRg5fED z;}v!{KDhMRIzE-};<%Y#ew5&ZO|_!Zq}tsh*{tu``#16U7^)c6;3YE-s)lSDzfX(d zuYO2wed0wmHaNYOHl5IR_uYK{QB16_rZcim`wDIE(!^y1R^Oy!l~=57V{-`&i{`Y{ z8X2`j)%!g}X;}^C%2}UqaxMk~jWYN#@~n%sPJNAVyLhd{1)W*bhveSy;bPyCUy+%1 z<;j{an?tpMo5baVj)mE=9#tGCkmwb{Yr8rY7q4IjxFf}H*CRn*Q4ltf%uPkcH>$Ey zjJ+)VIy6jiO|S)l^dTRJ1TbeFgDl6gw+sO&$0scwnw4_au!inOc_ZBsp2d?3|M}iY z1?`8WRGBCxzt-4lMkjoKC5iHEF7U2{9jjQ(K0ZKH+9!N$ZMH2bbwGl`%D;q_2Jbzl zX^IKY1nc+=CxvEr3lS;C97P<~_BoxtYy@IufGkQE>op_xJF$9D$1021T2~?)N8Q`b z8w+&*xCWAV{6I8ZkQ+p$8KcQQG3!`sZ-A<>z1S7bjiNS{8deQ`BvvPZV1HOXobcON zBG5ayd%jr5o>#x*P;zm0Tvi^^<*~B-%sEA*vTng>T`t{h#bSl-9`dS%pY>|(11r8^ zSA&k;i!$94=Dekyq-Kk29Go4Hap=|Er%@-kVrKOLH<1wOm2zs{i}?g`yLd0u?Diwa zs$I{?>jhrbQ6jS9zhe9fs}>aE&UD5dI~sv35ilNEH%`y|ZnmXq(qXr;4ug^^%mHz} z-a-G8Cd6grXv#+2+12Tayngva0@Ec7R_(0w2{%oqc-}jN592~%&+jJF>*JMwr}HnT z5!_1fo%TsW!?B9Di&D~|d7QzDZ@NR;R42)c`TWiSO58L6(C^-|LC|F~Wv^v$iyF%J z``X^tBZm9RMwwn{*#Ba|8A}|MmsyWx43lne?{_23i*=VwS;-EYA}VH}9LuEld)%d( zZ?8(dPSxjyQJ+0sy_w%CMhW7&Fj`&CBPro>^>N{)m$UvNQq38OtTn#cZJ_^Hq*AJB z+xFT6fjXgqKrLWZ57T{Yiz)Aw&5=K63F~;860U~+B8aA&t^cRgW~?DzW-NkH_9% zBr7Gwf*}p!?tz>0G9cgipR-acp?vCB*ja?j7&LiR-TdGk6b zN2O>tXFTpeqkHmuUr6Jy{)JqsF)OD6TNWs%4zy7bh`i)or=*KT#9RJ#H{OCvj@YjCx)o(oi zwI4tG2QPl@)r;dF{evIB`td(J{`G(KuTFlOI?FW|`02A%S6;my<1A_5i>u!w*~1S}9?5dn(`SVSVZu!sYT2v|hGA_5i> zut0&U=aa}2v|hGA_5kOu!w*~BytfJabOVviwIam03iYhh(JID z;y;0qTm%pzu!w*KA}k_c5s6%cMI2Z}z#;+`5wM7WMFcDmVSxzCe-0rR5wM7mnFv@!z#;+` z5wM7W1tKgWU=fL2ghd=!M8F~f77?(BfJFo>5MhA`%b!5VMFcD&WF`U@5wM7WMFcD& zV1WpW2v|fS7hw?x77?(BfJFo>B47~#3q)8T!t!50$VCJ!B4j2477?(BfJFo>B4B|C ziwIamA{Sv12Nn^qh=4@|5F&tp2n0kR{!0kSMF1f}E+T*s0fY!3L;xWI2#7$4074{k z5eRXB5CMb;AVeq-5wM7W1tKgEVfj-Cxrl&8gv><1A_5i>u!w*~1S}9?5dn)xi#V`|fJFo> zB7hJ91VkVp0`cb%l8XRBgj_@bAp!^yK!^ZB1P~B`5CMcpB47~#iwIaC!Xg3|k;p|@#DPTwEFxeL0gDJ&M8E9sB47~#iwIamz#;+`h_Hx&MI>?&7I9z^0gDJ&M8F~f77?&Ogaslj ze+3~I5wM7mnFv@!z#;+`5wM7W1tKgWU=fL2ghd=!M8F~f77;**00JTq5P|rwAtV<8 zgb2Ba073*1B7hJ9ga{xY0wDqjk;p|L!~sGC5F&sO0fY!3AOZmqi2nvcauGm?kc$W) zL;xWI2oXSt00JTqB7hKyTm(WKAVdHm0tgYliU?Rlzyc8#h_L*(5ONU#iwK#CfJFo> zB47~#iwIaC!Xg3|k;p|@#DPTwEFxeL0fY!3AOZmqi2n{kauGm?kc$W)L;xWI2oXSt z00JTqB7hKyTm(WKAVdHm0tgX6hyVg25D(j2AVdHm0tgX6hyVg25F&sOiChFi z93VshAp!^yK!^YWA`lRP_-hEsMF1f}E+T*s0fY!3L;xWI2#7$4074{k5eRXB5CMb; zAVdHm0tkpeKm_7{gpgbW5F+Fv0tgX6hyX$a5F&tp2!seAL?Rb~5C;emK!^ZB1P~&C zfCvOcApR!^$wdGmLM|eJ5CMb;AVdHm0tkpehyX$)auEn|fDi$M2p~iNAp!`9KtKfI ze}<4;1P~(RA_52zK!^ZB1P~&CfCz*LAVeY;fe;4>5kQClLWJf*1S}$8fd~skSpF9X zxrl&8gv><1A_5i>u!w*~1S}9?5dn)xmz#;+`5wM7WMFcDmVG#j~NaP|c;=m#T77?(B073*15P^UQ#Qz2%xdut0&U=aa} z2v|hGA_5kOu!w*~BytfJabOVviwIam03iYhh(JID;%^`%7XgF_xrhKl1P~&C5CMb; zARq!E0tk`FMIgihLIe;ZfDoZTM8F~f7KpGwgynA`5kQCl zLIe;ZfPe@DL?Hez2+2hNAwn)9fDi$M2p~iNAp!`9K!^ZBBytf5aexp3ga{x+03iYh zh(JID;{S$_Tm%pz&U=aa}2v|hGA_5kOu!w*~BytfJabOVviwIam03iYhh(JID;{Sz^ zTm%pzB9WYOF$We2!6E_(5kNo$0-^%>Hy|Vz0fY#-hyX$a z5F&sO0fY!3AOax*2$9G|AjAPe1P~&C5CMb;ARqz(5r}^iLUIv6h>(j2AVdHm0tgX6 zhyVg25F&sOiChFi93VshAp!^yK!^YWA`lRP_-7E3ivU7|TtomN0tgX6hyX$a5DJ zA`UDfU=aa}2v|hGA_5kOut0?6-+_>e2v|hOOav?G7|xd2v|hGA_5i>ut0=G1S}$vi?E0TiwIam zz#;+`5wM7W1tKgEVfh7wTtvVkLS`ah5dn(`SVX`g0v3p{h=4^TauF7BU=aa}2v|hG zA_5i>ut0i#V`|fJFo>B47~#iwIaC z!U7SNe-A<~B480AGZCB47~#3q)8%z# zmVX~YE+SwNAu|!Mh=4@|EFxeL0SiP}M8F~vxd@9mu!w*~1S}$85dn(`SRldz5tjb| zLM|d;5g{`Xu!w*~1S}$85djP2Uvl;~Kvtb+e&^{+!@V@!m+lLavE1Oc2YJvKYp@49 zB$3AYXxXsS8eTcxRGs+`LHe$RW(x%akYq@=l=|NHrz|NH&@9=Ql` z5t0~erBm_tZkPsjtKmw4200|*Uk%T%(2#^pU zArK(~Tm-lPaslLW20)4c7Xe`cTm-lXa1r1lzy*+t02d)ik&8OG2yhYLB0xfb1Rx1O z5`PoGy8sCRDFP$}NC=P+AR#~kkc0pUAxV*hI!FkR5FjBCAp%?kxBzkiR0CExFA|xqtQ3n?RE&^NxNC=PsBmqd`836ABBm|@gkPsjtKth0o00}@60wjba zMH1>DAwWWagg}G{a1r1F$OVwg4*;YHa1jtDz(s(I02cu+0$c#O2yhXS6uGE_ivSk^ zE&^NxxCn3o<;36PQfQtYZ0WJbu1h@cl5#S;uDRNN<7XdB;Tm(o6kN_kB zNaF7Tco!fcAVq+L00{vS0we@T0Fn?OAtWi1PzMPC5&|RyB1C|T02e?mfLxvhkRrfE zK$rj*0WJbu1h@!r0pudUMMzTQq7E(sTm-lXa1r1lzy**CAeZL=qzG^k5GKGyfQtYZ z0WJbu0J#Wo5t0}uUBm^QvfQtYZKrVn>{vLo70WJc<1h@!r5#S=gMSu$+7XdCpk|Gy%a1r1l zz(s(I02cu+fLs8%JP#m6fQx`I0WJbu1h@!r5#R#IMSzQtq{u}bTm-lXa1kIOKmw2i zAc-FWco!fcAVq+L00{vS0we@T0Fn?OAtWi1PzMPC5&|RyB1C|T02e?mfL#7QfD{2P z0>T8i2yhYLBEUs}3m_K(E<%zb7jR0CEB3 z@;?Je5#S;qOn{337XdB;Tm-lPauMJnBq?%H2NwY@0$c>R2yhYL0>}lB%Z~u02yhV) zCcs62ivSk^E&^Ntxd?C(k`%e9gNpzc0WJb01V{jq03`8`0K5y35Rf83LV$z-2>}uU zBmhYWkPwm-NvMN_00{vS0uds>MSu$+7eFrm7(j{u7Xe`cTm-lXa1r1lzy*+t02d)i zk&8OG2yhYLB0xfb1Rx1O68{9iy8sCRDFP$}NC=P+AR#~kkc0pUAxV*hI!FkR5FjBC zAp%?kxBzkiR0CExFA|xqtQ3n?RE&^NxNC=PsBmqd`9DsKL5&}{LNC=P+AR$0PfCL~30TM!z zA_;Yn5FjByLV$z-2|yBnB>pLYcL5RtQUpi{kPsjtKtg~7APE5yLXsj0b&wDsAwWWa zga8RZ5`ZNB8Gv^I5&}{LNC=P+AR$0PfCL~30TM!zA_;Yn5FjByLLh4ha1r1F$OVwg zKL?N^z(qiq02cu+0$c>R2yg-9BEUsRQsklzE&^NxxCn3&;3B{UkP9G}^8iu=xCjUn z;3B|9fQtYZ0WN@C1h@!Eid@veMSzO{7XdB;Tm-lPaslM>V*n`vTm*y(a1r1lz(s(I z02e?m0$hY7MK0>#BEUs}ivS4$5`ZKCN&IgB-UUbqND&|*Kth0o00{vSfFuM+2uX@0 z)Imakga8Qv5&|RuNdS`g7XaP`NC-#~AR$0PfP?@E0TO^D1V{);iX_xQLV$z-34v4* z;3B{UkP9G}3jk6CxCjUn;3B|9fQtYZ0WN@C1h@!Eid@veMSzO{7XcCiBmhYOlK7VZ z-UUbqND&|*Kth0o00{vSfFuM+2uX@0)Imakga8Qv5&|RuNdS`gR{-7xNC-#~AR$0P zfP?@E0TO^D1V{);iX_xQLV$z-34v4*;3B{UkP9G}e+?i-fQx`I0WJbu1h@!r5#R#I zMSzQtq{u}bTm-lXa1r1lz(s%yAQwO`7XhRQa1jtDz(s(I02cu+0$c#O2yhXS6uGE_ zivSk^E&?P3NC1)mB=JW8-UUbqND&|*Kth0o00{vSfFuM+2uX@0)Imakga8SF2oc~S zzy**CAea9gK#Bkt0bv4M1h@!r5#S=g1(1sX7a>WJi#oUna1r1lKtg~7APGPc{|A6~ z0TKdI1V{*w5FjByLVyGy2>}v9k|GIpkPsjtKtdow1h@!r0ptS6O1pg$#KTGhh68!4` zxC9Bfv=SEqE&{>?xCn3&;3B|9fD0fO0WJbugyfwF2_0NCgo^+d0WJbu0J#9hOB+Cn z02cva0$c>R2yhYLBESWZivSlPNs)^>xCn3&;37amfCL~3KoT7Q?*b$QqzI4@AR$0P zfP?@EKoSBZgd{~0>L4LNLV$z-2>}v-BmhZt0lW*45Rf83LV$z-2>}uUBmhYWkPwm- zNvMN_00{vS0;wXvMSu$+7eFox08#|F2nZA4BEUs}ivSk^E`VGFxClv#T-3oufQtYZ z0TKcv07(FnI04{YfP{b)0TKcv1V{*w5Fi0aLV$#jq)0*?Bm_tZkPsjtKmw2iAc-dc zybF*JkRm`rfP?@E0TKcv07(dt5Rw#0sDp$62>}uUBm_tRk^m&J2;g0Sgn$$Q5&|Ry zNC=P+AOT21fP|2wNJ1SX1V{*w5FjBy0+0kCiIV`{1xN@;5g;KzLV$z-2>}v-Bm_tZ zNs1)YK|+9p00{vS0we%Q0FrnTz`Fnm0Vx6`1V{*w5FjBy0+56N2_Z?5ggQtFkPsjt zKtg~7APGPcPXTxrAR!<{fP?@E0TKcv1V{jq5FjBWDUwhJ2>}uUBm_tZkN_kBNa7TL zcL5RtQUpi{kPsjtKtg~7APE5yLXsj0b&wDsAwWW)x)9(Zzy**CAeW~BqzG^k5GKGy zfQtYZ0WJbu0J#Wo5t05#S;qOn{337XdB;Tm-lPauMJnBq?%H z2NwY@0$c>R2yhYL0>}lB%NYPE0$c=y32+hMBEUs}ivSluE&^PHBt}lB%iji&BEUsJm;e_6 zE&^NxxCn3oi2uKkiAwWWaga8Qv5`ZKG zNC-)aB-BAdfP?@Efd~=cBESWZ3m}&t07wzwA|OnFivSk^E&^NxxBzky;36a`a#05t z0WJbu1V{*w03`8)jK8;TUHi5Be*0hi&OdM7@c&Hye~TNk+W9Yj>u*f_@yK7Q-Tc?T z`syFtdha8(cmMh7Pj7ts-LKT{Z(g1G*!*AkceVffiNEosWB+ORAJne=^>zRCXPqs~#+8lx z8xL+=>A%J|8+#k~3};#6t3KwCvvH*_B(AbbtyUY!Rt`0e)%ewFQclnA`o?${!g-tj@;CCX=F^RPaL6lG)Ck>FHnF&GFo^j-Wn}-IqO{J(_(RfAiV& zf1ijudC|iNIEJ{R+1LO3g!J>c6G5}^?iWjN8=YrYPgRB*o#{sBs=;qQM>3BXfg`J? zhQ=nkFR#SDK3Ur~M9PnE%JlT)6m~;^QH75!*~qFb$?+C#R}W>wUeK0h;jVi%!Oush zhHhvyUl?laXmno*vxUi3ByFSJ6(*W*g|2xn!K(qSO9`$7v}*~rC&R4?wk5-z2`(qY zivi8^tG1*!0KP#TZwPobfH$aXT?ya~>e}0r;noD(lHtw-my_YefaZDQMAF1b^Bi&F z6C*=~QM;>AyE`(KrO=^<;0Lz&@$LB0 z=v|+!@hX({poGO8if+EF4Qj(oPQmSB*zS(!O!!_5H@o9GGrS5X!&U;d8yZhv9BM2* zKfJ2ZJhgJw#zuE^b#+Cf`Qr-u8Wm6T#}mzexhks+O^!$PG`TY4HK$fqhj6-L)x_a$1BXUZ0%4s!sp&T!#$MWojaOk<3rh+-F(`iG#IWB ziwbyhOP-(pm@-L$qF!#kCZm&)a8GVQK58Po87{PHp4G5<7T04FPi`#|W2cV(#B416 zS1YT=VAZOrd3l7eJarp&c6;|~gGE+I`MgO%C0{mQwc0TI;Uici!0Q^be(v|C>Waei za$J*<^naRu5{WmAjt{Nalu`5eZ)C)j2=hG5pr!phzH|c( z9AV>KFW)fH{C{D7l^-&ie@5tdH_vWdBcJcwYUdugZ7_9bGdHo3C8qEf5WvK6&@eWLm2c&{1Zw3sGdg{oq>N>gVX z|Cgcq>dajCUw991tF6nbQx%@;hT^Slixrg3L9N;vtCIdCdSR$>;+lzf$;SmB7m2qz z0>eszC$1&_GgS-U`AThKYDJ^9vT)L9t(H|Cqj1++X{`LjfSDmulwGZ?nk?h*YLw!u zM6dNe&-z#u&E?Wt)Po@*BsPEi#i8skUH^LM!S6%%uATj~rzf(J>IzEoRbugKl@w9E zy1H^w29=7bSs&NBI-HD%L1HtH!RFO<)m4)e^VUa=3xD{^!+7GFDX*0&pF7Dj9sxLU zE$VcAretnyoT{#Ev_6rXMz*Tci>hEW(Ym=(nXJ@vG0>sYQmbJ@J;ZYy2PANP;|iKm z#MM)~5Yc415ycfEe4_PP^Qdt@(fWKSsa9(A;A&)=O3jj{8^gD{be&8;Uz#S5X5pcz z46f2{kS%%d<&o57w5zsqC<7vcU8Rwjw1WESFt5vohk1NnmwW2N-ex`Km#O#i5yMCJ zJbq|yPkpt9N4Pnk%xi@?6%&7$j1-DBp}27<8$~h1;AO;{ow<6Fi&C>1l~be~_G8zvAH%+W z9s3dN*S?Rvl34fM- zu+!ZKW>Rn6viasfvCgIVv4UeqKB!o~HEb3dHscJNX@<=z!)B9Vv&gX7W7w=QZ2AwI z$it@Ou!;4BzS^YDXZwyGKIUzqAEr8a`}RJ+H}w4Gx4C{85kLQneIh2l5+Cm56A;vA z_Z@wlFU5U8h4zk}v-=MmIW+h1{_pH3vuA54Kz=bs^$ldl{PPa;k58rBag&QeT_4I; zrw_Yvmu{Ag4rNuo@aX3Rvuunax9h-OZXN#K9C1Ywy{moxvBOlB{c1)wLAg`l)K?Bq z{SKI3{N;lOXQ%dPoL-P+8-}uVTr)m5yPs=WbzE4iqu}+N`^}(?2BP204|Zf(g902c z;_Gzzn+*S*N2m7eLm;6}WO9SJ={GT_FEY{mr5WFVw1y4${d^XM3z-e-M~1RSG4(NZ z=-`p~0Q`|Gn;ObK_FnTTE~A|~vVRsv2K_4s=1M7^o>|~HeHer`{Fhia9 zUU^}41yR;$?Z~5M-o8cqB4Y|7qOr6uMxCu41GV?v{onq*UL971aGnTWIursAr4%^C zU}dagD>l|Ru`{|gui86nEX|W|%Q&n0L5riiW~p0Bv(dN*JO2*NWS_-&ndNfJymUlj zto5T}d^mG9w;M>Akhtr>{r3$fSZ;jRp1Y}s%J&aX&wcwSbyaDwDqVd|EmIn;Nk+R4 z;MbOQ>Cj*9%h~Jl2sCUto$D5j=zpDDNqy8h8GXF&&+ETY9VLkwiSB5$b~d^{pIFG8 zQ(*YDgKk{o^lY;2{=9LI^D8=B%_J+?p5Izbn36dC5XeYi$H#xwghbeH8mclYar&Xg zj(p7f6NWAQb8bJk{7jGA>*<$Db23Z|Y?qC;C+6sU8Ipc`XSOT5FWbYk=GRc&%>?31 z_AN{v%|^p)&s@N3MPe3fyKNDR(b6b9vSwHI^G&xpHIq|SO0J* zf99{O<#R5hF-8h|iwZ29ba;S~;i$bGzl-oa+G5qa_olv!9hLiz9z1l&);&5o3={nb z4_fo$jE~(u_V}3f@xxJP^ILnc#g1Bzckr|7X+4w%hObe?8m)h1{Iy=7l4rUrYNmyX z?ZsFP*hVHiuoYH{mjHxh$%DJ(3Ki~v(!ROUP{ znYex8cIFs>g>0(2Ite0sy^pL~g@m5ConS?BH5r$UjXBdg(P}bOKuKr6b-d=#@VI6q zdXwSzNq31M zl_8K%%jL7T0>iRcQHtTXac;Y9R%l(w1%^6OjmcsMX3wA@0MRD4j;|$$*6~>QvaY9g zSaj+j7gVslu&uhDStn|X8yf9Btf4UDbHVX@IHWJ%Xb&?Pn6y?+QRV%ST&Tn`Zv7ES z#Cz8XNk&RO{O0o$EvI&Ihj;j|v#!-s9uw_aW&QY2rJMxk#Xmz=v4_&GZNLhXzSzli z)}2iJvn<0@X!wCr*jZag)S_5sLuH$67P8gV;RtHup6%5Qgr6y{u`yEU_Az54&7!t0 z7#YRnn6XkxQv^})oSAK1sM;7(=u)5?ojXj4CFiZF;kVsa{djHT*hW?=CgVY?frOVT z8ygGjm#G^IqY?Ry_PeBlaUKs{*7#o_i;qeh?H99RRZRbxMHbe7sygN?i+|}Y|86Bk zdkl4B0jY*c0i`JdhWS6l{|f(C+*kcbzl=gwfy-nf=_e~f`hcpGo?bZ8vvh*FQu$<$ zXcCwUNzSPy7RIAZYIjk6u;UG-;|*J?Yy0UhquY}|ReG6@am$Rh1-%2X;M113dsZ!V6dH(KIzo2!(|)9yDCf57)!Lz5fe5) z8|$~y7}6zO!Pm%Ee0%?F74%E#)w}l3ievGW!s6S;OdnK7qLfG=&+GAew+EzGe>7X! zv&&zaUifP%zJ}-d&UDR(FBtV3(>?ix8&~9NKEjQPo}159Cs;{-o|K8oA}X}9I;znD zpnA{&b;R=(ho#Y2JwLXNdf0Y7uZXRo$IRQU=h^naRN`bXFC}dpn^?N79#tN(wcarc zi|xP9_dVy145wL|j~W|geDo&>GlE9zFkkaIFuad1N3WPa`2Cgf38v*y`uXb36$UB_ z8mo^$maEXbiRMj$MeioUGB%p6XzU=O9sQKee`hi;L!C*d z_Q%qiO=q%NT}jESROpd1k4jlMTV(Op^JxUydcNpFw9dK8)LpdCgEY%V>s+07IK_PH z9GyP;Oe5rlXh+UrJ%a2$t*;_C?4kjTCsl(k2A$;VeXVn8O~Jvp{YlxqSQbT>{wXx) zqLVjVHGAw41|5pxV(+g-L<)l;`Fw0X>gItAzWs;K?%NhOdVcj+z) zd68t!=%eq~HR^SpoJqocZ=KFLsz&RlMV>`M5yf41H*~heiY|$ER0?dgNbs)3yt6GY z1lvARERP>eDO|wnV&(hj8iuMxD`_^MqH&9diPUvcXsOFDQY)S(-?l5q9t}Hm?+i?* zu$>^LSa;2Sv@dnW2EJc*Q=d&e%~uzn9~+oRi7yj6_qS`7+1N6vSMvFTg%0n3 z5Rv+mcVp@6ldQxayx z9X_^8yu~~uxuX?Zc3Rt$X1jGzy{H$Cwp527Xa@4H_hOMkrev!O~1-QQxv0~}s(PBwBj)je~zG#wDB z*Ke#2r{GMq-r*Odz+!~+PPIBrE9hoD!t(+R3{Tq87c2nQgD=oG;aTX`WQ+xmrQa%A zBUGcbE@OF=Rn}C8OZ7-~)mY7aAC(Hso|w(o>`v^waWFLszs{8yXKVVN`I7S*`h06r z{JvlmylKIw&qpu!9*4p2V3@XMwcc5i29fX3<9WASb8fwpGM3xiNV0Zq-PADKHGCbu z(d&AAs_cvs<&CE%Q@`v5++W3j-gVvX?^BaWHC?K=kGOtK%%ZrT&CpUbAe13bTjpu-57%hunMJqDTmX!pWoKNbh%xIgxkj(-&? zZamfIC>u*hcH?k=_}n1>{n;b@bMtA>;zlj@Vl~fX?05KgB;ufcH?kk)pPN>R5(9G- zck|h%y8?<0u2ZqGHPO@MjbFpdYy^BR`wTJ<;OFtIu^Rz~uxGSsH-Zl5FPOq*u`_lm zCWMD)>332l$ZM7aH$ag6m{+u(K;?Rg#UUA}3>U+ti(%`tHW@p7pt>a{Mq`YYN=ftG z9GwWDVk`4kUX?K8Qf1$j1?0rE#plG%O1;Wzw126My43NM5H7usfOb zY%Yv)VV`|du``S8fc^+bx$>|CWuoIEb6$Pn%HE+7xS3hcdzCIii;N#vUzx5zU*BSC-!|d28Xq=da6U2rsJ2_ zYU6oJn3Ah#CyR=I{6@x&eaz`oMY3APkvAGW{xq_#*}yz;h2Z>J^(MAm)vQ8DREEbS z9DjuY)yh3}S_Lw}&N1db*@b7LGgoJJSr2TtsMATKE7h*mqndoz)inf%W|${AFCRmv z_%=6>7vt<+dX)oa=lN=MPgDGo&uONgxDpfCucXf0+Noi-*d?`l?i^jWUct!8csk{g z1tx7ee=)>{JSH4BPTI+TVjB!-xiTc4cncmUE`Q2pm!cZ~)fKK$NIhv*>D;ltI$Zea zO?9N^u(qbkOp>)Pl`5$mqm(qiYwd0qptH3csqFLxKJ*8UORNg+h!Kja>AbkK?9E~PtmPY3Q z?a^ev)e%}N+XR~(8>*fCmcNdrEuIaLevf3oPV>0j%T(@4`9&LfBwODvNZH@V<9NT} zeXLLp80?{${N!>MQ?%mo`it1@||BD_a zn9~0aE$0;KqGi1G!#J8E)Q?B2{Hb5{Gbl zJdpQ(7ysWctp$+pa3^mYEvWS_&rHT7>H;>-N|8m3=Hy$77mZXI;E z6APX0B7^P5^R95@J9lNjhkB2Ock-fpaO4`j z-PeaXu9gjN;!!TMu{l2$%6`J;cF$n6-k50pNl}%*k*e}HN_|?#d!wGqvu0YXQH}A8 z4x7jre2liRwaB@h8Eum9Iy)s}TBmc|R<1om|1O-jyR1j#9%*uIdy^E~Uk7L`1k`y) zNTyy6#BOYAxKQGZt(iKaGub?cqrl{-P~zynesHA=u|b&CG;>9 zEgSk2UxkwRO8aYVBHb6z{zhuQ--rzr%^$NXgGt9pIzV`7z2S^n`yn!4Iq5dzCi%21 zGE*~j+J=nw48Wk)FcY!l(1=vlNzGY*8regY%TVr1NRQnqhRoivPMc>-a#^XWrDH;f zSiZ)(X~;F`_Dn1TIr%t~5+B8CO8`+HZ&@&?z+%DEikBmkwDiQC6WnJ*MVMd4_O+Qz~CFDoL7sYoaFl8N;UM z@)T9&YD_9x<&iI|C9IM+1FV;x@0Uv7Ni^?`gqY`<)Zy7+(!2AFN_#$yc3%oDZj$!7 zT>D%R(S$4|kBUfx`C&yyoVSnvh6hgaeMt%>htF%xDDfp`>>fn@9BLNT9^BlQZ48}- zY(C}qB%wVY%Mi(;oIY}pl@=?UqzqcbA{n^DQV}^0P3X#v7@tD zrlbKYG$E*8^IW8AM1mvImt+w`(?rL^YOyZtsAR!{*80zyY&a5Kj-xb{y~DV>=w&by zV^^=}sP(E&#gk7dGX5f@tz%Z($&kf8rj!{#W(-`8@nd{FM#Lo!F$i4R5-%T3 zFCSI->8Z%^&jz{nv!QuDX`TBd~VpyCb8Tf_8xnyyU zA>$-9=%y^SGoC>l-8QshD2^Sn&xG1v`{1`;KNGF>)Ya4=hxo+|cV|00F2)<}GfXeE zE-^=0r>mfX+*r)|kc;xgIC2qj%j$h+a~grC_+L6V$;12Lz0tvlzK zLP@h)`P3Ct)jFvc;_57!GVz5or^q#ZV6sVFeZ-N-!whzg z*mI#m@0x&Rj%i&=UE)jLS3p#*H(j5huRC2->a5MWpOsxMn7QwTz>uk8r_0hd=l^pB({W7 zp*UHxS(Da=@>o`DQseeJJ}K#1j;f}Jt6m@7u*ip2l&5knIjiW<5pLo~eRTe;sh2Fj zmZ7prez!M|MlNO^tue*M-k4X5%&1YHlksKx4a@W_cCVgvtdK;Yu>z0vG$1c$OBXae zW#zDV3PMEPskKosiSsBlkzwc|T`MqDI31_RYTak79nh23(YT+=9gug)4tkkbCQRanHYg=mo;#9w6C(| z52Zcrt4ZhS*m_g0oho`T_f;XoiE)xkn>y4$swd0+0 zduQhk4=giJ&mDRA@cuaBWdNJk29}Gb=l0Hf-exWivV?kp!G+n)3+``l3PfbB@n;(9ZU7M

    1R^r(kE@@kt|Od{ zrHfQTi#uY%IM0{#Vp(n@LNl`GQ8o^+-x5XCQ6d9$*Jk2mt8g#0`8_0S)^}oQ%I63i#-#@nZz|n)> zpF8}m{Rh4^&*q5(56^K1?u*q%qs>OD_5?3-ED1)Lm_Vs<@t^i$qVZsAv{=VQK5HS3 zd&Z_#5ifOY8Fzk^L5A+jA~zA8koE-8n1mN4Zi33P(s;b?VwRKmpC~)|ji;NE$YzyXp(QR5-Z$rw{b~75Ej;oQl zh?D~2(v#@c|58WosNaB8#a`pBG@3juJ(O&0sQNqWeQYqKozyw(_zH{+2#|dL64zrk z3%@MN9vu$WyYWdigcqM1$}GB|)TP^uvZ=LBO#Ff=eml5X8+Cc#j6N) z>;0y8F#hAAbQw%IZ7^@p;~|}zIgXh1^xiRtLUBh&ocmQRm*T^Yj8y3 z7`xC{a2HvXiBD3jKG34Yf!%@qjOnD~^uU(3aJ75xFqQSu-|VAR&5zb!udy=@>G5^8 z*@@g^y|K6_>Wv&)oR%k_Y-118up{Wv4Dz8vV;6qR_3Pot7IAY-#oXrX2>(CcH#>y3 z{6zxsNFB?xZH!HXoY`Q6o~Hymd)zp<$Jk!nQ_$SbWiGUU?nmM(isFh4I``SzQr z-<-mEt8~LlaE9TOo5*B<$K)nyc_ubBC*9(TF*i_5`ts&m8>*|YEnZllxgw9D{ZRKT6>5WKOkD>pLt*u(qw9(aT;ivz|-9)I-P z2j|_(`0+m1>%J4moR9RMa~|kd9^)YU;rab@hnMxQ4<9^ubfAklH?Skou6Jj9ie83V z9XXQs0Q14l^h&yb&A#&c5iR(0d!&TsMDozN+%6`IeWAMZ6pz^L-F^{7nx)j ziwzYU*)e9W9OM5@)zLETOGU~Abd<|WWk$cwjWT<`&GifP$FEz2_OJlxMkFiL<2J@; zjhv=c)74V-On!9?SEsFNJpC$<(_2;T;qjF7bW3iZ=$tP+Jmo6DlibUcHSUP!{CR8f z&hzHyD4?C^iTHTQGp>zIdP5UU_}OH6Hf@Or4h5G23x8MBmWXhZCswF?UY{x6P+-r; zGkrd|%q*XvyFJeSMgjHA)RPzJyHAg>9rAW)^NafQ&Afd}YyUKQp(tm3(|ssUziL2; zDZQND8k*Bv)jS;)6riSazGVBCt{_rWo>dYficAx~~ndIc>pF z>UeYKe5+c$qtV$%UjOV+y#lZ30nm?L^0oafwot~^Dvlq=7AqAhuQmKD)lx!71*%#{ytr9rNI zrm;kt#w*;XohxS=OEVULMrWRrs+Gnm*%22^;Ly%aJ0ufnNv9k4n6>|q^;lx!^9hI$ zc$*cM*fJX)m&}~;n#iSyT6(N7QK9gq7Yh>=nzS8rr>G!l(p6%`Fnnou;aWxb-&vTb z2$T83L`9gKDoj*_$=Sk0MVLHWn5YPo#ll2Im^@IJs0fp13KJD!;sLP~wS~z{VS*wG zMf0`Df4Lf4OGxVv@{CASM~7{?d9wNigp!*q8fSXS7aQ%{$tHI;+V?b0{HW31mFg{0 z&AqS;Q0_J5e&L|>tBv;kjkagRe~fjb{Y}jFHrjKI_Wo2$I-bGNqnP8W{nf(b{b+ul z|HmA+KAux!|CPn99rs@}+N~%R?c0Tk_Ch-G-#*E@E>nyZGlwK7`1LgOtaCVq4D&Q; zd_PU%YlJ-4M4uHk*Cw{_x7meFVf!HB8|@!B@y4p_4O4W4!`HnCjhFRFx_2^b5Rz}w zaP|IDKsmnzcW6ckTCssSc2PW(MDKzw08T_|=GitTke+)LZ2#kTQYDln&`+g-Om z60Hl@CKji+I=w+{*m;wLXWzMt8ip9#I6VAA2}dZ101~V7I-@(r;|O8oi7(GblWUXz zyJH6=Q#z*F4;v@8ghdnyC8MBV`--GmI)@_ajdM&X8wDw^xb{Vgr?)928L&{oYYH7N zb^qzoITTR|sYt?l`|>%Dx+Rf{7e~O}9T4;3Nab7>@SC{gZ+B~ojFR+)cgqOqFpo%&GatJ^Bol>9SEEl{Ze(J z(U~bY2P1{{BJsZ|^e6C}r$k?%PmHsfxzX8;oef#nix*Kxj8H~4ax@=EIwQ>nAoE_IUuqy+t{aLPw2b1)x;Uv75t2LZY^Gka(Mh2S z7n+LZpS#w>C2&(iE8K5XXVitSpHY>p%~s$kh=tyXP`O2^y(P^DSE~0s_VE0%*}0(x)mP(buI~6@@Zg8Av5Vp^5aPIXSy~u`B8t~>EY(_AZM9mLeJ&lh z9EJ}SA;LmppRTuGq;2#1RKn-|d%T#j|Gz z^FEiJJ8hisb>Zl9izyHL5;<&@hAi3ay#adqap>|@0ND?)Z+TdHjeZ|hv%olUQZwBSYbGmL~@ z5^dsGU65#B$g|!BNH$0*=2_duv<#CXMPuP?f|?48#CGWd8RZL3;aD2TE=y;U&(_tv zj!wpC32S8WGq`D}j}mb;Qua*#zSdP0zE6|nyiBYX$5hte?DP%I#Z;rswK(nwZ%^~f zmA5t8w^5ty$Ty3(*~6aP+-A{j@4%lC$$s^;C1hz@ZR5md=BSI4$xG7|H#)38VUsAs zYNx#8`fEn)#r>VPW7=4HGp(w@meD8%3`2P`{#5Op6a!C(>pP42&NPIz-)jeq-dC=q zOJ6AILKm*1FSkB9p{(4Zm(;;4Ttu&Ixr;C?#XV5Y*hZUI_>7WLY-8&yDnwF{j2gFN zL^$w{xbxlQ+mgq)5lYKc8sx4xIZ+z6w=Z+WmGxF-^yQ|QI(v?WIL;NO?4FyQDz9`f zL!PP;g(t>ii|PyP#x*`(aN14dE$FBa;>Kcbi%N!`3!)##iZ^8q_r2?pI_biyD3024 z15;TDQcF`wr*$GDUtoU_gL&VxwP#EL;q(xT?I~ZYWyw9_+qF7Q*2++2>t&Vsx%N`Y zzS6?9b){6cx0lM+)}C@(u4FW~vovJ`YAL~uUX7==lMhkEWWmdfUN zGtPTjjo1tLR>U$D?b5UvrtHX?Vbt{cnT1KOw^x|hpR)!b+ZDc2Ek&m3tp~OG?p<%5 z3-dIhPi{g@wzJVl<{Zl@)UB<3>TQMEs4CR-P-!)_lFud_Au$RYdL9e4X;q=q%FBI#roi+QCmJk$~{Vv~($ewHVD> zR33~-oN-P4Syw#lKPGOf66J5iawh#l_~>M-s$}&*-hHG9Zq1Dzvd2WBjBsvq^#JY8 zbiSMW$s-cZOu7kfhV9#q@mL88O71>V^je~_ec2|C+uGZOpQGd4Dv=yCTH777-e^~z z`Y&a2^0~u`6fy=BOQ&SOzPm)4T@87-P$d?7A_>k{DqE|ojlf>dO}95pwEunC%}Z%X zhp=^d6&nTW_ znjSN2m!-Z@=z7JzH0qW6(x_MPdq%yIA6HYS+OhX307|~*sgQPiYD#|3e0xt#$uG@Y z+X{7*{L-9@#!5AtLqi?)+8|q`1Pc=#Zbd zwt) z-Ro6%rh8t`r@E(8V&V0C&+GX~-F=TDi10b2sml6h>m!k(vlV>>@h+QK29l3)lojrSOy;AfKqPoq3i(O2?8 zkQBUENY)~#ucXmU=W682>F~~`qi1fc4m;G07${-|c~Jy9uLQLJm^mIMoY|J^PMdro z=cec%=-Iir7m#p{5h?2`y8XFBUX9FWciB zOxB_*#akLqM$+a*%97BzWT|9Mo`enl-Udja=Lb=PYE}5kidBv6c9A7m7gecEH(L*=DTq}}Ux`}c+%S~_O)8Sa{tB4M^kZCXZ9|Jf5)5>gwdAwkOZ=cMkVAyUe%vxzF9UpC9(oO>za&K#A=_IK% zD=!Fm@~pen+OMr>oZ7+S6ala*J@u@9*q_DP7<- zJMy~wR6n`H=fd%Lz8=r#eLmgvd1~G#=NWlEVFN}a`8*jePo7i#)McNGu|p3~(|#kK ze-h6>b)!BLvuu5NzzoU&c<}*?GEmgDC<~hciz*hYvWg8g86Z!9G-k>*c@-Iz52Yq8 z8d1^4s)MC0w_)@hQ|r?xmib{@Kh-LJn&+0=yjs}2y3B?f+>)QxrR6qP3Y#m-Y*-pj ze%iI=HgO-Uti5rY=Q8`&)7~`<;c1Y6gqFYTaER}U$FkjJ(AudUbh4nuo3UfV=f6N4leq}qC#QNn+ayj#tIe9r1hq*Swreux?$ZU ztwt0+%b03i!6@pe0`r(+dZ*|JIfYS7FUGnEfxF^Bv-2)V!IfFWjcqkb){RqRf{P*w z9T#iMVv7Am^Kap(y}fkA8cK-RUgv9O^e?U{w84@~kLuQ(Vu=OStv#yDs+3gIj+$2A z)hSN1m@*S38=}utt*T}_$BTawnSeh;+g#2zEFe!(dG`87ji{!HOp!G zI{WAhk`vUad*TUgESTo&^-EjZ@-UWIgmGKRkTGM)u;p~2CAt?&S91v^783HkP$qV9 zh9uNU!c{hTGqVXl{#bp!Vam&W&5_M>=}3e3k=pfhwcE#Ej!Vbl1pO^^$@LrE9NMC! zFBK8)@K-E^!-<(s{J4_WCju=UrP3p1JeTDPP~m5yH5Fuy%tUtW6sOu-9V%zCd{OcskHo~-9sN$U^TZxX{!?`BQBmio7nU)XY=IQ zk_|nalFgH6ryK?s-JSPSu@S$p9*qkb@~#mzqFF1~dBQ1rOq>#&yt%rTDYNZN!mx*R z3*BEX&DhHR4(>o=(Km1_cOw#~PdnIU9re7LQ_qIx)UzKU!!2$gBvc8DTljH>#0JEe z4;Hu3=uOgy)vBxLjPb_}6SgPgTZ&WKSG{5Y>`$;Sr`=)Zq$G9Bgv0?t zI(Br?>Dck4W9QKOmpxTPdauy>Dck4W5<(@9Zx!TJn7i^Jm-^+ z9Z!06JllWD)9#o3?_$PI^S8>;Kba~z=g}$94>8B{)U)(aJ8u_bouUY}yUR@Ijx95> zKC)xo*MDigy4<97={+V_mYZpFE?qPUvAR6SPtc= zV;R{;bL}N`Z}sWy*Yxa|^X}KRE)D3e^y}IV=?a;Vt9v?gb)SwFsHbc19ME0v*EJcq z_FT3uqVsA|7n=C+Dx;@slm-fmUj@ zn0clfaL*O{N$Tt;u{QVxvGUStK2yufI53V#c8R65Qx31wlg5n88r|4J+vPg_bh54+ zgOBdkQXfm4n34^O_B23*4ENQSwJneMsA#(_B z*pK^}3Hc>SGul9?EZH^MT`ylZ^yM6<^4W&&1~xz1Y=nZF3ZYZX6yTK}1(OEd4S6pp zBoz`1^kwN4(P0Z^USEdVfH`LtLCh&Q!GcuhTeR@9!R{8^QA#0#jTKjSgQCMIj~pzb z?qc*r)Wx0vJTE>Ib-%pBizimP8#pAu{7C%akbBFOYw~O^rh`-1X>CuONgkc0=x$|b zeKPKK?Z)u%B%AdaGYHJ+bw^`$3M?p%Ca$B&nwtI;M-6>I#kTKGPxHd=HNi(RuZ|5FHqBWJCh z9Eo=j;Yg*WKyd6e4!K1F)XEENPrjLFHsZ)Ms;xJlru=vEO(V$A8)Zc0diprs?dZKL z#%K`P>B!=>>(}i+DB!4*!#^gaTZ0!95{paLQ$$WKG;%qi|%QP_NxQxzWOYO`@Fe^1Do zH-uJvUAH+!@h2-cy0N;Rie0Lh;yb{Uo}xZ*m_nQv2LZcVi(IJU({Vk*KrT$Z>@=BJ zE`{P=JTIHupz!Zw zcos%~&C%3+h?hae;1)j}D`|qnE5c_$(H}M^2@upb7A$FQScIK)Z$5y)2 zeMgQ@T%!W2j881E)yX2bQ+J`EiF(fBfG3~RHb00#y^u}%8qe4~8kg#Le~vHo(%{1x zw^g{1`9{$Dh8)l^wy6NTdSg*5#{IWs z5Na7tW%t!V-EX9#kNd_G&Q&FaEd?qzQ4Ysvuw0XdcwOMujTG28$=3abx#xjA_vB*8 zJx^Rg9b)*1Q8aXU>qf2k#O|_!=8CKS44zLJbRWvk%q{fifp15|>?H=$`)8w$h_-I$ zCkNBcyPFTP0BFh|iS#^@pNFQX#H?4B@r>2fPDg^>os?q?Ou_mmiY={l>(gQZU3`ul zwEtX3bmiT(X-qC|$rlqAw>Zvh|2ZcX{v@4E{&RG!nN3b^oa9RQ@K$Nu#=m>WFF^O_ zsdn5MC2*(TF7956M7)#|@zSla?=EVBlw`axQjreFMjWGIHf*6tOGoO)EK41gH8c?< zR;`A!6jS!|(U4N7XLt+GFwdcTsN8zXs$wD0410Q}+|x18m?f!4V|sZgPlbu@#d_th zM0UKm-r}0>T=DMF{n=k5B}UB0k5fkHvQOSc`Sm*OSxV`{QSeiqKZ-up6C1q7+kEd8 zrs)#ztytciZPn~Z<42>Jwv zB>S3p25j{yy0kua;79Yk&$ib{p=iPNQ|NreE}0 zuKLwR9LG8nh3pC+^!DZ%1F_j$)P2^3TG*TdwAtY;Mpl-ak+?AHw&@)c-Iw`sZZ{6n zc3-A;8N+}@z~RCYI|uq<3S%oPJN8Zzv5~sb{Sn0@b)FU&=;!5lVYUdwmRcOg=DLH} zg534q3Of1=TdG^qtN0Ge7!^j&gyB&1qzKWEEWs~FZhJWb5%eSyUnoRJnlMJ#5g#p~ zEbm}-?l!~rkUoFv$f2{EBj}SchQPtXEVj+FCIU5<_i5Gatud{|IS?Nc-OGN7N%Z@I z4DcT!KuL(RUDj23B7Lg5-cBIZ4l{gi@aD#k+l`9ICp%o9WwqFkL9jtKPQ=E+7>_wd z*D-xAjt=6Eife;Aj5sE97H0QUJ+wTMN8}EMhzRgC3&SAso{XV+H7M{0(kttht1{rC%>n0y zo%@d*J$U$W?;!FkW?6AXL=@yYs%Dzmr_UrY z$)7v4NEmW{^I7LV3pN;Z-=#uNt%wC>G4_1r`ouh1m<;Av${YyI*^rdm(iCFs)1&y| zpKBO$mDgoXEV=_AEKa`Z1WhOx*@Db0c&-ky5pB3G-JE>0=LWCR_oaO9+1I7;Xuu2v z9J?2SWA}o^2VG<)VwWG+U5qfjP3jrjd=qG89S8g>b*DS-d$5Jhhag_9Vz zC7fa$8b{%OM2#S;#^!@SRf=>z@PBLH#R;oExVDVzv{}l^Q{MATJ>AO8&W^Fy85^~8 zo;RwMsQPj*G{|f7@7)$s^s%(Vh>p=DDY<8w~^3l2Pdo+*+~w@Z9(IA3S#C?)ig9<_4~Jv5q^^=(`$Y)z=TsAA5B0;t##w zH+S^t{G9h;#5W=aUdQJs2QJRYKTkQ}=FTJE9dLu*uSX7q!+N?ll#NoqWZA&&fZFIB z+p(}6pqx%pVOM{z-Q97)33hA4ZXlN>yZqW3F7L+Gc(@w4+s4@4b>QgS;kjUEQ<`N1 z(Yxp1p@@{3{g3W905-JQV1~Zy;L)Q8A02RIOPq0+Veclw#+~1rI}DEq&ulnGn)k;q zlNKMSyX)BF#dkb#bwjw?*e7iwT-`S}Ki}u<#&9-xP2$(_{*f>r$Qsjg^GEk$wIQqq z66))7k7)DJ_j>yB-bWwR>ZbQv?b$aM@;(+;1DWG%`wrq~^1Uy**DsB7ady}Y z^}39mX^y+824opa1KIV?M;;+BA31pVOY?gl8L+W7nBIRl-I6(w8OUsNhX?X1#lpNb zcepGZ1LZsN@UGddeSsLZ6kjg@QGMPP&DGLv-q+_hUQ>vo8oX`bdo@u$yrgKLE{G~_ zcEISa!w0{+>yX8*g*Kqlh+L&jq1{l0dDuj zRZ9cu<=1w|tG8Ug!`a6)%YJ<*`{V~WC+f3o%TV@f*LN2|&$3SsWuLmf!y-dw*)2oa z<_~Zih3&)8GzK#-3+>&3bT3(zI`IS3gp{!R`=P@(jN=a#X`f493;u6OkKY{1>{oy2 zDCDD@cDp}hG?F;>$v=R6ipn+Kdg*KmxpkoJqlOtM?E{w7M+24v9}WG$l%Q|Bu*JvL z5u)zCgY)}mvuyQHHaxxe$UeS|K|)O*BsV%n)o5x~9Sy=rjM*MJOdFDAe879v?t}Xe z96b^P)`e}1Q&D}+2eR!!FYNHaq|C&^9X_UgFzT3Cn919Um2oj$b$aeQd+AFm^6p~{ z3lX1JR>?tbs!t}wCKmR@?D(I#vvJ}C(>fCiFGh3V`1}EDsf8D#mRfl6f2?jQhZrZk zhKVVo)*A`iKX|X+xq#Li{uZ8{WL&}kMWgl){0$f-SIbjN zWux_6EW0i|8&_Jkf6)8DO#266)BZv3Hx!9O^O@=`j+hvyr$DH9zfcT@vhf6m5sLSx zptYg~T!22%b08~xmVfA0;f7e*B8WUcrlPNGz9mRd+VkVzJJNAeh~ zP?Xz~7J6HeaW@uy_9AUvOexgA?Qn&n!qVn7lHOFLp-*%8tXDSQ2*%EmF(Y?k*0akF z3>ZqaUPC>`3=08Vc$QqlbwBjm7hYuWyYP)-R=M>?c@Uf6$5^@b9FECKEJpkwp!s9w zWoDTy8fwgrGk>r!3kxP)6g=zZROUXN%v{*x3P#f4Q5Ac*1iyjCcC>|AZ}~1<@4&-2 z7lEU929}b8fpb2EgWD?W*rQkI?!ZB<=OTaOM4yWt)F=yk80p}L7-PFa%)n7LP$WWQ zENdu{WsUOXNs=#j&TU~9E=8b%()d(WFHvDhbDB+UlKRF+7cyK8STM>lEKZ=hEmDtCDFs!$r@z z?2cm5)5So)+cm|)rHiYNuyWvnh-)U-6Yv{;>(rG+S6Z&`^w#@uc#watN_?>7?Yoie zsko!a?XBW|z&nbsq^8fcxdb%D7q-&3&y1LP^sed5qJAuE{1$=pD_sWoK*ZOrhBkfb zZU2X08LKOg#9EnXiEa!}^ECSWM(@K}MB%T>aZ`NCEG?<|nYKd+AQH@dds&M!u9PrElHAEx z(D)jh%j+(xx&ZI@CSql!M`hJP~8_RWEP$%UzZ)Q{`tMjE%HB z+l#_m?#r))4iA?tO{ik0$#0u}$B1V?rl_PlN$9=g>mJ|h z1$~ow#JvO7P)EYk!w9)AXTS6Ptse^t|1D*9^sA-x?`AjN7i-q3J?KTx=az@pOl>95 zJKCHYl%AJOzZ0=}l#r*Yttg7As{KOR{bH0`8dH;FWq$qq^eMN}q=+{Bzp5VE{;T+< z2zSFvdT)Ow8Q&F=ZJTwFy&cE0#+}5qwaz2(OZDw<^l$I5I!eJ#`7Y%eJ=nNx?hlLE ztCyvIFS3@E7dva-TWcN3_&#}m!|W08lr1f&NBqKf3i)it$#HAO!B>wD#`TeGRNAxr zGt_k6o_sUDn{HBkD~l}wzF-~=mZzKL9MPFl72j>K-`G9tmSQ%RO)MNuJ;{ZArK@?y zCu_gLY~Nun9iDoGxz#xqjqJKQwR9$Sx-57}QRq)4{V97_Prga-if#EZx4*DO4Wq4Z zRws*oV)8@Znoj~OoMC%^3`?R5nflt%36EZ^E>8U_7tuFQT~DUNxxjJ@_gXx`HWo%X zcI`t(^xmSoRoN;-d|F5C6`-_U7)Bmm4qMvq#-5IaZg>%cD z@esbabfoZgD*A7y#s-~nIeMkRU07@aVWqYf`-6=`ZeynNIkkc(>z!^h2nEY=kVH!i zVa}^qrgX(3?m=h7kzW4H4SddP;V4nGFJD&2~7)sHN9g`ht(&unZg93nV{wc3by7JEM5pQo1V`?6ck=@4EAf)S~N zXyoPl5&JwrI}s>Cl+J6Ckv%`mPTC$l1TYd@PC&6Pr3}*aYv%*Ji`Y0+_egRoCkC=G zpO0ISVr9$kJez2Zg?UqJVcxM0sW(10?)SJ;X&d3*y~u!#dpIynPRQq8&ybJYD^bkF zo*}oOjm;(tn~_IkZoa2l8}LvND&imwQWJi92+kt+@rm)*Zs%hy5ux&U&J%Eo&#Mv9`Nu@C8}Th$t!PcXy6 z$CvmJ>BhKgpRE^+qu44@<1+#k7ynk^i%SI^3c~Cmx8N+is3QcRVuKbU(f~23-PO17 zSh#TF-6HVfy7&fcD6z{&j7)#T&=C{gftaW|9xGTmTjP0Yp1vILFg@iIl{UtAvX5GZ z%EmAiHA*ACC`%>W+jYqm3J{746w^xMsU2jl5VLjE-A997!(HK?%~ypIuVdVYWY~T& zG*&hQ&D_Kr47Es5Zggrg!X7JYFKR13%QigbHwvP5!+>2`L#@WFjF?E6=EmCaJ3`R8WhuXBr!&sdLl{BjK`ezP&!xg}z>b4zGGlQf?p z`gMRUep2SS+33NU&(HXQndTeOgf-t_HznKun{T+wtog`!9G#mT zD&qFkOsJR(IyIBm5~pT@#;KWd{^-;Uu@_DX)M*%$^GB!1sKsHsQ<2*^|D2ZrXFg8N z_y%HE#Wy0pc#(xXrx4l6$o)j*OGub6XUcUYwl}30gu}>MGv%o)vfS~kvTB9xO%%KO zhz-+=QQ287VwkV#zlbqgAvu?`Fry2#%v=&6c7iqk1D`*}Xa*JQ`dD}u!NR#>b_#oV zamU<$(DQ{dggV#BD81<%OR{j4b>#eSe4d z;(LU99~9m9f^_jjdLQ2;PL@bs&z?;*M9t!s0xJZd(fNz@O5927W~8tta1phg+s*pi zOmb(ux^vIR_nQCQi2b88$XSEm>@+axM}{t}pdm|Nrd03v}Jpedqg^ELpNF9ZP>3u#IF6gfvcK92pF8T1;&G zz@FHkU^{`t87xc3wu&qXJwQ&O(c>JwA5LpJ&^oQ@kjA9W3|VnIP76~yLt8py?zp|A z7iOF;G9^>fIt}eLGo+U^_w)Vjz0X6qMuzEHy=z_hc>nkN{rK(Q{ym)aDs4EK=bSgT z=bg>+bH0AR5&f$VHL-JppSGCVOAx{9L7-#E#La83HHvPN$PYSvVRV&EM012_3Krdx zkt}K_lBnOuKUNlJ$FSA~xzjmsG}Jr#A8+DtW`3e;rv_=9)x+OQBZISsg(b5^I9 zfu#UBUZzv|0Vv5L?HF1&nR5#mW;{l2Rg{ri?Eqm9S?yZ#@)BTor?^IQKL`8w-wUK8 zoi?ubIpI8ToT(=ubYV@V9?~L;Vh`$n0+%h3dS*X%C$I*K_MDgLZA^`of^Y+fU<#Xk z+Ta5y=rgA+*6FazXHJJ^oOZV)#k6^(&IJLDR0HjYmi{HBcPM|N#F>{2lsJvuFxaOF zbv^{S#-FaH+udBvC^!d4hH@vp`@CEJWPNQv_5DUBxGEmv7kCw-|cM2 z=Fa0M*m_>`%hK%gET+=90$p+vi|y5|_Mznho-I3fZo7o$r-Xo0Bes`})aztIwn)9I zGg7Zv>4+R-QIq6o8tDSJ2@lotAk?irq1#@C1dh{dtGUG_Sn#Mi=!XVH($TL)hp2I? zCq&WPt&Ui&u{5c;Bw2&dITxAuF1n*E7Y78ftZUd0x>H6@^rg1wGq6_(Lf^W&M))paNYoYR07kYCi3i7mI79E1KBW-XWF}4&8`~ z6y2{=;?QscW%dyz9!iHj0jqgDthIu-U+(gj$HSK4gv(Gj`Ju7Hz&d2mqPPxiIgmh$OQ>LcvjPJ&;$N_0e6~k^$^&*bDco%}e<_6( z<|^^nGP{_%DP$yS!zbfm6OChjzIMY1YWJb}339VPy9nbT%qVwlmYn#oklbqSzc<`} zwYCjx$hB_xBw#R?LH3a$Cz(w>l6LhCqM*!+%9(5$Br?%;Jl3bVW{am+nGK<6B+KAy z-RyjUC>7JP8+gEdw!6>0(CX{C9+g98Za4GZYx4;7uGrMp)Oo0b+vVM%^71>6*SIlqU^I-3k~l&pbJFT*m;ql;{$b zJtVqurIh`IJK7W(W)?xwcA#>U!C3ADg$ECv=m9JmI<J=4p@;!Z)HmOhft>x4;UJ7P@#C*Hh(q~l3MlU~3Wue2m z%hez(ol~xxPZ>w5P1B)Mv`etLcO|5{*XAD3@6cNss*_6{0C2H>fdZ+FO(rt#5*TVAooI{gCgI0C8%)3=(!ayI z+E9@<=ki%(x3K)2sU}m{HtTrknJe;oE|YHDGsV>awzIf=&f?UPrOyo60ZdYm?CbVKWuPnw}vF53UXLR%8%Iq`N zOgdei*C5M2zlFK{gSzaYGi2*WpA0LY`LxoOMKsTq>!yavOYv$dGhaAuZ|>wM?^u$)W0=10dkCO?g&-b80E`7GYO{{f(6kW=O zE-2$Sv>v~w(@U_X2 z#Td&@D}u!CR4YJhX`q4rLC8S>xRm*qHpF0(8M*P(H5r#n7b6#ew<+vSsO zYj0|7g{uB?>$;=6lPM5IP!zul7h>LQ+1#RO29^5CS=aN9&i0;;D7v*M`uMEV+>Kt_ z_V?Pb^-bL^?M%(fYgl_5i|>}AX!i3_bMbTSZS=>gqIcuS%yNF*{vmx`xJ<&JFFYnpu}MV(%r*j&_(_qUaMv(dt>ZYg>%39x^KF1Mbf+O?rH00J+4T8Ce_)+@?U$)%6k1< zk=y{SeNSg>FoO?0K=3y|FX)=ooXD>;=g`h zKr!_em-g)I9ROp1L9uXOE^rKJC+_ac29Vb|e((1VB-2o_gU`O9VaKDP@#QttfMv#Y zA<1Sxmjcvgcg;m4eHSq%Gl6E{BVqpE6W~l*Js8YBZ65HI|ZU8mQg%j>!Vm>BpcNe=G^F5X-e>7nZVf>E?ZPhGQb%lgi}E#2Iv+))M$wFOpc za(Ms17#9V-u)SA+jN#PV-0X%kA1Gr-T~5N;@2E2YGo9vo5qzJg`9B%7ZEsF7DrX!v z5=Ly(Z897Qzc}zrc)}TY#zh=1_Yq4JakxAlE)hqgByBm2HQ!Q~Rb?s9MPu&mXzWSh zS21}38hg^Rsu}6_SydAems0T|3MOdQF;8`d#XVZ`k~eldM_4#^+zL2urJ19qzsEA^ z?+K5Pmc$pc=-Tk2ICk6+$7);td*s;h{3pJ#eYYUNRg=qg>6<092 zxZ>dzlH=!|kxqA^GZstMB z)qORcP!$iahJB)n8W-1sAFomy0OFccO)Uo-TN_#%n^J5~HX)uWs&%w8SmxP70mN;! zl9H4V38#Wx#%sji-^z7sr(xM0g$%(A#oz<#@dRh7mn7oh zb#4X>KH$AKY7GxQ5D#yBs3_|6h=z@wjSq2?Ubz_nZAbT^JQPF-+0cIQV0+tpK$A2z zeSGh#oA&K%YQC|#x%p-YAih`>^~@;uj?Tu811(KmyV_d1vlxbdf}(lS8I~woOq-YP z6Ay1ur)Xm{0xS74&BCtNKt~ePVDw{}S_%xk3=23e$mxCeiXsJ<4gdHW?Wf~w{FIjW z6)}^x!g8qaDRz^6MLfKl>=9!Ib-l$|80mrLU}JB~!5#!Xn4_i5ITBpPT6oJhK~_;D zXEQjgHsP->^WnjrT$lNXBRNurJ-sYU`L=ew8gM9Ulbrpb9BK&4;_{Fk*;;Z<5wc_U z_c2#fw}KD2@g3UYN2^uiJA`CZHeit$VRtb`Px=@gnoFZlqI4+g=t;|G^rYev$7ahg zq&~Jkblbe^jfX|c6A$+~-(F3d;a;0Ilr!9`v~A)?5~Ok z*BOmBPsXyl*KO~x_SDr?mCwO&Z4x~z4wtQhf*po9JQb#6IMAIz(Jr52jAF||E@Wuy zBYy=zmWs*GX+w7P2&5{*r?Wf!aG-|=DCv+o1K*Iy*})OkGb>aF?U1~M=#R4C2{6D zT5;y?~z5?`sKp;wMdYu)fM+Q&~!kz&azml7EIWA1#|e~RYYdYJE19;fvA z@$hkkBhN<`WAXg?^XDv&=1U$AxZ;kk4ZJe}M?i^SglZDNG7hD&>+BO^ZE#+0jyb!Z zW6rVEMgZalguIf}Q#p*k#IT4s!ubX=iSd`}Y?_I*%q7p8YoyNU(;oK}0Z+T$d)kfC zfLE>o?HZd2s(Xe{YEKwktx4eOe99#_YHLZT8*zZ9^Y*-$9MaY-|eG3mtCgK zEYoIX$^U|7DtRpzTc(O^@pH?x*(S|yPS(-a3MGKe6l%7$ZD0G=<|TPMRh7hF?&@y3 zYu~=ERQFYRA@eu3bak{g9+IfYYx4XT?&xl8Yi{gp&I`BZAt%%~?|HfmS3Ha=GZYU? zg-S(KB84Y4YZE957Bd?}&!ew;s=()osc|wtxA`bD^HC-tT3Fu%x)FFmaiGx{em-Oh zOdeuqGsSOXnP5uI{^$8>KFBiI!^pnr=7pn-p&qK1?Mef05}3^WzURf=g8`P6(k zP~@B4==XeGq5Ki!fum^@A&?Y0#KY&@=T*sUjfWxjlDZ#`m}+Laii?NOBgWN@30k-x zv*jSXha?8eAs@>A4Gu6J3hP;b&+LVUhz~9OaU(Fm>8QWUT z>aPW6H(DVY0QVh*aSjikg^Sv;whfSNot{=8$JurdNzDX z1OKq9rOu|i)Ygg}ycYgJ?$c!=RBNvhCJys|%pQ#6!J3e3le`oHPpZcYS@{ zbI4eXYv6ktanxZ}0>t_p7&c2$K$e8N~`T7U^-}nt2KMv$ZqBq zrrIk9%VDIPofy;Tu19urccyrnIi1>B4U&BMY zt3w(>w1Rgpg+XHQR^q~zfCfQ*tO$lp=L7yjyO%&XdGR=`GK*~sW<<2fBh@ywMi#-0 z5g&rym${X&9vWF>MCj~M38M_eLM60i4SaV|**w^E1`nYLBp!SOuf9yYNz{q?kwopN z)LMdr{bpv&9HqT0>$s08V4aMOTqCpDRyY!wcgt0Ey3=T=tWiCO zTx8DEz13o?FD#t|8h%J?N2|4PPVG9H)l%QAjN#!(qxk@2fCeoe+P8BfUg zbs7I!#*;F>D&sd~d`-r28BfXhx{TkHaRQ@Mr%$h$9mec9(pxj4dz*Y^+8pdFA!=XA z91K*)h!?VEq|Y5$eI;yEwevZ9@v5tpumgZ9vsn5**zW4UZq|&9$T%Y7s4%fRI8jBz zDST*FWPz=fV_I60wCcp4Zs_Ukgyt~0lZmPjNgj7LiF|jPkLmN!{m=v*`_OG4yU~#` zZ`rotW2_&QdY9}hBX;PqGgNs6e1ve_!sR)#BCfZFt+0BpU`ynw;3(=?IC4=pVfJd> z1&m#|64n(E;`~XC2Lk@Zx2KpE3e>}CUo-d=XGBFoz+e@H#Dm{}NFPOgkJk)-6Lo!* zWsbgQIjZgK@Y(b|`|gB{ft+PiZ?{9)b$#@&N11@`5F#T72VikLrNCB_`D1tak&_i3 z93i-bx>i)uz_meg|5DGZ8{FZGkbj=_ZoM>NQ>d5lW}Q%y&#WI@x~`RITfkDBh``|l zEdrTHR~)g!AhCteL|KIxVC@el-CDD@27v}P8fy)*dS-!dN=O5~39z;A`v@uhJ{-y# z&%W>1)z*F>S`pRr!tjBM{L>XLq075?rf!&*4|)f!zM}(i{szxP%k{W_-h3$dv#J=P2#}Ze8uNqGgewnf~u4 zikB5J8|$QaSvrcA6}em#V_|ji9Awz`eS?m9k$B5^T-LdM+GDgR!2$IB>tRBM4c72& z*K!v(9@zPj<8+WD0VpQkT|W*v>bF3 zeN;2jz|TcC%|K|N%DSa5$mAvfm97!wkv``^C$p%4wDIbv?fy);W-yHePsSr)xJ>FY(O^H=xq4k1W) zQWUYVWb2B9*80d4;8M+-Tjy;h zbCLYun#tD~(h3M-NMd%w<~x3wok4@&w&P^*+uDK$pLGuV z!~>5A>Ks_>xdsP+UxD!;5^?Z-M8T|CCR+a+O#7<1TU)3q#5`admWe_aefk~`{%keD zN|Xv~=V*zmrG2ZZC5Q`a17E8?0dx+Vl(f*jLPFEg~q`CeTMF?yb>kcAg^& z@Y?_mUETVQ#C?yE8gT{j=pu_#?DCYa%k!~{1+cyM9pUwc?C`Fbk8BvFLl>@8*HJC* zGA?^D_IW3kLESozxYtbVJa7tNSDEY-qm^N&AgQjUE;ik%dU16tN8L5Tk1W9v-jW>~ zn^v3Qle9xSoCKu|y_G=1o9iI(Fy*jzAq7e-a|;S$)gdg#&1eO>XxUU)bRQL;9gK3p zJACUB)#%p?u0%BvO_GcDAEj}didBt>Zb=gm4;@pXp4rdx&z7Tf1H?tFDHXr%%pE6=2gI|`j%2%8I@#f5;8_$$r8K2k+EdrWqVW-*uwlcW6BrF zuJ2538QJxnNz~He+05d;=ax#OMmBM_Q97Qyv={3c;(S{@gtkIVTR_g+jj~?n+UBp+f8(ULI9hmPo!7&@Y9Z3F*-8L|;_D6?Gm&^KTDa?a#gYgsxPbNlq2;lsIoCLny&U8R5o#m1gEnhcN@30UWn zT#`T*ifYDU&YVM~N4~3;E4Q@D|EQcbfT)zdAJvr!Nl^!s094ceT?AtQIsht^13ySC z2KWMe$xgsQq8ezya4pWYESk@oN8Oxqpp#UhB#P?h%&91vqbYzAX7&d|{PaBrnyMO1 zsR{Z443xsGwbDRmU*0+d@JgQ|yRlGvCNZsw?FlF-T{D9G^JQf<1g1}v`z=>+ZL!{= z`NMkwnDg`rml)k#eCT6sAoSz!HL$6gx0Ie1U2# zF$4|EXe>!j^6gVIfiJ4wohQ!eh+5y)*m|g|rEBJtUhHA%Y|djzm-G9{c!Zna_4dAo zJV3}_RBLSzV@dbZ?#Zq-mh^~J%H-^^IpQMBXQwewOwAo@x!+}6s*v$QOT|>U*_T9$ zYQYov9?~^#*=d=MC70T!ZWRc5hpz}#X4bZfRUP_$EU6{9E}&K|)0~57i-f!L4A`#e zc!GR&c41+y6Xxetu-ZKHJPe6%gB%uZpV0n&CWylPeOL4u@^%!#`yP2aW@J;YyFyz0 z5F+KSDFTT{x6B1LNK{KMn_k5DNSclF6r?JRew7gyj+((&xJ6V~;ing7T^;*-1X!RF zP8K;8*QuJLh5URl>htW(w|Cvg?Mw4Q7QXGDLLW7FHs>u<&uDnif@Dxm6un76n!AS6_qAA zva)9I`!$2FYomrZ$oPv{Xd>q$z|7ZOrUWO-H0ZFt$ZyY+G*sNZ1CZ zs~FJb;16i)!g>Scf53V+pM{)d0%Ypu(4yiwJnJUrxc6gn(uBH4K)NNZlpbOyKL7rx zB3M@CtT*PYH|4CWbJm*;aviyqjSBR^t%f$ZRqWMs5+F8PqdBeZ`@usNmj}{4s4U92 zq*^;f`c(r_IS9l3`$2;wTT>`lQm2f%_w&(*tq(QO;5w_Iz2iFPU3w|cI!wys=m8&8 zoz!~gG#O3H$7yaK6$WS2Xh$UNhHcc-nV5qRo4!b58*Q!f5-PCr({`Y|%)(+Yi#8W$ zu@vr&Nk`^r>y_d0Yc!23Ta%DYT3fAe}xU-`pIfN_@-(=$Eh>yB|`85C*LR-b{Z<Az`3*^(H~bWO}lVR{K@3JDZ6_+*K2IB~kpi5N^T(K`%D($}&$ zA~hu&I5Cl9~O37g((7ZSXX`b7_B%sF58bb`3MgCz=))B#5Y`SY|h`lPlIc5Q9k z2^H0U?^5mG692AsC;{}6m@mEdol*4%P!^CA^!@>1mquT(;n0m}^o4{VL#xc_3ph2e zT|lUm{6Yk4+4CGc@#yo3L?yCD?eR%Fxt*d)O24~2bmN%s5N(RUi7B8r5eL#CL9$F7 zvaq@S0x`4(o=5f`)CBeh$kP>61Or8tI-Hzj{gkyp0({fkZAiAeG1$&67u*30LnIw& z#ogt_%BAnM2m?h#pux{8J8nV)Y`?@|GH{yVV5cX?uwe~6;}`g-2#dJZBNhc50M&ch zP5S}n$h)!_*-P^_v@?J7uLaa)*N?qhtC!dy2A;XRj7GUh)rDPIcOR|2@Ir1=3|-+q zYnFmS-9_I~Mi63=^cYWTObVzZOqv`a5$t%2jr2=qzFZ#l{nr0_^Gm;`SK+ZOrIGM2 z>iV%Q*0W;}HyUCF6fPD}xOzk;4(f6-^+S)ZfjQW@W-t(ng|9lD*d$Lf_ zz?&+o?)~Q8&tl{Ty#G~gDnjw4?ZDgHo*yoTI=Xau^J=%%9Z-1%f((H0+ zR#%O_L2^u&yqWoogNEvoyVmR)58$6GcTA@vy3MmNUuY&>{n zP{5-?&+RHxQtQ8@l@x`O(5fI68Z+u(tBOixW#Mu8p^B=AbrjbsKRngtSEDklUr0nc zFyG%zPSjR&ODT=Z=GnPQ^{kE>RS0m#Drr!_OLC{6q9J4QlDydIMup?0!T)NlvY7JwVKBk*OkRwZ7jq!LYc1NXT?=; zdy@j|jfbn2dqnd!)Ks+5Rjk$i2(QAL-6tG-&uT4vyah|>Fjtf; zm8u-M?c;hPXs^hARfc)w_L1v7SZCf2JWwW!45PHla<|*J)dZXKa>Q@u2i(%H$FGdd zhtaoKGr^XDuZB|_KL)+5QZGx}z!>$VjX%dI*9a7!4kZF&po74oIHdrxUQ3T^zWZXc zHBrf$-eXC({HnD4U_LL-rbQSF^ zF5@|^wWm2%gsR@^%{{HH$)*F1o!!Y!$xd-^)EgUHTA@eK8}(;XsrzMDWavR$)Gidh z8aqb0G+F%QCqMjBu>53x*}N)`c3>+hDe}$MF}M0`d5}We;R2?`R3q@ z(=BoOAn)*Nxd1#+79kEc-I7oQzK#?)ebA1wLvz{9>p-ADzh7ESBDR_YRo9PAJ27W@ zRBDH_p#;X(sp;FSb&g5M9Pxk@Y>$Te~v)#bdQVFTQ;wmt)K%ES~FtPZ6in4&>szlPZIcCKc`S<{mXx`W# z38{pkaL4vkRV^vI%K2j!yVYgG%D^U3l*!&>S=A^mT^+JWXi@qFlM5L0`()U-yQ@mCuu|C3fWu@p*wS~oN^LlE(EgbqBbzV2n8@;1GC zjP+6BvEF#Bm7%x06$tpEL~YI30jpiFUK@!}TnmP&HW6IhScGA#T_LK;Lr^Nl=|}AN zvRy{SWG%{lu%A+N6~vaP*5(WFZ83D4bC;cLfOlzh&7z}GVW1^k76@r60x8(3w z&un7eQPFSIq`z5m4zi`(aO*wR4fherup9$z0x^S7BP=Gp%HktxpG{y__~LtoB!CLV zHFm@SBx6Ug>hu%%CyOplV@C{v;7nq^kdTES0n=lTmCn5;{adD~35UswuVy!yQ$%rz zGPWT3szf8c=38L|LeQMkvi?WHC3Eb2*7io$hA`zg{cU%f2525T?~h-z=e*y@^L``e zJ(1@s}^r`Bz@6lDq+0pt(wJ`ccs?0ba zH>{a@fi2Nwn|lm+`cb>_^^Y2@G&60JcEji7O8>szudwt_7L{LRvS<}f8F?SD;bSN5 zoy&QP{=?ZEC4j|MO~#&c%jP*!Sk69UU76E=P$SXFPY6cnd!;7*$M{@P#fIp7bUywW(?Sx3EZ$UNimJK*mnn$#6ZVH7~GaUYJT4ydStBaJu2Lj!%_U zs=H|Esu?Bu%|AcoyJWoZBDu)#<{3G=_rYuxtaz@>zJ>myUN}RaAYeKdh9^^p^9XVy z*COjT#XU>hAPG7z2(CdNNH@FP1GhTc3)9Yz3qxOiwxSYIEiL->!ibu!xS;INwHCBp zr21wnj8ot$sD*Exr3v|K?x=>L>E>Xa00wS>-AJ33`UGrb|GinXoNZB(#%r%)n8Muf zcqV+i5U!aO4rClV$5I~G_KInXZ+yZK@i-GOC0PdJ+B7*7MfGPp0deR@+aWLHHtY*| z8}@}PFaOwuf=%;6*t0L>ivk$C5cA9J%olQ-=mnjy=T%E%^&C5|TH<3>KJ2P|Si%&> z&TE~Ioj0;VSa`<|>nN}T_`H_s_~Ggu09xIqsZ=UpyQ0JnFtI`~OKWXcw8-gqb&5}& zMpQhVCRRaaQb#S-*j>@`27tCk5~rfO$UvG$JEMz5MB{9J2^$T2elB8FH2WZ>8;u=I%BBxp$&QdVLgsgktr|DxKGYh(8dO@8l!>-ViYBotY~~ z4;WIWas@Vn&ii}jbI* zpEXQN-{aQ<);bU(&jiK-yMw=d#dssua`}-G>K4#+j&Jp9=rnS9;UB`O2CN;$x>4`z zY(H4s4a32WP`g+KWR6$r2f8tRvZ`h+*aufN6_}SJW9w+r<`nfV5Gq z73u~YXa5u6QB26Z$bU4Bcxi-2j?7CIUDN*}=1&`x?*s6~c+CAEFGPg#z8S2;mvtFPBM; zriLrcN9N4pIl??eep|xPD+e z%^ETNo>dU}8`ZH6=^vB1zpjcBDwJCxyqndacwTwHH0p#NU!es4dsW#25J4fL>p!8B zj4IaZO-$}T4u%4)=#s8>U*~3_3R+<+Kq!fc9HE&D5llkm1V1e-Qn8E{PnE&1+}i-2 zG~Zk_Y~#0D0<}x=b)G5A_!{3VcCp9TSsXAy^fU8}XpC5$VuvTuh15vY^SqX-eD^RZ zo9<~qcD(GBlbJH0wd8>(uqY=zk^PN)VbNSDxM`?}oLwJeZAB=it5q6(D9Sk@E5qsI z=GlvaeF^!ADqFL_K($;isT`WW(bw0SjbC{Z_`9fDfevh3Sj1`@G6XIr*Bwl*n&bCa zhjK3%hqMD~!0~&^v;q~NacN*TY4C^vglQmCgNF;V2r)n!akP%#L*C;%!(H6P<1X$j zwCi=3Js^TgVMiFh#|m%G7S4;7GHUjPH(TMmiMTry-b|}9i)2jxkU01huip!73RW-w=c+ad6(5_sY~${X6d zTDn^vOcnaxncCkdUk^=}k3Vc6Wu5GO8TB`3 zwDZr4BOLko!$O%W&EXGI8vK;v{k)d1 zc-D|U-rd;J2Hvp-;jm&4MS8RMQS{qf^xKFoCoGvteJf7mQ|rv~if)iKxqdHrO~*Xy zJZ?{+%a;cMZI@_C?R&*-yGPg&7O>sseC?6EnEin`S^`d$sPxMEWLhvsZNVJXN|8eg z<0y@vC^40(Y_y_963z>Ky(NhRmvnuZA9~syKPHUb_%TOAGJf3F+eG;~kUHZhD2LP& zz$s5VrWB6GtVP$gCls3XVS{!jD4x=k?zvM)lnwAqDtTUR4u%NDNuzVKe1o4}Wpl7V zj!=?PG{#Tnv*eJm(}?10qJAisoD1&`Uz^&&wR>9Bct(FPT z&CVunJn*PMpYdm`567QTz0pFH3x(*%G9_Xi|9Jy^lszqY5xp=o+iQs?bSK2&!e)%0 zf>=#upG?#jd0m^L@h|>#QMB#SEPD>d*wxXDhLf&_&h`f-RD9*emVNtDuwXW&?%LZ0 z^pWZmgP}N{>cI8ps_u7$fk&xxEl_27J*++|S!dMvGdduIMjd$6Z0sEak5bKnM_rwn z-i}<}10IG-^5-yEXwcJenQ|L1O_-EOgC~TXFx2j-otXhYwov1uF#pl1>h$~u%oxes z=qVP#bgDoDX<#n0H|^k4m-v~o1v=0L&j>YC@L{hbnLvXX7Qq*2s=dNU`&5P?1(%Cg z*K2SP&(TaqM%P&Y#x*zne>+fFTIe;VT(htY!Ag8jya66McU2+|+jTM5o+ku?0k=cm zq9Ar&*gVQ1rLsRkfzES7Y`&WzI3^&_-QP*2tPn)?1Kg4DA!RY#nMHc;-uFN6Q zvfPDL`Lx>IE|*8%efDlL|5;yfK`{-MWeb_Q`fMN%6_Ot*xe@0+s?|eaH}_gyAv^Qx zk-RH=ao&I}MO0xHay41BiyNb@0R0P$S2})M7Fps%2ujFw{yBp9d@V~vwBpF z^R{(wRxP`iZ5JJX+3liy!!G)&?V_*xUG&^~-P_K)p&dUPkH3-KBeNF3a@#-0&t|=H z`{(#sKQG45W;euK-3vEF)$27GugW-!G2uvWnO5m9_adMWL~tu*k+IBu!{MjD zKtxW6;IeBS`lCwY@waO^bCmA|bsyV$e)m0X(^#G3Sd9fIOr6Z@zLO3WW!`f|s?|SQ z0rg)za1ZZ>2_(umeq10sakV0HiRTr&tU(>YuM#H&V$MNB(zK}WthN&U={Aj%9|O$Q z?kwN~F|1cPN6Y40EL|#e4PYafV4>=cV9_{IN$(^34{W=-iTDLCSVFz0OHnC+CcILv z3T$#J+V(;#nmyWuh%XC?F8b3>|Kp-sV4YAWw6oS^D}{rf?f%*~(e!F1VObceg+2J? zse80^c3+}bEt!C_!FqZ#B z4Z`&tUkYwvVxyoF;*%)cXmU14+rz#%nzrcos7X(egQI7kxF?X0;}b%{H&Bfe!CySs0)_$FYcXx}b_$RTv98w0#7+-bkERW<9!;Cp z@j#*PwtOh6$%`4Tmg@#K(Ljfiv9X;@n40d1ZN>4#HkynnwylZXj8KMGrg);+V`GrO z9Bc#WEQV%H)(>(A!`c}2@&p$&G{ME`A9G~DJqt~vT*o=~TWJyxpGofCaxPqz38#}NQ2 z(Zqo)71G23vNf;pRXOI7r9zrG;0MUOLmMbaH*?WX>;6GW)JGra5ix}XgP043$c?Cx z7Kt*;VrWk_W2+N&Vo&heC@&YfT2@{N0!!z_9^z3SHFsi<9`VCP1t*|DJkd>MVMJlO zv~83V0*`QYQ6clixvl#Oju~Nz%5Vu%=0Xa<5lbt$%aM@vszSlU!G)cxkeyhrKAI>U zh1wa(C(ss5K%*LNMdX9S-?(sunPiYkhC!U@trad_wBy~8L%FBGaSN}1KgadrO@@Si zXTUXO3jjmh?p>29bw6?wY@&;X)^nzm>nG*{$c!d>t7|fXFyjo5;!Ls%;eq2!4Iy|f zy`AHKN3<1``h8IY_6^aeFz<*qMavqZyZLM%u7^&38@b%c zJH(yjI5Vf(4diq$rSBxd%)k|&!BZJ;C*^w5E4O>C%I_@dry7X-St8y|UUw69BldSj zyM80lwiB?6NSlea;UcxV?k)nAUQxDLzBB8V+)1VHBJ~#XP?0ku{M2@e*hausg4EAD z$nTxemfdBI-%4J0UZlF!8?0Z|JL-hHqm?te{&qa?V&D{bAN5g0)l37sGg|Ei;659I z`|x&yaOp%JxpWzKlG)vqqO9v@lYf4K8#hJwP_Io?L>;`5I&UGr9r&r!KY&D|!l{^q z^q&BYr>4;zBv8}tz^<}qM!)Ui6;c+vti1Jpe_QR=Na;XHGA9E29Un>(xtd zHxC&IzS3eGB=u5(-TMsq)>Yy@i*!GJZ4T(v#yp`5T;>YyC)${d%f#)=qWBs3bZ*C= z@Ed&g8+QDO+>ReeSuA6_1zOI&0RQ3SGGDs`n$6t-&1&1|M6n&OFF0W41!Nz(&D@wG z*rRmkf+t>ZqOWWAMlc~VGCM5ImRX`LycZxmb8Tsy`C$CP_`@}s4;L@ATW6u4m6o{y zs$t;8_3^+aRBjiGz#`79;t<-zeU$Ph+4ok78iVFLN4D(D%>bn?Z4VIu(sd^E@*`_Z z5iuIAD&Fu=9yKe33vJ;vn8sO=}!0faIk}SqG-6Gr~CQ!`aR-;u{TLyOFl3!-~LL>`V z1Cc3^+0G`f7K=_m8G+9vy0bHT;2KO9dhtKTnNa=J61wc z?sKE)IvPwqgjts{9@Z+IRJ`f!f^00}&>i@6!i@y@sEn>sg4Hu$m|Fqn-R1%X%4VIj z%MQ0)nk(R1A(vHuFi*<3qt}VYvp=P~45ETq7n-Q)D1#OV^H`CUhcz69r~ar#=Owh4()ZT!P0AK{v=7V|>VU6r|z8dWarTKy4z<^hktC=8!mk z;=W0G*0L0xVw^E?t^+m+Gd+qQABs~jY{nDEU2PyftjY9?`cC*K#*fRLK=~)R&eUWw z0K7#-)u6RBc2B-LR&85LlaOaCx3#x->}_nie@l0Fhd8}bZK+Nv-)ryb{uF8hA!K!e zySVveG8&mB7pv*6lJZzTEV4I6{tp76lDJvMh7$PzFeU1M@ma%bwSO*A?5p9 z*MoD(e7i2rJZr033`qy|-(Qx?d7XySD`JvB3&|^1=2^c~Gtas`F6S=Sj+l8CG9Til z5BhjyC4WAQgeybW+F~xZJX#AEJ@I^4Z{(S?mIo_IZL9bL>a7AaWzz?7FrS~bPx_#V zM-f<`xtSlUpQWNrDkBzD=kM=8&a)`$}3w0S-s!Vh#0AX$gQZOA?s9BsXBIhziLN^ z)B$X$)O3TYtU9PC+kvZ;VPY>1jhF(uqeW_mqcRfe)m7!Xi+KuRDy*ttw&rNDkgI_R zM;=?rOf*3iDnu5#S8)ocy3~_W)g$jc^ zUa2Z7cScZtEenGx9;9$lK`}X%u$SZrbI>R%w}ik`O?IJ_JEDZ@+>7-fDGti1o(j%b z4pEb--I@#CkyWb6VyKuLRT|4fjd1Za#-gmM4pJm``kE}#Sf&iFR<#}U%xv$W5EY{$ zbdy$;iigOh8OMRh25txCsetVyRM%`Gc5ZT3G+DDW*(?IflU-He;YL><9mJwV7q z(Urln6Q?>u91^g5YkI7w!Rng_NwP@261qx-Ym%3%R5i0)0KEnmd8+N|Y_}y;sEG~} zFjoy~j^+xtekiI|wo3RpqHBUDRG!!}ly!4d9#HVsk?IXs6YEIrCK3}NZ><6Cn~U|% z4SrTU$`jYiXN8Pq^4#qMpl+#xXcB#nJsMrFKHI&V+vE0`3{8{;FVuG=YX$=6_|OkM%hoiL`flq zASk(2`!j++YU9i+9&UNXRrAL#juAXhm6k%#~H*)>u9l}QTM>} zUOe$KXYKb?D}D};eVaJGqCidxg?uH%5);|P%Pw1v^qPs+YBJyF=Q;j()r0eI;$=Jh zzOPngz6;Q-xzw5OQX`-k;6!j`TZ_sKI613Zf;s6rYnt9fYpW>+tY?wF z2K}Bj=-2m-QT14xjI76!X;j{_SKDXZyR4qx?4@!dX?^_`w>pN{9zI>zLP07J4iQbi ziPJ!x{-Ff4zX_d+`yaPzo}LF_?&2BbNnb+=0p;(eWkL|XO5LqEau@hucD(}^JFz6; zPUfy=HchFG?h<;2b z;!v+E?DakC@};WVUdCY1FkYz^WcYHJ4Ok%~wee#Dh%Fquq|$3diRI?if}DqqL?X|P zhO}G`{f`Kyw6r!)UQvZJuh_%D6ABPGLPRmt$Ms*);$7$2ke zrkrY9!#rd)*xuKAmMz7ZoTzlF`OMNm`RZ*dXUllh^`kgg4F^S&Fww48s8sy z5)_8TMz2=;ZRQ8=r-`2VfoshVe4;Qmlk1WaVkn{z!>Z_+#P!VNx|m<)Zv!klxz2al zJw{krA`CUYb0`gU>~@@!y>k0-Z;H%gEz zB+EL*b~bqc52t+3QwA3c)8y^sJh>&bX3Tbk$t~8_$t_ttfEeKI{!257qbhFk7X+B( zRU;zXtFc8;o{i!$x4OF9u4DwV&B!5>OC-mz+B5W49qw}e(`LA^#)tU(ZwoWYw(*okhP6uZSEmd@%plUj&DSMN8MzUb&H>jX3u%U)opWB-p6v@ z4SC)TIq%*)@7}C;rab4J(Yz(E<=N1w)?DaRYtB17(5d7DIq#9Y*dsab@ZPG}>6~|8 zUhKY{cR&Rz_C(J6XkP52+JG3)sV%``^8VyA5`6lCVHVm6usEF-NrLxkOV9 z!J8?Q=iM8;nKpUeTTHU777Vi_&;JsLWnPK{ z!JAo{=iM5-A=k+BP6zLi0`HOF-B;k<7rdGAd6{H42cuw66MLzyZ5PM9B~#w_8)YB7 zL>K1=xy+{;m~h_OOP1ccg&9vZRQ%MjY-dhQuyr|KR&MC6saB@5kIRhoO`jTJ3VGi? zri`~9Wr|P>RoK=)78Sx|bv{h8kSO~aFy%oK?E=Sgz708RZ`PVA&sy2ov-EEVa@LWY zHJ!8e<*c5&+x8rt_!9vE+IJ!=_s=$lzz$%WKoEVDTmRYMHS|w#*h&u$Tid~5i#X4b zDGv@?vB5E*r5POkT8zOlVCxW|Vrq*tp38E8?a1AcXiY!lPpi$l6}wp);y#E1iP#(W zqXJ$1%VuJhG#iCkM&bdNsxR(8Wj;safsTYbqP3sg8&9SohQkElc=5FUE_|~5mmk;P z-+r?ECj0xpKUu!d{{H7Dmj$LnhYWP6t8UPT4p}7O2dq^VYb9{LP9^60WE!Ok#qpuw z5*?rqh%-O(3uY40F`FcKG*OfJam^4qxl5gFW6J=}KazwBe>5o5qSlm_&1~|ppZ4iT zUCYR_EVA9`Q1;MHi)2po*DRX9=|`8C6lE(ZK2%Ut(43+UQfD20ce%lGLm`(OJ<;5CI2D!mzQH3Ydn%Xk@@0IPa zAT`$#xB$dU2scQii^`;tdQCoNt#?TcnoR3t&eL`wlh0+nCR~g9zw%L&;{bvn7f1r4>DJW$ z6=w2R41b{Qz>%HhB5Dm<{x%~f-^d98?f(ktD8%HE$|lx3y)C?bo5D{w^^!#1-&H)fhWiG99 z!1m-hIY5krUQi7}oB%#go?}sFU>$^r0EupfMiCxG{Q$xdLfBXQIIUrTR5SZeT_rs} zI6=$EE{R8cw9H?1dziJ(&keKNjr0wf{<1WA&K-yrT0D0i^0w!J^emb;XE~O+HIr}2 zc$;3FDle)s4(lo-QykpLnF2491yEx)1!C|}3l*PC1eh7Vgm{Sh(tA6>QceW0D7tEw z5Sn1S)eqJ?%|V0p8XJP8<$GkcOcZtVz==axRDI2vf!$QjjQAY^-`r17Xa@!IdFT8uB%0lyiuOU77utl9IViD9#tr4LH+9ibw~6y5gTGH{%%?I_2)ttk4$W#@s6!UIko;pKI` zZ~qRHU>rs7t{CnjUGEZ0Nhh>JpDc=g`m$@U`RRM`_M&L*drF_{!1pwYcc(hqJG<40 z_c(=AY0|P%K62^$`!4@*O3f6Ga-+tM1dxsPEir_lqDc8Zk1gj#i%b#FQ1G28SEZFTuQG#4HUUrQ(bRO#H zZnrgedt+-O1dVLf+lrzE=}q4k|K$0_GHrArCnGEbU$YDpfDR|T-ya=w;sADPkPvv=mkCTp{6EBjF1?G{(Tt#eyrSGSYIpoSI9wDs(o+@26!+G?6l9f=t(+nmzdYl+1}jK z)V+0Mc@fm1`YYIwAKDr)uV9k;Y;;K@0uT&; z1sip1d-sl(FDkIt0wGwjy}l6hG#YEHFwH-;V}f$z(c7W0F7F_#={X)DyvnOFY5v*?uYZL<_$*WLk$Z*3;x z?^whFA=CaoyPboc&90G1zZ)(YjL{6X>eWsRx`K`2CwXqUfj~`rK;IUThhkPS}^sQZ%v&o9U8-eh~2EacPuwX zw?HZyL$$5Fn-2Vd^;=0#h1Ql-r%r#8-(XP+R$RMP$0ajh~+MMiuDm zk@r$+q@^PUHp+O!Vy$y`n#;Jk`D51sblmy!&dmjRYeNXZOD&Ls6T~hI8ruSx@eYYs zYiMgNm_~P|y74Nw?`%$`3hp}^pi3U#9y+e+Lq%6SX&lV%Ey^)?*|XGhIar~S=)12 z+sUl$j0YbkPwUM-SBnr~YI(3tCxdM&%ji6HyfUOx1s1?uY$VW=r(=H5RD0J|Mz!Fn zr(NLW%RI0_SjqcnRfwiUQ!haa^pb}@km{pCMdtgwBlCTJt!bR++6|eSf6IG5y!Sm% zG@{#6wcEKMfw5ok&A$K9EB=u%FGnfpuouLO8s3G2rLV`DJ9g4cDtjgu<{U`XUKqF|=s7(Z%k&$lU{utUU)g>V# zpCH#6GYW**qvnJizE07AXOwLPuGET^o__n={!CI8z`hXBuU1 zMz61qWAn@v!FFP_3YLk{DoiJVqp0&4VlJl!i;EYbs1!E|@e*-58n7f?hpmJOepFi7 zM80QJPg0Tz5OX{x!M!LfwH4xm6?ohNj}v(OYV8&lg6jlfCv4hiO1ctG$bT92(M&(4 zehcx5j2p<$MLb57VAPrxmuBthEwBt zb3Esckmt`6tb?;(UuAJM!7+Co4A)Z|sSn#Gn+F>N zO=Itypl|{d21N&=j1Xot`Fzg$T+WKzxDeqCV#L)eHt~^Y=@~N^XbE2PzRBvMxkk`t zm2{QULVS_kfnrQV8w^rx0A5sm;!4w-?kmn5vA}8=vr0^!Y}p({IVMbzDuh{9jGMbE z&}_xUnt`6I+k9{r#I49ByDF0OR%omJ0thxC)FxjL$nT&Ackl&*$K3XPmCpT(EcYt+ zbR>X`_q2=0V#&=&U9+}OCKRiOan{k{NSy4%;J65jZE86(OsuhNDXz=em9l;&>Rt

    |(Z?{LeeM|n+$voLo(?;Mk^|XebdKrDt)alUVp%N=6o_d&t>%;1x%E1?UnW>G| z4RJ13&01R$r*DKV2$GoG2vD2Tp(3Y4o)%2r`fXx4&r3TGlQ`FIN+s~NgN=Hy#iIDB~61G-_Du}kXM&=M{2Vj*BnV1uo%saKbY zRk+PqW^){|cNjx#4}8Kmt_MBh7^ofBv13dY3$A0-6+5xiKje}m#N{i7VaI@6zS%N^ zlUEGE)tg|c5#O)aheaE@bUW5g&sd&BgpjzC6?`bVu8^iQ3G=dJp3bE<(AEgyG?QP= z%JG?cgN`@<@Xdz*A@9!i)WOF43o_zDwy3GSrL76!y1b(0B~THKtsMs% z@9Wm(!xv$??Ud-j?)yMrayxEposlS4ujc&x)RcXokSw>nBo3@*`>8-yg+9=h@?kDs z$?=vxjN%)9$Ob>A9|3ctB8YF#Uyb%6l%?YnNUO{t8Y?eGsK6#k7)JqG#I6rQsNaTrR(VyNKDN0Iobl-P(?-M~$q1nn`;SSP29OOAyr(UF*xFda!M z{ovEO7ELQTRG5xt4NoY06{g9+#RnNPVJhUi2A}4bR4OsVlFO9w4^Lb5-^yLXE~qm( zY2v91_I5ILLDH9|E|{Cic_HzkGMzq5F%;T6re(2_Ixez-A91QrtLNiQ;whiHK-$+# zTF%sK343WFUl_^A$AspDx-yN~F!QReHkY_(PiFPt(*R`CmA+wXDkB=AB|^{|1``L& zr20U1O~h&_2lcHK1tGiC^djU3$+Q$>z-5kw(2_24Nf#O0EJvu z_|vzNT(Tm4P67vQ1pQ_=y?Q}l*j&TvjHfr=TUP5LukmU2xNjC1pWXw*V#vnbyrshX zyidhEWG1euh|^~)q(TL7fsRA^zNfg1r}y}>)92kLA|FAA3b6Zr`aBb95TgCSk42FP zj^xT|mP8q9S~Mbn&ir6E(8faXnweLY&7(L?XD=S@iuG<(S*Y@ev#P$6!jUHuWQk808*rot_LON6Y&M4a~m|kadY26Z34cjme zz2zVZVPxnNxK+!piicmuVyackrEZnp34h9jO*--B`krE(T5OjW*IT8 zh$9N#E;D~qrF8X$##5})bW^TZ#VVcWlsJd!UOZ~1Vdurr?1d#H0xndy5n_Y08!}Ms z-Nqd%PvDjJB{{udZ+O=Pq(`ETajd(p&I~-Q07TPI=eP%^PuP}ERPBz=G@cTRry-N& zgd3s5eB$ZD2?yh7uQ?3qc8=V9I!M_DSe`zm#Il?WmQz-p2`g!Wtfa0=Tk!O8Gmn~i z%*-Pyux9#*Cb3tIh1zj#R-`dSza-MInqrCRV`fJs6iH+}CIf8~HPgprJX!J&=&zRT zxHM;YM0m2hy}dPxK;16bf~w9hited3*$PO-*#hO$XHuQrEs!sJuX#}0ZL>fLp1V8M z)D7vX2okvkmG1;i?Oc`6wKcT2w(J*GtaNK{Xz6THGzDyGZEfl3!XY?meOq&9>Y)#H zB|BQWn-1)2@4VlYYp+$(!QIxDqJ9F=Hn;5MX{xEI1>M7dR!u34djF#krIz%g- z(a{tMF?!n`KCf8c{WMpy!Vmq%-VDuXt^O2bP3y?F<1_1&!AG$-Zd1@7-`X?A){^u`Tsnz}aN@-;M0 zCZJO9>PR*sB@j&}VB0H1>mJ-Z49(xw*3^+|YT4J4^7(CR+^cR@XK8qodk-c3P->9w z7OA`GLC=kdvRQxohGauyH_$P)RR7(9cJgqp7FP7w~d0&fWfvRN1 z_$n&SmQ-W&PdSCt&p;p6P{E499(V+A{#7UkLpZ&_Kd z7uH3aMX2%+JJ80KE{5ZfJ!dLKk{Ie+zhyQ+6~IPyaOLeiU96R^u5FEnP}i~(S&}qd z8^Fdxyv(*Vp~g8Z(ODkc+j@wHPp$J$?^=K7&bvOl>29}_H>URX?2jT|zn5-k z?>N-ivj0GL@;^M4jg`EyeibjGO1Wy~s+;Opetc!U+ep3B=Nj(LQFL8VRCmd`sylLj z)j4al{L69F@NeU+Pn+z?th2zfCoi50xR1Ww*xI@s{Y=rpt|mG#)3Pu_PQ6QtyxWYOSn;m6R1Q5P z6{8LzLg*WHLo@Ay*?{x zv&1_xH=?DKv5opjl(&=1*g>B;$($1R4b?X2zCyob^wdRqG8x@Sy;m7IM61>PE}xJ3 zM7f9d2U&rUmqjTwrG#Q$PM_x5;KtlZy=-O-gbghm^O+*ZH6Gluy`;>LX6C3AjtLa? zHn-N>*h=)Hq=6n{v$=FSHw?zt5ooy&9Ez{iV= zN{-!GzpehNJf8*i^X5RTSMuw{eNp|Y8EH$BBR@!%G`+I-dH#|A zWAgl;{+Bg(ee2kL|LN~e92=mbZWL|*ICH48aRrBlT&7HHWWbVS^J8oZfb^Q`K$}8hLM2(&5Om2rm(?hY z=-RiBi(faeDZ1ylRCU!ayQ=EORrNRDcyqn|eGccERX5(m@8(b3{E5%if2OcOX2_ExbO2H1zBR<)7Tp-b{Hd``aKnP2SxO5PtXSTj~djugmWT z#%0bx@dC!NxT1KVxG0+Z%Oml1CD(Va{K=IcDE@`9-~KnZWKP`i(g(i$(c8{G^50h- z`*iz>^q2qQ?y=wc@4xitXNH@uA07M25943_nMZ#2x5u7+w)9IM`jz`r|L0dLTfX_s z)=&KM-yUrH`fJAqKlERJx$2`0uYcgq52_?md_z?=vF$N%%meSdmnRmrcg=tF^&3il z^FRLVU%mRVuYK)o+y8a$C;#=@-(2^5-~OAbU;V>Ze)aJm{^6fr^Hk-7-}+GUi9h?D zANT!b(VzbCuLnN-XvgGFe)o$1@(+Kq;Fe!sdDo4HyEor|$IS~LD!u8hrj@5|_}=fO zzE*Po;+G%z`_turcX0Ia>TiDIzumUtwXZLJ`VY&BdiMYM!PlFbmfiBDmeGIz^-Z_6 zeeS;LCqDn5zfwO`e{RmV*8O4Cf;BJhKfYt+?@!gI{=WT>{<7|ygWWB+op}Dh&wTTL z?7!)kf4ltEzS1r0{^oxUG;cosX4lhmAN{-5)UW;CuFuyV{p`{|>fQWs$JdWsJ#f{D z;n#orj}!g1>;CM@t3Lep?xTx8`d6FY{Nc0T*?-sae_8rBhqrz4FaPkunYaJ`#oKP& zyfAsoSjk`gyRVmRK3esQKRs{XcpJRYjG58#GrNcLT5$Pz~8*ctmCA^VnnvhPdQ zER8K>4GANJvW3cWrHEH4Q6i0fC(Gp`J9!VK-22|o``-J0K6n0^^PF?$nK?7h@B95d zzcX_tYI!1I!Sc%#J{W|Mdl(26fPs)54r3N}C^-7UXmTlu*=5p~2^YpIc$P4Qq3?l$ zCjbjL_E^ZEbWj?>`IO9kk@O2lZaD=HR?%cL`pD_SpkX10K=z?fFeanG>)e97Qy;WX z00iJ2Kz*=+6G{RQ!-zmQoB;pehyY+ZSjh^d0jK~N3<@sd0YosE{dE!$9)RM(MmSUo zKmp>pqPfCRf)3!d=ut={+RKF>eHe;p>w^U(7<g8%~or}K?@OL0D#8A^4ID%GVN#3Ti=_I0T1jmojXx`Yqn z;aY5w68D@f&a*}}Qq(;RkkspVvV&844(l^R+xN)PMJq*l@%9SRu2F?IZ;hE#%Gs|g zobzgBAIBFzt-(4z0NLnJxx|%!&8|sDv?c~$|JAPXjtk!6*Nh_mv^)|$9=JP>vFK9H zM%l?ZbIi-Qim?07u3ylN5a<_Npq(tzkam&q?l~1H!8#gw+F|Ve1@Yo8QBJ=X@r-n> zH#F>mk?78h&yFKyQV-0rubD4D#H}KI-qnSye+tJ^rrTsbRHp6^9aP;JY0(2btmk4D2+BT>S6IwPA53(S_tlCI!&4#;`FH|KTnB$Efy= zrb2>(KWHi>3J3}QpsD^}r6~g70mm1VfcorzqbZ1bUGNDoko@BZc*s&nBFIumEItNu z9WM)k)!P=4O|XI~IyJpp3tey1(_?7s>~;y+H6~1>53m(FMR^8KMa4zYU#<b9j$@|9gx<7Y&-lHu5OKIFQDlUT>r*&UwZ}QmA*_oZqtlVhSTct4 ztR-AZXg%Y{w_7%NS1!BJVP3*LPR6mb4PFk}On<7WCqq%XW;*%uMe}r?W^2Kih8ucf zC3|zUrSIc@h?NqN!eFBaVdwx`?&bNrAx4aQ$US*rQ&|0E898q|YczM=g8PMs z2>l|S68eG;lK)^C)d7eOSw;lFK$d~>{6BDZ@f*&R00lrUS0+~~3Uv%;-rrn*NVD(b z@tfQa-2Pqd2WAJweu%Z8BaDGqa|c`jm7_L*f2?00{=d_&hZq`Q|IrU5ivBnrk9BlB z5)SDIz8|oC%$Ofq)Ga8FmNkodANn=ps*-h!VVY-GvTS7DyqsKBTkojXUgVoA5>i|7 z7jyfgA7X3p?!j75SrH>O6loBv4|C?H;iqW?W%y z$4&ZG-TXBybAYMP;Q3jN6DgamM*bfrdUd^HbR+WX zVUN2x6c418_P;63mWKp_U zQojMpS0*RW?9!=`6LY^bJ;Nin>}s8lw-_7BohgNIE|A zhzPvHZ0}5O6x-{*dlklucfKlyxf^kddHdBsP=Q%PB{i04udjPf-(tuTW-vSz=I@AY zsLEvKebWBcmh-Lgs$cV{Hf5Dav8qu@f(nBB?2-)nc9`$l1D3U1<^K2yO@|O;y>%ri z(;nN&+dK_La|`ftOTOU*UA7ogT>T#2AfFceYCBWXT%F3S-%|EP3j-wBgQ^ zX(DB=LU-ohw{0?_{pR;oKXT6mG7M7k&qTPtR?t!{(YOyN$3f7X%Haq3)`fJG%_X*XL6=2DovKG7mWsLjh=^Co2cV4m(Vd*cL zkw=!w1}&BGJ4^kkhJISAlXm62$)#?M3?TVoDSWUR`KReN z<+o{i)TRQc4KPoKQUK})k79)?0`hMo3pnx+_(EL)XF%zw4f^e8uYb*NN4@&| zJRUIl|I2m*(>pQ>X{Q{o+!1kehs5}WjjS}ySK6>=FuSn8TUI-W7uDyquOl?cs_G*+ zYX*1TKhi_{N|;K;U$`OJl$uN`AF6$+vf~PsmEi~i5lggdvIJQt?2CaXg{zx!FWRNU z0@s%WZ*JlF1w;{(eY%nG?n)gk-c+3R9<}mVdWgYznWfqmuh%^dx}_5~H+_>64Xme~ ze3+t3j0RF^x701kUclumcHJzPGd5FQIvqk88uYr-)5TvD|4L_c=|V9NyfLsz*Z6&U zkM`0-3bq-q8^TBlazfFkJHjRTfE|)1yju=Qqn1l?m5q^IW+d^WR;~uWkU^%~HFZHm zs5qb6bqh8BT1A@eF;~@QE6T=fzehqN+>@-gapnWp^jzdp!|Su;2PJEM*;QL2hD9o7 z{_D)P;j1uB5uyw8?I#Qn%Rb~H29JfJ&Q%to(j)5RDqR?mMW3H0Ib2nya3i?$R3Jkh zo7Y(920vrusCY6|JcP<#x1h6|Y*xyw$s_;qI-km>kGueefD*tEkb=b~z~P^f2R_B0 zIqO(aEB6{eajb|%B5)98j6dp3CkX^dj%|hl%*R@%1jxYf{8F{@nKjQM zZ$%B6qnJ-(*fp-*{Yc94U}s;Cp+19xGJ;tsOWmfzx^IdOiL6af@=oRAa>__|9Aolt z{glJL&`a2ymb6(Ad~pD!!dtZ=#4=W$mkv&9*wM@9qK9K3;)GH)=8kev>12WvpVC3yN : EventArgs +{ + public T Data { get; } + public bool IsClosed { get; } + public bool IsHot { get; } + + public TValueEventArg(T data, bool isClosed, bool isHot) + { + Data = data; + IsClosed = isClosed; + IsHot = isHot; + } +} \ No newline at end of file diff --git a/.refactoring/test.dib b/.refactoring/test.dib new file mode 100644 index 00000000..6c442688 --- /dev/null +++ b/.refactoring/test.dib @@ -0,0 +1,92 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"name":"csharp"}]}} + +#!csharp + +#load "./base.cs" + +#!csharp + +TValue vv = new(DateTime.Now, 100); +display(vv.ToString()); + +#!csharp + +TBar bb = new(); +display(bb.ToString()); + +#!csharp + +public class Emitter { + private Random random = new Random(); + public event EventHandler> Pub; + public void Emit() { + DateTime now = DateTime.Now; + double randomValue = random.NextDouble() * 100; // Generates a random number between 0 and 100 + TValue value = new TValue(now, randomValue); + + TValueEventArg eventArg = new TValueEventArg(value, true, true); + OnValuePub(eventArg); + } + protected virtual void OnValuePub(TValueEventArg eventArg) { + Pub?.Invoke(this, eventArg); + } +} + +public class BarEmitter +{ + private Random random = new Random(); + public event EventHandler> Pub; + private double lastClose = 100.0; // Starting price + + public void Emit() + { + double open = lastClose; + double close = open * (1 + (random.NextDouble() - 0.5) * 0.02); // +/- 1% change + double high = Math.Max(open, close) * (1 + random.NextDouble() * 0.005); // Up to 0.5% higher + double low = Math.Min(open, close) * (1 - random.NextDouble() * 0.005); // Up to 0.5% lower + double volume = random.NextDouble() * 1000000; // Random volume between 0 and 1,000,000 + + TBar bar = new TBar(DateTime.Now, open, high, low, close, volume); + lastClose = close; + + TValueEventArg eventArg = new TValueEventArg(bar, true, true); + OnBarPub(eventArg); + } + + protected virtual void OnBarPub(TValueEventArg eventArg) + { + Pub?.Invoke(this, eventArg); + } +} + + +public class Listener +{ + public void Sub(object sender, EventArgs e) + { + if (e is TValueEventArg tValueArg) { + Console.WriteLine($"TValue: {tValueArg.Data.Value:F2}"); + } else if (e is TValueEventArg tBarArg) { + Console.WriteLine($"TBar: o={tBarArg.Data.Open:F2}, v={tBarArg.Data.Volume:F2}"); + } else { + Console.WriteLine($"Unknown type: {e.GetType().Name}"); + } + } +} + +#!csharp + +Emitter em1 = new(); +BarEmitter em2 = new(); +Listener list = new(); + +em1.Pub += list.Sub; +em2.Pub += list.Sub; + +// Emit 5 random values +for (int i = 0; i < 3; i++) { + em1.Emit(); + em2.Emit(); +} diff --git a/.sonarlint/mihakralj_quantalibcsharp.ruleset b/.sonarlint/mihakralj_quantalibcsharp.ruleset index 5ad478ec..a2e953a9 100644 --- a/.sonarlint/mihakralj_quantalibcsharp.ruleset +++ b/.sonarlint/mihakralj_quantalibcsharp.ruleset @@ -1,390 +1,390 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Calculations/Basics/CORR_Series.cs b/Calculations/Basics/CORR_Series.cs index 246baa79..f82d82de 100644 --- a/Calculations/Basics/CORR_Series.cs +++ b/Calculations/Basics/CORR_Series.cs @@ -1,52 +1,52 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Linq; - -/*

    -CORR: Pearson's Correlation Coefficient - PCC is a measure of linear correlation between two sets of data. - It is the ratio between the covariance of two variables and the product of - their standard deviations; it is essentially a normalized measurement of - the covariance, such that the result always has a value between −1 and 1. - -Sources: - https://en.wikipedia.org/wiki/Pearson_correlation_coefficient - - */ - -public class CORR_Series : Pair_TSeries_Indicator -{ - public CORR_Series(TSeries d1, TSeries d2, int period, bool useNaN = false) : base(d1, d2, period, useNaN) - { - if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } - } - - private readonly System.Collections.Generic.List _x = new(); - private readonly System.Collections.Generic.List _xx = new(); - private readonly System.Collections.Generic.List _y = new(); - private readonly System.Collections.Generic.List _yy = new(); - private readonly System.Collections.Generic.List _xy = new(); - - public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) - { - Add_Replace_Trim(_x, TValue1.v, _p, update); - Add_Replace_Trim(_xx, TValue1.v * TValue1.v, _p, update); - Add_Replace_Trim(_y, TValue2.v, _p, update); - Add_Replace_Trim(_yy, TValue2.v * TValue2.v, _p, update); - Add_Replace_Trim(_xy, TValue1.v * TValue2.v, _p, update); - - double _sumx = _x.Sum(); - double _sumxx = _xx.Sum(); - double _sumy = _y.Sum(); - double _sumyy = _yy.Sum(); - double _sumxy = _xy.Sum(); - - double _covar = (_sumxx - _sumx * _sumx / _p) * (_sumyy - _sumy * _sumy / _p); - double _cor = (_covar != 0) ? (_sumxy - _sumx * _sumy / _p) / Math.Sqrt(_covar) : 0.0; - - var result = (TValue1.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _cor); - if (update) { base[base.Count - 1] = result; } else { base.Add(result); } - - } -} +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Linq; + +/* +CORR: Pearson's Correlation Coefficient + PCC is a measure of linear correlation between two sets of data. + It is the ratio between the covariance of two variables and the product of + their standard deviations; it is essentially a normalized measurement of + the covariance, such that the result always has a value between −1 and 1. + +Sources: + https://en.wikipedia.org/wiki/Pearson_correlation_coefficient + + */ + +public class CORR_Series : Pair_TSeries_Indicator +{ + public CORR_Series(TSeries d1, TSeries d2, int period, bool useNaN = false) : base(d1, d2, period, useNaN) + { + if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } + } + + private readonly System.Collections.Generic.List _x = new(); + private readonly System.Collections.Generic.List _xx = new(); + private readonly System.Collections.Generic.List _y = new(); + private readonly System.Collections.Generic.List _yy = new(); + private readonly System.Collections.Generic.List _xy = new(); + + public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) + { + Add_Replace_Trim(_x, TValue1.v, _p, update); + Add_Replace_Trim(_xx, TValue1.v * TValue1.v, _p, update); + Add_Replace_Trim(_y, TValue2.v, _p, update); + Add_Replace_Trim(_yy, TValue2.v * TValue2.v, _p, update); + Add_Replace_Trim(_xy, TValue1.v * TValue2.v, _p, update); + + double _sumx = _x.Sum(); + double _sumxx = _xx.Sum(); + double _sumy = _y.Sum(); + double _sumyy = _yy.Sum(); + double _sumxy = _xy.Sum(); + + double _covar = (_sumxx - _sumx * _sumx / _p) * (_sumyy - _sumy * _sumy / _p); + double _cor = (_covar != 0) ? (_sumxy - _sumx * _sumy / _p) / Math.Sqrt(_covar) : 0.0; + + var result = (TValue1.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _cor); + if (update) { base[base.Count - 1] = result; } else { base.Add(result); } + + } +} diff --git a/Calculations/Basics/COVAR_Series.cs b/Calculations/Basics/COVAR_Series.cs index 3826ee9d..4d6891c6 100644 --- a/Calculations/Basics/COVAR_Series.cs +++ b/Calculations/Basics/COVAR_Series.cs @@ -1,46 +1,46 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Linq; - -/* -COVAR: Covariance - Covariance is defined as the expected value (or mean) of the product - of their deviations from their individual expected values. - -Sources: - https://en.wikipedia.org/wiki/Covariance - - */ - - -public class COVAR_Series : Pair_TSeries_Indicator -{ - public COVAR_Series(TSeries d1, TSeries d2, int period, bool useNaN = false) : base(d1, d2, period, useNaN) - { - if (base._d1.Count > 0 && base._d2.Count > 0) { - for (int i = 0; i < base._d1.Count; i++) { - this.Add(base._d1[i], base._d2[i], false); - } - } - } - - private readonly System.Collections.Generic.List _x = new(); - private readonly System.Collections.Generic.List _y = new(); - private readonly System.Collections.Generic.List _xy = new(); - - public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) - { - BufferTrim(_x, TValue1.v, _p, update); - BufferTrim(_y, TValue2.v, _p, update); - BufferTrim(_xy, TValue1.v * TValue2.v, _p, update); - - double _avgx = _x.Average(); - double _avgy = _y.Average(); - double _avgxy = _xy.Average(); - double _covar = _avgxy - (_avgx * _avgy); - - var result = (TValue1.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _covar); - if (update) { base[base.Count - 1] = result; } else { base.Add(result); } - } -} +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Linq; + +/* +COVAR: Covariance + Covariance is defined as the expected value (or mean) of the product + of their deviations from their individual expected values. + +Sources: + https://en.wikipedia.org/wiki/Covariance + + */ + + +public class COVAR_Series : Pair_TSeries_Indicator +{ + public COVAR_Series(TSeries d1, TSeries d2, int period, bool useNaN = false) : base(d1, d2, period, useNaN) + { + if (base._d1.Count > 0 && base._d2.Count > 0) { + for (int i = 0; i < base._d1.Count; i++) { + this.Add(base._d1[i], base._d2[i], false); + } + } + } + + private readonly System.Collections.Generic.List _x = new(); + private readonly System.Collections.Generic.List _y = new(); + private readonly System.Collections.Generic.List _xy = new(); + + public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) + { + BufferTrim(_x, TValue1.v, _p, update); + BufferTrim(_y, TValue2.v, _p, update); + BufferTrim(_xy, TValue1.v * TValue2.v, _p, update); + + double _avgx = _x.Average(); + double _avgy = _y.Average(); + double _avgxy = _xy.Average(); + double _covar = _avgxy - (_avgx * _avgy); + + var result = (TValue1.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _covar); + if (update) { base[base.Count - 1] = result; } else { base.Add(result); } + } +} diff --git a/Calculations/Calculations.csproj b/Calculations/Calculations.csproj index 701f67f7..0ed29821 100644 --- a/Calculations/Calculations.csproj +++ b/Calculations/Calculations.csproj @@ -1,80 +1,80 @@ - - - - QuanTAlib - 0.2.30 - 0.2.30 - 0.2.30 - Library of TA Calculations, Charts and Strategies for Quantower - Quantitative Technical Analysis Library in C# for Quantower - git - https://github.com/mihakralj/QuanTAlib - true - Miha Kralj - Miha Kralj - Apache-2.0 - readme.md - net8.0;net7.0 - disable - preview - disable - true - en-US - QuanTAlib - QuanTAlib - True - AnyCPU - False - full - True - True - - Indicators;Stock;Market;Technical;Analysis;Algorithmic;Trading;Trade;Trend;Momentum;Finance;Algorithm;Algo; - AlgoTrading;Financial;Strategy;Chart;Charting;Oscillator;Overlay;Equity;Bitcoin;Crypto;Cryptocurrency;Forex; - Quantitative;Historical;Quotes; - - - - - - - - - - full - True - 7 - True - anycpu - - - full - True - 7 - True - anycpu - - - QuanTAlib2.png - https://raw.githubusercontent.com/mihakralj/QuanTAlib/main/.github/QuanTAlib2.png - True - ..\.sonarlint\mihakralj_quantalibcsharp.ruleset - 0.2.1-dev.2 - - - - - - - True - - - - - True - False - - - - + + + + QuanTAlib + 0.2.30 + 0.2.30 + 0.2.30 + Library of TA Calculations, Charts and Strategies for Quantower + Quantitative Technical Analysis Library in C# for Quantower + git + https://github.com/mihakralj/QuanTAlib + true + Miha Kralj + Miha Kralj + Apache-2.0 + readme.md + net8.0;net7.0 + disable + preview + disable + true + en-US + QuanTAlib + QuanTAlib + True + AnyCPU + False + full + True + True + + Indicators;Stock;Market;Technical;Analysis;Algorithmic;Trading;Trade;Trend;Momentum;Finance;Algorithm;Algo; + AlgoTrading;Financial;Strategy;Chart;Charting;Oscillator;Overlay;Equity;Bitcoin;Crypto;Cryptocurrency;Forex; + Quantitative;Historical;Quotes; + + + + + + + + + + full + True + 7 + True + anycpu + + + full + True + 7 + True + anycpu + + + QuanTAlib2.png + https://raw.githubusercontent.com/mihakralj/QuanTAlib/main/.github/QuanTAlib2.png + True + ..\.sonarlint\mihakralj_quantalibcsharp.ruleset + 0.2.1-dev.2 + + + + + + + True + + + + + True + False + + + + \ No newline at end of file diff --git a/Calculations/ClassStructures/Pair_TSeries_Abstract.cs b/Calculations/ClassStructures/Pair_TSeries_Abstract.cs index 2203d3f1..99ccfee6 100644 --- a/Calculations/ClassStructures/Pair_TSeries_Abstract.cs +++ b/Calculations/ClassStructures/Pair_TSeries_Abstract.cs @@ -1,132 +1,132 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -Abstract classes with all scaffolding required to build indicators. - All abstracts support period, NaN, and all permutations of Add() methods. - Indicator classess need to implement: - - Chaining constructor (Abstract's constructor executes first) - - Default Add(value) class - - optional Add(series) bulk insert class (for optimization of historical analysis) - - Single_TSeries_Indicator - one single-value TSeries in, one TSeries out. - Pair_TSeries_Indicator - Two TSeries in, one TSeries out. (includes simple semaphoring) - Single_TBars_Indicator - One OHLCV TBars in, one TSeries out. - - */ - -public abstract class Pair_TSeries_Indicator : TSeries { - protected readonly int _p; - protected readonly bool _NaN; - protected readonly TSeries _d1; - protected readonly TSeries _d2; - protected readonly double _dd1, _dd2; - - // Chainable Constructors - add them at the end of primary constructors if needed - protected Pair_TSeries_Indicator(TSeries source1, TSeries source2, int period, bool useNaN) { - _p = period; - _NaN = useNaN; - _d1 = source1; - _d2 = source2; - _dd1 = double.NaN; - _dd2 = double.NaN; - _d1.Pub += Sub; - _d2.Pub += Sub; - } - - protected Pair_TSeries_Indicator(TSeries source1, TSeries source2) { - _d1 = source1; - _d2 = source2; - _dd1 = double.NaN; - _dd2 = double.NaN; - _d1.Pub += Sub; - _d2.Pub += Sub; - } - - protected Pair_TSeries_Indicator(TSeries source1, double dd2) { - _d1 = source1; - _d2 = new TSeries(); - _dd1 = double.NaN; - _dd2 = dd2; - _d1.Pub += Sub; - } - - protected Pair_TSeries_Indicator(double dd1, TSeries source2) { - _d1 = new TSeries(); - _d2 = source2; - _dd1 = dd1; - _dd2 = double.NaN; - _d2.Pub += Sub; - } - - // overridable Add(Tvalue, Tvalue) method to add/update a single value at the end of the list - public virtual void Add((DateTime t, double v) TValue1, (DateTime t, double v) TValue2, bool update) { - base.Add((TValue1.t, 0), update); - // default inserts zeros - } - - // potentially overridable Add() bulk variations (could be replaced with faster bulk algos) - public virtual void Add(TSeries d1, TSeries d2) { - for (var i = 0; i < d1.Count; i++) { - Add(d1[i], d2[i], false); - } - } - - public virtual void Add(TSeries d1, double dd2) { - for (var i = 0; i < d1.Count; i++) { - Add(d1[i], (d1[i].t, dd2), false); - } - } - - public virtual void Add(double dd1, TSeries d2) { - for (var i = 0; i < d2.Count; i++) { - Add((d2[i].t, dd1), d2[i], false); - } - } - - public void Add((DateTime t, double v) TValue1, (DateTime t, double v) TValue2) { - Add(TValue1, TValue2, false); - } - - public void Add(bool update) { - if (_dd1 is double.NaN && _dd2 is double.NaN) { - // (Series, Series) - if (update || (_d1.Count > Count && _d2.Count > Count)) { - Add(_d1[_d1.Count - 1], _d2[_d2.Count - 1], update); - } - } - else if (_dd2 is not double.NaN && _dd1 is double.NaN) { - // (Series, Double) - Add(_d1[_d1.Count - 1], (_d1[_d1.Count - 1].t, _dd2), update); - } - else { - // (Double, Series) - Add((_d2[_d2.Count - 1].t, _dd1), _d2[_d2.Count - 1], update); - } - } - - public void Add() { - Add(false); - } - - public new void Sub(object source, TSeriesEventArgs e) { - Add(e.update); - } - - protected static void Add_Replace(List l, double v, bool update) { - if (update) { - l[l.Count - 1] = v; - } - else { - l.Add(v); - } - } - - protected static void Add_Replace_Trim(List l, double v, int p, bool update) { - Add_Replace(l, v, update); - if (l.Count > p && p != 0) { - l.RemoveAt(0); - } - } -} +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +Abstract classes with all scaffolding required to build indicators. + All abstracts support period, NaN, and all permutations of Add() methods. + Indicator classess need to implement: + - Chaining constructor (Abstract's constructor executes first) + - Default Add(value) class + - optional Add(series) bulk insert class (for optimization of historical analysis) + + Single_TSeries_Indicator - one single-value TSeries in, one TSeries out. + Pair_TSeries_Indicator - Two TSeries in, one TSeries out. (includes simple semaphoring) + Single_TBars_Indicator - One OHLCV TBars in, one TSeries out. + + */ + +public abstract class Pair_TSeries_Indicator : TSeries { + protected readonly int _p; + protected readonly bool _NaN; + protected readonly TSeries _d1; + protected readonly TSeries _d2; + protected readonly double _dd1, _dd2; + + // Chainable Constructors - add them at the end of primary constructors if needed + protected Pair_TSeries_Indicator(TSeries source1, TSeries source2, int period, bool useNaN) { + _p = period; + _NaN = useNaN; + _d1 = source1; + _d2 = source2; + _dd1 = double.NaN; + _dd2 = double.NaN; + _d1.Pub += Sub; + _d2.Pub += Sub; + } + + protected Pair_TSeries_Indicator(TSeries source1, TSeries source2) { + _d1 = source1; + _d2 = source2; + _dd1 = double.NaN; + _dd2 = double.NaN; + _d1.Pub += Sub; + _d2.Pub += Sub; + } + + protected Pair_TSeries_Indicator(TSeries source1, double dd2) { + _d1 = source1; + _d2 = new TSeries(); + _dd1 = double.NaN; + _dd2 = dd2; + _d1.Pub += Sub; + } + + protected Pair_TSeries_Indicator(double dd1, TSeries source2) { + _d1 = new TSeries(); + _d2 = source2; + _dd1 = dd1; + _dd2 = double.NaN; + _d2.Pub += Sub; + } + + // overridable Add(Tvalue, Tvalue) method to add/update a single value at the end of the list + public virtual void Add((DateTime t, double v) TValue1, (DateTime t, double v) TValue2, bool update) { + base.Add((TValue1.t, 0), update); + // default inserts zeros + } + + // potentially overridable Add() bulk variations (could be replaced with faster bulk algos) + public virtual void Add(TSeries d1, TSeries d2) { + for (var i = 0; i < d1.Count; i++) { + Add(d1[i], d2[i], false); + } + } + + public virtual void Add(TSeries d1, double dd2) { + for (var i = 0; i < d1.Count; i++) { + Add(d1[i], (d1[i].t, dd2), false); + } + } + + public virtual void Add(double dd1, TSeries d2) { + for (var i = 0; i < d2.Count; i++) { + Add((d2[i].t, dd1), d2[i], false); + } + } + + public void Add((DateTime t, double v) TValue1, (DateTime t, double v) TValue2) { + Add(TValue1, TValue2, false); + } + + public void Add(bool update) { + if (_dd1 is double.NaN && _dd2 is double.NaN) { + // (Series, Series) + if (update || (_d1.Count > Count && _d2.Count > Count)) { + Add(_d1[_d1.Count - 1], _d2[_d2.Count - 1], update); + } + } + else if (_dd2 is not double.NaN && _dd1 is double.NaN) { + // (Series, Double) + Add(_d1[_d1.Count - 1], (_d1[_d1.Count - 1].t, _dd2), update); + } + else { + // (Double, Series) + Add((_d2[_d2.Count - 1].t, _dd1), _d2[_d2.Count - 1], update); + } + } + + public void Add() { + Add(false); + } + + public new void Sub(object source, TSeriesEventArgs e) { + Add(e.update); + } + + protected static void Add_Replace(List l, double v, bool update) { + if (update) { + l[l.Count - 1] = v; + } + else { + l.Add(v); + } + } + + protected static void Add_Replace_Trim(List l, double v, int p, bool update) { + Add_Replace(l, v, update); + if (l.Count > p && p != 0) { + l.RemoveAt(0); + } + } +} diff --git a/Calculations/Logic/CROSS_Series.cs b/Calculations/Logic/CROSS_Series.cs index aab93397..2c4cad1c 100644 --- a/Calculations/Logic/CROSS_Series.cs +++ b/Calculations/Logic/CROSS_Series.cs @@ -1,44 +1,44 @@ -namespace QuanTAlib; -using System; - -/* -OVER - Generates +1 if A is above B, -1 if A is below B and 0 if A=B - -Remarks: - OVER.Cross generates 1 when A breaks B from below and -1 when A breaks B from above - - */ - -public class CROSS_Series : Pair_TSeries_Indicator { - public TSeries Cross { get; set; } = new(); - - private double _previous = double.NaN; - public CROSS_Series(TSeries d1, TSeries d2) : base(d1, d2) { - if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } - } - public CROSS_Series(TSeries d1, double dd2) : base(d1, dd2) { - if (base._d1.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } - } - public CROSS_Series(double dd1, TSeries d2) : base(dd1, d2) { - if (base._d2.Count > 0) { for (int i = 0; i < base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } - } - - public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) { - - double val = TValue1.v > TValue2.v ? 1 : -1; - val = TValue1.v == TValue2.v ? 0 : val; - double over = TValue1.v > TValue2.v ? 1 : val; - - val = (_previous < over) ? 1 : -1; - val = ((_previous == over) || Double.IsNaN(this._previous) || (this._previous == 0)) ? 0 : val; - (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t,val); - - this._previous = over; - - if (update) { base[^1] = result; } - else { base.Add(result); } - - } -} - - +namespace QuanTAlib; +using System; + +/* +OVER - Generates +1 if A is above B, -1 if A is below B and 0 if A=B + +Remarks: + OVER.Cross generates 1 when A breaks B from below and -1 when A breaks B from above + + */ + +public class CROSS_Series : Pair_TSeries_Indicator { + public TSeries Cross { get; set; } = new(); + + private double _previous = double.NaN; + public CROSS_Series(TSeries d1, TSeries d2) : base(d1, d2) { + if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } + } + public CROSS_Series(TSeries d1, double dd2) : base(d1, dd2) { + if (base._d1.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } + } + public CROSS_Series(double dd1, TSeries d2) : base(dd1, d2) { + if (base._d2.Count > 0) { for (int i = 0; i < base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } + } + + public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) { + + double val = TValue1.v > TValue2.v ? 1 : -1; + val = TValue1.v == TValue2.v ? 0 : val; + double over = TValue1.v > TValue2.v ? 1 : val; + + val = (_previous < over) ? 1 : -1; + val = ((_previous == over) || Double.IsNaN(this._previous) || (this._previous == 0)) ? 0 : val; + (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t,val); + + this._previous = over; + + if (update) { base[^1] = result; } + else { base.Add(result); } + + } +} + + diff --git a/Calculations/Logic/EQUITY_Series.cs b/Calculations/Logic/EQUITY_Series.cs index 8323feb7..36a97667 100644 --- a/Calculations/Logic/EQUITY_Series.cs +++ b/Calculations/Logic/EQUITY_Series.cs @@ -1,91 +1,91 @@ -namespace QuanTAlib; -using System; - -/* -EQUITY - Generates P&L portfolio based on trades signals and equity prices - - */ - - -//base prices: bars.close -//trade signals: trades -//optional: long, short, long&short -//optional: warmup period: warmup - -/* - -public class EQUITY_Series : Single_TSeries_Indicator { - readonly TSeries inmarket; //for every bar - private readonly TSeries _price; - private double _equity; - private readonly double _capital; - - readonly int _warmup; - double _cash; - int _units; - private bool _longbuy, _longsell; - double _long_order, _open_order; - double _investment_value; - short _inmarket; - - public EQUITY_Series(TSeries signal, TSeries price, int warmup = 0, double capital = 1000) : base(signal, period: 0, useNaN: false) { - _capital = capital; - _cash = _capital; - _investment_value = 0; - _warmup = (warmup > 0) ? warmup : 1; - - inmarket = new(); - _longbuy = _longsell = false; - _open_order = 0; - _inmarket = 0; - _units = 0; - _long_order = 0; - - _price = price; //we buy on the Open price of the NEXT bar - _long_order = 0; - - if (base._data.Count > 0) { base.Add(base._data); } - } - - public override void Add((System.DateTime t, double v) TValue, bool update) { - - if (this.Count > _warmup) { - - // harvest the gain-loss from previous day - _investment_value = _units * _price[this.Count - 1].v; - _equity = _cash + _investment_value; - - - //execute orders from previous bar - if (_longbuy && _inmarket == 0) { //time to execute the long buy - _units = (int)(_cash / _price[this.Count - 1].v); - _long_order = _units * _price[this.Count - 1].v; - _cash -= _long_order; - _open_order = _long_order; - _equity = _cash + _open_order; - _inmarket = 1; - _longbuy = false; - } - - if (_longsell && _inmarket == 1) { //time to execute the long sell - _long_order = (_units * _price[this.Count - 1].v); - _cash += _long_order; - _units = 0; - - _open_order = 0; - _equity = _cash + _open_order; - _inmarket = 0; - _longsell = false; - } - - if (_inmarket == 0 && TValue.v == 1) { _longbuy = true; } //out of market, enter long - if (_inmarket == 1 && TValue.v == -1) { _longsell = true; } //long market, exit long - - //Console.WriteLine($"{TValue.v,3}\t {(_inmarket)} : {_cash,10:f2} + {_units*_price[^1].v,7:f2} = {_equity-_capital:f2}"); - } - inmarket.Add((TValue.t, (double)_inmarket)); - base.Add((TValue.t, _equity), update, _NaN); - } -} - +namespace QuanTAlib; +using System; + +/* +EQUITY - Generates P&L portfolio based on trades signals and equity prices + + */ + + +//base prices: bars.close +//trade signals: trades +//optional: long, short, long&short +//optional: warmup period: warmup + +/* + +public class EQUITY_Series : Single_TSeries_Indicator { + readonly TSeries inmarket; //for every bar + private readonly TSeries _price; + private double _equity; + private readonly double _capital; + + readonly int _warmup; + double _cash; + int _units; + private bool _longbuy, _longsell; + double _long_order, _open_order; + double _investment_value; + short _inmarket; + + public EQUITY_Series(TSeries signal, TSeries price, int warmup = 0, double capital = 1000) : base(signal, period: 0, useNaN: false) { + _capital = capital; + _cash = _capital; + _investment_value = 0; + _warmup = (warmup > 0) ? warmup : 1; + + inmarket = new(); + _longbuy = _longsell = false; + _open_order = 0; + _inmarket = 0; + _units = 0; + _long_order = 0; + + _price = price; //we buy on the Open price of the NEXT bar + _long_order = 0; + + if (base._data.Count > 0) { base.Add(base._data); } + } + + public override void Add((System.DateTime t, double v) TValue, bool update) { + + if (this.Count > _warmup) { + + // harvest the gain-loss from previous day + _investment_value = _units * _price[this.Count - 1].v; + _equity = _cash + _investment_value; + + + //execute orders from previous bar + if (_longbuy && _inmarket == 0) { //time to execute the long buy + _units = (int)(_cash / _price[this.Count - 1].v); + _long_order = _units * _price[this.Count - 1].v; + _cash -= _long_order; + _open_order = _long_order; + _equity = _cash + _open_order; + _inmarket = 1; + _longbuy = false; + } + + if (_longsell && _inmarket == 1) { //time to execute the long sell + _long_order = (_units * _price[this.Count - 1].v); + _cash += _long_order; + _units = 0; + + _open_order = 0; + _equity = _cash + _open_order; + _inmarket = 0; + _longsell = false; + } + + if (_inmarket == 0 && TValue.v == 1) { _longbuy = true; } //out of market, enter long + if (_inmarket == 1 && TValue.v == -1) { _longsell = true; } //long market, exit long + + //Console.WriteLine($"{TValue.v,3}\t {(_inmarket)} : {_cash,10:f2} + {_units*_price[^1].v,7:f2} = {_equity-_capital:f2}"); + } + inmarket.Add((TValue.t, (double)_inmarket)); + base.Add((TValue.t, _equity), update, _NaN); + } +} + */ \ No newline at end of file diff --git a/Calculations/Logic/TOrders.cs b/Calculations/Logic/TOrders.cs index bb1a1e71..04d5ffa3 100644 --- a/Calculations/Logic/TOrders.cs +++ b/Calculations/Logic/TOrders.cs @@ -1,34 +1,34 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Data; -using System.Linq; - - -public enum OType { - NIL = 0, // No position - BTO = 1, // Buy to Open - STC = 2, // Sell to Close - STO = 3, // Sell to Open - BTC = 4, // Buy to Close - END = 5, // Exit the trade -} - - -public class TOrders : List<(DateTime t, OType o)> { - - public void Add((DateTime t, OType o) TOrder, bool update = false) - { - if (update) { this[^1] = TOrder; } - else { base.Add(TOrder); } - OnEvent(update); - } - - - protected virtual void OnEvent(bool update = false) { - Pub?.Invoke(this, new TSeriesEventArgs { update = update }); } - public delegate void NewDataEventHandler(object source, TSeriesEventArgs args); - public event NewDataEventHandler Pub; - +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.Linq; + + +public enum OType { + NIL = 0, // No position + BTO = 1, // Buy to Open + STC = 2, // Sell to Close + STO = 3, // Sell to Open + BTC = 4, // Buy to Close + END = 5, // Exit the trade +} + + +public class TOrders : List<(DateTime t, OType o)> { + + public void Add((DateTime t, OType o) TOrder, bool update = false) + { + if (update) { this[^1] = TOrder; } + else { base.Add(TOrder); } + OnEvent(update); + } + + + protected virtual void OnEvent(bool update = false) { + Pub?.Invoke(this, new TSeriesEventArgs { update = update }); } + public delegate void NewDataEventHandler(object source, TSeriesEventArgs args); + public event NewDataEventHandler Pub; + } \ No newline at end of file diff --git a/Calculations/_Updated/ADL_Series.cs b/Calculations/_Updated/ADL_Series.cs index aa33c7b2..2222887e 100644 --- a/Calculations/_Updated/ADL_Series.cs +++ b/Calculations/_Updated/ADL_Series.cs @@ -1,69 +1,69 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -ADL: Chaikin Accumulation/Distribution Line - ADL is a volume-based indicator that measures the cumulative Money Flow Volume: - - 1. Money Flow Multiplier = [(Close - Low) - (High - Close)] /(High - Low) - 2. Money Flow Volume = Money Flow Multiplier x Volume for the Period - 3. ADL = Previous ADL + Current Period's Money Flow Volume - -Sources: - https://school.stockcharts.com/doku.php?id=technical_indicators:accumulation_distribution_line - - */ - -public class ADL_Series : TSeries { - protected readonly TBars _data; - private double _lastadl, _lastlastadl; - - //core constructors - public ADL_Series() { - Name = $"ADL()"; - _lastadl = _lastlastadl = 0; - } - public ADL_Series(TBars source) { - _data = source; - Name = $"ADL({(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _lastadl = _lastlastadl = 0; - _data.Pub += Sub; - Add(data: _data); - } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - if (update) { this._lastadl = this._lastlastadl; } - else { this._lastlastadl = this._lastadl; } - - double _adl = 0; - double tmp = TBar.h - TBar.l; - if (tmp > 0.0) { - _adl = _lastadl + ((2 * TBar.c - TBar.l - TBar.h) / tmp * TBar.v); - } - _lastadl = _adl; - - var ret = (TBar.t, _adl); - return base.Add(ret, update); - } - - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _lastadl = _lastlastadl = 0; - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +ADL: Chaikin Accumulation/Distribution Line + ADL is a volume-based indicator that measures the cumulative Money Flow Volume: + + 1. Money Flow Multiplier = [(Close - Low) - (High - Close)] /(High - Low) + 2. Money Flow Volume = Money Flow Multiplier x Volume for the Period + 3. ADL = Previous ADL + Current Period's Money Flow Volume + +Sources: + https://school.stockcharts.com/doku.php?id=technical_indicators:accumulation_distribution_line + + */ + +public class ADL_Series : TSeries { + protected readonly TBars _data; + private double _lastadl, _lastlastadl; + + //core constructors + public ADL_Series() { + Name = $"ADL()"; + _lastadl = _lastlastadl = 0; + } + public ADL_Series(TBars source) { + _data = source; + Name = $"ADL({(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _lastadl = _lastlastadl = 0; + _data.Pub += Sub; + Add(data: _data); + } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { + if (update) { this._lastadl = this._lastlastadl; } + else { this._lastlastadl = this._lastadl; } + + double _adl = 0; + double tmp = TBar.h - TBar.l; + if (tmp > 0.0) { + _adl = _lastadl + ((2 * TBar.c - TBar.l - TBar.h) / tmp * TBar.v); + } + _lastadl = _adl; + + var ret = (TBar.t, _adl); + return base.Add(ret, update); + } + + public new void Add(TBars data) { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TBar: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _lastadl = _lastlastadl = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/ADOSC_Series.cs b/Calculations/_Updated/ADOSC_Series.cs index 63aa2104..4c943b07 100644 --- a/Calculations/_Updated/ADOSC_Series.cs +++ b/Calculations/_Updated/ADOSC_Series.cs @@ -1,90 +1,90 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -ADOSC: Chaikin Accumulation/Distribution Oscillator - ADO measures the momentum of ADL using the difference between slow (10-day) EMA(ADL) - and fast (3-day) EMA(ADL): - - Chaikin A/D Oscillator is defined as 3-day EMA of ADL minus 10-day EMA of ADL - -Sources: - https://school.stockcharts.com/doku.php?id=technical_indicators:chaikin_oscillator - - */ - -public class ADOSC_Series : TSeries { - protected readonly TBars _data; - private readonly double _k1, _k2; - private double _lastema1, _lastlastema1, _lastema2, _lastlastema2; - private double _lastadl, _lastlastadl; - - //core constructors - public ADOSC_Series(int shortPeriod, int longPeriod, bool useNaN = false) { - Name = $"ADOSC()"; - _k1 = 2.0 / (shortPeriod + 1); - _k2 = 2.0 / (longPeriod + 1); - _lastadl = _lastlastadl = _lastema1 = _lastlastema1 = _lastema2 = _lastlastema2 = 0; - } - public ADOSC_Series(TBars source, int shortPeriod, int longPeriod, bool useNaN = false) :this(shortPeriod, longPeriod, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _lastadl = _lastlastadl = 0; - _data.Pub += Sub; - Add(data: _data); - } - - public ADOSC_Series() : this(shortPeriod: 3, longPeriod: 10, useNaN: false) {} - - public ADOSC_Series(TBars source) : this(source, shortPeriod: 3, longPeriod:10, useNaN:false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update= false) { - - if (update) { - _lastadl = _lastlastadl; - _lastema1 = _lastlastema1; - _lastema2 = _lastlastema2; - } - - double _adl = 0; - double tmp = TBar.h - TBar.l; - if (tmp > 0.0) { _adl = _lastadl + ((2 * TBar.c - TBar.l - TBar.h) / tmp * TBar.v); } - if (this.Count == 0) { _lastema1 = _lastema2 = _adl; } - - double _ema1 = (_adl - _lastema1) * _k1 + _lastema1; - double _ema2 = (_adl - _lastema2) * _k2 + _lastema2; - - _lastlastadl = _lastadl; - _lastadl = _adl; - _lastlastema1 = _lastema1; - _lastema1 = _ema1; - _lastlastema2 = _lastema2; - _lastema2 = _ema2; - - double _adosc = _ema1 - _ema2; - - var ret = (TBar.t, _adosc); - return base.Add(ret, update); - } - - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _lastadl = _lastlastadl = _lastema1 = _lastlastema1 = _lastema2 = _lastlastema2 = 0; - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +ADOSC: Chaikin Accumulation/Distribution Oscillator + ADO measures the momentum of ADL using the difference between slow (10-day) EMA(ADL) + and fast (3-day) EMA(ADL): + + Chaikin A/D Oscillator is defined as 3-day EMA of ADL minus 10-day EMA of ADL + +Sources: + https://school.stockcharts.com/doku.php?id=technical_indicators:chaikin_oscillator + + */ + +public class ADOSC_Series : TSeries { + protected readonly TBars _data; + private readonly double _k1, _k2; + private double _lastema1, _lastlastema1, _lastema2, _lastlastema2; + private double _lastadl, _lastlastadl; + + //core constructors + public ADOSC_Series(int shortPeriod, int longPeriod, bool useNaN = false) { + Name = $"ADOSC()"; + _k1 = 2.0 / (shortPeriod + 1); + _k2 = 2.0 / (longPeriod + 1); + _lastadl = _lastlastadl = _lastema1 = _lastlastema1 = _lastema2 = _lastlastema2 = 0; + } + public ADOSC_Series(TBars source, int shortPeriod, int longPeriod, bool useNaN = false) :this(shortPeriod, longPeriod, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _lastadl = _lastlastadl = 0; + _data.Pub += Sub; + Add(data: _data); + } + + public ADOSC_Series() : this(shortPeriod: 3, longPeriod: 10, useNaN: false) {} + + public ADOSC_Series(TBars source) : this(source, shortPeriod: 3, longPeriod:10, useNaN:false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update= false) { + + if (update) { + _lastadl = _lastlastadl; + _lastema1 = _lastlastema1; + _lastema2 = _lastlastema2; + } + + double _adl = 0; + double tmp = TBar.h - TBar.l; + if (tmp > 0.0) { _adl = _lastadl + ((2 * TBar.c - TBar.l - TBar.h) / tmp * TBar.v); } + if (this.Count == 0) { _lastema1 = _lastema2 = _adl; } + + double _ema1 = (_adl - _lastema1) * _k1 + _lastema1; + double _ema2 = (_adl - _lastema2) * _k2 + _lastema2; + + _lastlastadl = _lastadl; + _lastadl = _adl; + _lastlastema1 = _lastema1; + _lastema1 = _ema1; + _lastlastema2 = _lastema2; + _lastema2 = _ema2; + + double _adosc = _ema1 - _ema2; + + var ret = (TBar.t, _adosc); + return base.Add(ret, update); + } + + public new void Add(TBars data) { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TBar: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _lastadl = _lastlastadl = _lastema1 = _lastlastema1 = _lastema2 = _lastlastema2 = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/ALMA_Series.cs b/Calculations/_Updated/ALMA_Series.cs index 514e0e34..e4c8c3de 100644 --- a/Calculations/_Updated/ALMA_Series.cs +++ b/Calculations/_Updated/ALMA_Series.cs @@ -1,114 +1,114 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Linq; - -/* -ALMA: Arnaud Legoux Moving Average - The ALMA moving average uses the curve of the Normal (Gauss) distribution, which - can be shifted from 0 to 1. This allows regulating the smoothness and high - sensitivity of the indicator. Sigma is another parameter that is responsible for - the shape of the curve coefficients. This moving average reduces lag of the data - in conjunction with smoothing to reduce noise. - - -Sources: - https://phemex.com/academy/what-is-arnaud-legoux-moving-averages - https://www.prorealcode.com/prorealtime-indicators/alma-arnaud-legoux-moving-average/ - - Discrepancy with Pandas-TA (but passes the validation with Skender.GetAlma) - */ - -public class ALMA_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - private readonly System.Collections.Generic.List _buffer = new(); - private readonly System.Collections.Generic.List _weight; - private double _norm; - private readonly double _offset, _sigma; - - //core constructors - public ALMA_Series(int period, double offset, double sigma, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"ALMA({period})"; - _offset = offset; - _sigma = sigma; - _weight = new(); - } - public ALMA_Series(TSeries source, int period, double offset, double sigma, bool useNaN) : this(period, offset, sigma, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - - public ALMA_Series() : this(period:0, offset:0.85, sigma:6.0, useNaN: false) { } - public ALMA_Series(int period) : this(period: period, offset:0.85, sigma:6.0, useNaN:false) { } - public ALMA_Series(TBars source) : this(source:source.Close, period:0, offset:0.85, sigma:6.0, useNaN:false) { } - public ALMA_Series(TBars source, int period) : this(source:source.Close, period:period, offset: 0.85, sigma: 6.0, useNaN: false) { } - public ALMA_Series(TBars source, int period, double offset, double sigma, bool useNaN) : this(source.Close, period:period, offset: offset, sigma: sigma, useNaN: false) { } - public ALMA_Series(TSeries source) : this(source, period:0, offset:0.85, sigma:6.0, useNaN:false) { } - public ALMA_Series(TSeries source, int period) : this(source:source, period:period, offset:0.85, sigma:6.0, useNaN:false) { } - public ALMA_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, offset: 0.85, sigma: 6.0, useNaN: useNaN) { } - - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, double.NaN), update); - } - - BufferTrim(_buffer, TValue.v, _period, update); - if (_weight.Count < _buffer.Count) { - for (var i = 0; i < _buffer.Count - _weight.Count; i++) { - _weight.Add(0.0); - } - } - - - if (_buffer.Count <= _period || _period == 0) { - var _len = _buffer.Count; - _norm = 0; - var _m = _offset * (_len - 1); - var _s = _len / _sigma; - for (var i = 0; i < _len; i++) { - var _wt = Math.Exp(-((i - _m) * (i - _m)) / (2 * _s * _s)); - _weight[i] = _wt; - _norm += _wt; - } - } - - double _weightedSum = 0; - for (var i = 0; i < _buffer.Count; i++) { - _weightedSum += _weight[i] * _buffer[i]; - } - - var _alma = _weightedSum / _norm; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _alma); - return base.Add(res, update); - } - - //variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - //reset calculation - public override void Reset() { - _buffer.Clear(); - _weight.Clear(); - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Linq; + +/* +ALMA: Arnaud Legoux Moving Average + The ALMA moving average uses the curve of the Normal (Gauss) distribution, which + can be shifted from 0 to 1. This allows regulating the smoothness and high + sensitivity of the indicator. Sigma is another parameter that is responsible for + the shape of the curve coefficients. This moving average reduces lag of the data + in conjunction with smoothing to reduce noise. + + +Sources: + https://phemex.com/academy/what-is-arnaud-legoux-moving-averages + https://www.prorealcode.com/prorealtime-indicators/alma-arnaud-legoux-moving-average/ + + Discrepancy with Pandas-TA (but passes the validation with Skender.GetAlma) + */ + +public class ALMA_Series : TSeries { + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + private readonly System.Collections.Generic.List _buffer = new(); + private readonly System.Collections.Generic.List _weight; + private double _norm; + private readonly double _offset, _sigma; + + //core constructors + public ALMA_Series(int period, double offset, double sigma, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"ALMA({period})"; + _offset = offset; + _sigma = sigma; + _weight = new(); + } + public ALMA_Series(TSeries source, int period, double offset, double sigma, bool useNaN) : this(period, offset, sigma, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + + public ALMA_Series() : this(period:0, offset:0.85, sigma:6.0, useNaN: false) { } + public ALMA_Series(int period) : this(period: period, offset:0.85, sigma:6.0, useNaN:false) { } + public ALMA_Series(TBars source) : this(source:source.Close, period:0, offset:0.85, sigma:6.0, useNaN:false) { } + public ALMA_Series(TBars source, int period) : this(source:source.Close, period:period, offset: 0.85, sigma: 6.0, useNaN: false) { } + public ALMA_Series(TBars source, int period, double offset, double sigma, bool useNaN) : this(source.Close, period:period, offset: offset, sigma: sigma, useNaN: false) { } + public ALMA_Series(TSeries source) : this(source, period:0, offset:0.85, sigma:6.0, useNaN:false) { } + public ALMA_Series(TSeries source, int period) : this(source:source, period:period, offset:0.85, sigma:6.0, useNaN:false) { } + public ALMA_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, offset: 0.85, sigma: 6.0, useNaN: useNaN) { } + + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (double.IsNaN(TValue.v)) { + return base.Add((TValue.t, double.NaN), update); + } + + BufferTrim(_buffer, TValue.v, _period, update); + if (_weight.Count < _buffer.Count) { + for (var i = 0; i < _buffer.Count - _weight.Count; i++) { + _weight.Add(0.0); + } + } + + + if (_buffer.Count <= _period || _period == 0) { + var _len = _buffer.Count; + _norm = 0; + var _m = _offset * (_len - 1); + var _s = _len / _sigma; + for (var i = 0; i < _len; i++) { + var _wt = Math.Exp(-((i - _m) * (i - _m)) / (2 * _s * _s)); + _weight[i] = _wt; + _norm += _wt; + } + } + + double _weightedSum = 0; + for (var i = 0; i < _buffer.Count; i++) { + _weightedSum += _weight[i] * _buffer[i]; + } + + var _alma = _weightedSum / _norm; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _alma); + return base.Add(res, update); + } + + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + //reset calculation + public override void Reset() { + _buffer.Clear(); + _weight.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/ATRP_Series.cs b/Calculations/_Updated/ATRP_Series.cs index 3ba2c1dc..7f4cf4c7 100644 --- a/Calculations/_Updated/ATRP_Series.cs +++ b/Calculations/_Updated/ATRP_Series.cs @@ -1,87 +1,87 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -ATRP: Average True Range Percent - Average True Range Percent is (ATR/Close Price)*100. - This normalizes so it can be compared to other stocks. - -Sources: - https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/atrp - - */ - -public class ATRP_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TBars _data; - private double _k; - private int _len; - private double _lastatr, _lastlastatr, _cm1, _lastcm1, _sum, _oldsum; - - //core constructors - public ATRP_Series(int period, bool useNaN) { - _period = period; - _k = 1.0 / (double)(_period); - _NaN = useNaN; - _len = 0; - Name = $"ATRP({period})"; - } - public ATRP_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(data: _data); - } - public ATRP_Series() : this(period: 1, useNaN: false) { } - public ATRP_Series(int period) : this(period: period, useNaN: false) { } - public ATRP_Series(TBars source) : this(source, period: 1, useNaN: false) { } - public ATRP_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - if (update) { _lastatr = _lastlastatr; _cm1 = _lastcm1; _sum = _oldsum; } - else { - _lastlastatr = _lastatr; _lastcm1 = _cm1; _oldsum = _sum; - _k = (_period == 0) ? 1 / (double)_len : _k; - _len++; - } - - if (_len == 1) { _cm1 = TBar.c; } - double d1 = Math.Abs(TBar.h - TBar.l); - double d2 = Math.Abs(_cm1 - TBar.h); - double d3 = Math.Abs(_cm1 - TBar.l); - (DateTime t, double v) d = (TBar.t, Math.Max(d1, Math.Max(d2, d3))); - _cm1 = TBar.c; - - double _atr = 0; - if (this.Count == 0) { _atr = d.v; } - else if (this.Count < _period + 1) { _sum += d.v; _atr = _sum / (this.Count); } - else { _atr = _k * (d.v - _lastatr) + _lastatr; } - _lastatr = _atr; - double _atrp = 100 * (_atr / TBar.c); - - var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _atrp); - return base.Add(res, update); - } - - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _len = 0; - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +ATRP: Average True Range Percent + Average True Range Percent is (ATR/Close Price)*100. + This normalizes so it can be compared to other stocks. + +Sources: + https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/atrp + + */ + +public class ATRP_Series : TSeries { + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TBars _data; + private double _k; + private int _len; + private double _lastatr, _lastlastatr, _cm1, _lastcm1, _sum, _oldsum; + + //core constructors + public ATRP_Series(int period, bool useNaN) { + _period = period; + _k = 1.0 / (double)(_period); + _NaN = useNaN; + _len = 0; + Name = $"ATRP({period})"; + } + public ATRP_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(data: _data); + } + public ATRP_Series() : this(period: 1, useNaN: false) { } + public ATRP_Series(int period) : this(period: period, useNaN: false) { } + public ATRP_Series(TBars source) : this(source, period: 1, useNaN: false) { } + public ATRP_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { + if (update) { _lastatr = _lastlastatr; _cm1 = _lastcm1; _sum = _oldsum; } + else { + _lastlastatr = _lastatr; _lastcm1 = _cm1; _oldsum = _sum; + _k = (_period == 0) ? 1 / (double)_len : _k; + _len++; + } + + if (_len == 1) { _cm1 = TBar.c; } + double d1 = Math.Abs(TBar.h - TBar.l); + double d2 = Math.Abs(_cm1 - TBar.h); + double d3 = Math.Abs(_cm1 - TBar.l); + (DateTime t, double v) d = (TBar.t, Math.Max(d1, Math.Max(d2, d3))); + _cm1 = TBar.c; + + double _atr = 0; + if (this.Count == 0) { _atr = d.v; } + else if (this.Count < _period + 1) { _sum += d.v; _atr = _sum / (this.Count); } + else { _atr = _k * (d.v - _lastatr) + _lastatr; } + _lastatr = _atr; + double _atrp = 100 * (_atr / TBar.c); + + var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _atrp); + return base.Add(res, update); + } + + public new void Add(TBars data) { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TBar: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/ATR_Series.cs b/Calculations/_Updated/ATR_Series.cs index 9935f5c4..be22668d 100644 --- a/Calculations/_Updated/ATR_Series.cs +++ b/Calculations/_Updated/ATR_Series.cs @@ -1,88 +1,88 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -ATR: wildeR Moving Average - The average true range (ATR) is a price volatility indicator - showing the average price variation of assets within a given time period. - -Sources: - https://en.wikipedia.org/wiki/Average_true_range - https://www.tradingview.com/wiki/Average_True_Range_(ATR) - https://www.investopedia.com/terms/a/atr.asp - - */ - -public class ATR_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TBars _data; - private double _k; - private int _len; - private double _lastatr, _lastlastatr, _cm1, _lastcm1, _sum, _oldsum; - - //core constructors - public ATR_Series(int period, bool useNaN) { - _period = period; - _k = 1.0 / (double)(_period); - _NaN = useNaN; - _len = 0; - Name = $"ATR({period})"; - } - public ATR_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(data: _data); - } - public ATR_Series() : this(period: 1, useNaN: false) { } - public ATR_Series(int period) : this(period: period, useNaN: false) { } - public ATR_Series(TBars source) : this(source, period: 1, useNaN: false) { } - public ATR_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - if (update) { _lastatr = _lastlastatr; _cm1 = _lastcm1; _sum = _oldsum; } - else { - _lastlastatr = _lastatr; _lastcm1 = _cm1; _oldsum = _sum; - _k = (_period == 0) ? 1 / (double)_len : _k; - _len++; - } - - if (_len == 1) { _cm1 = TBar.c; } - double d1 = Math.Abs(TBar.h - TBar.l); - double d2 = Math.Abs(_cm1 - TBar.h); - double d3 = Math.Abs(_cm1 - TBar.l); - (DateTime t, double v) d = (TBar.t, Math.Max(d1, Math.Max(d2, d3))); - _cm1 = TBar.c; - - double _atr = 0; - if (this.Count == 0) { _atr = d.v; } - else if (this.Count < _period + 1) { _sum += d.v; _atr = _sum / (this.Count); } - else { _atr = _k * (d.v - _lastatr) + _lastatr; } - _lastatr = _atr; - - var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _atr); - return base.Add(res, update); - } - - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _len = 0; - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +ATR: wildeR Moving Average + The average true range (ATR) is a price volatility indicator + showing the average price variation of assets within a given time period. + +Sources: + https://en.wikipedia.org/wiki/Average_true_range + https://www.tradingview.com/wiki/Average_True_Range_(ATR) + https://www.investopedia.com/terms/a/atr.asp + + */ + +public class ATR_Series : TSeries { + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TBars _data; + private double _k; + private int _len; + private double _lastatr, _lastlastatr, _cm1, _lastcm1, _sum, _oldsum; + + //core constructors + public ATR_Series(int period, bool useNaN) { + _period = period; + _k = 1.0 / (double)(_period); + _NaN = useNaN; + _len = 0; + Name = $"ATR({period})"; + } + public ATR_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(data: _data); + } + public ATR_Series() : this(period: 1, useNaN: false) { } + public ATR_Series(int period) : this(period: period, useNaN: false) { } + public ATR_Series(TBars source) : this(source, period: 1, useNaN: false) { } + public ATR_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { + if (update) { _lastatr = _lastlastatr; _cm1 = _lastcm1; _sum = _oldsum; } + else { + _lastlastatr = _lastatr; _lastcm1 = _cm1; _oldsum = _sum; + _k = (_period == 0) ? 1 / (double)_len : _k; + _len++; + } + + if (_len == 1) { _cm1 = TBar.c; } + double d1 = Math.Abs(TBar.h - TBar.l); + double d2 = Math.Abs(_cm1 - TBar.h); + double d3 = Math.Abs(_cm1 - TBar.l); + (DateTime t, double v) d = (TBar.t, Math.Max(d1, Math.Max(d2, d3))); + _cm1 = TBar.c; + + double _atr = 0; + if (this.Count == 0) { _atr = d.v; } + else if (this.Count < _period + 1) { _sum += d.v; _atr = _sum / (this.Count); } + else { _atr = _k * (d.v - _lastatr) + _lastatr; } + _lastatr = _atr; + + var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _atr); + return base.Add(res, update); + } + + public new void Add(TBars data) { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TBar: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/BBANDS_Series.cs b/Calculations/_Updated/BBANDS_Series.cs index 198b2a38..7cc5a3e0 100644 --- a/Calculations/_Updated/BBANDS_Series.cs +++ b/Calculations/_Updated/BBANDS_Series.cs @@ -1,112 +1,112 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Linq; - -/* -BBANDS: Bollinger Bands® - Price channels created by John Bollinger, depict volatility as standard deviation boundary - line range from a moving average of price. The bands automatically widen when volatility - increases and contract when volatility decreases. Their dynamic nature allows them to be - used on different securities with the standard settings. - - Mid Band = simple moving average (SMA) - Upper Band = SMA + (standard deviation of price x multiplier) - Lower Band = SMA - (standard deviation of price x multiplier) - Bandwidth = Width of the channel: (Upper-Lower)/SMA - %B = The location of the data point within the channel: (Price-Lower)/(Upper/Lower) - Z-Score = number of standard deviations of the data point from SMA - -Sources: - https://www.investopedia.com/terms/b/bollingerbands.asp - https://school.stockcharts.com/doku.php?id=technical_indicators:bollinger_bands - -Note: - Bollinger Bands® is a registered trademark of John A. Bollinger. - - */ - -public class BBANDS_Series : TSeries { - protected readonly int _period; - protected readonly double _multiplier; - protected readonly bool _NaN; - protected readonly TSeries _data; - public SMA_Series Mid { get; } - public TSeries Upper { get; } - public TSeries Lower { get; } - public TSeries PercentB { get; } - public TSeries Bandwidth { get; } - public TSeries Zscore { get; } - private readonly SDEV_Series _sdev; - - //core constructors - public BBANDS_Series(int period, double multiplier, bool useNaN) { - _period = period; - _multiplier = multiplier; - _NaN = useNaN; - Name = $"BBANDS({period})"; - } - public BBANDS_Series(TSeries source, int period, double multiplier, bool useNaN) : this(period, multiplier, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - Upper = new("BB_Up"); - Lower = new("BB_Low"); - Bandwidth = new("BBandwidth"); - PercentB = new("%BBandwidth"); - Zscore = new("Zscore"); - - Mid = new(period, false); - _sdev = new(period, false); - - _data.Pub += Sub; - Add(_data); - } - - public BBANDS_Series() : this(period:0, multiplier: 2.0, useNaN: false) { } - public BBANDS_Series(int period) : this(period: period, multiplier: 2.0, useNaN:false) { } - public BBANDS_Series(TBars source) : this(source:source.Close, period:0, multiplier: 2.0, useNaN:false) { } - public BBANDS_Series(TBars source, int period) : this(source:source.Close, period:period, multiplier: 2.0, useNaN: false) { } - public BBANDS_Series(TBars source, int period, double multiplier, bool useNaN) : this(source.Close, period:period, multiplier:multiplier, useNaN: false) { } - public BBANDS_Series(TSeries source) : this(source, period:0, useNaN:false) { } - public BBANDS_Series(TSeries source, int period) : this(source:source, period:period, useNaN:false) { } - public BBANDS_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, multiplier: 2.0, useNaN: useNaN) { } - - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update=false) { - var _mid = Mid.Add(TValue,update); - var _sd = this._sdev.Add(TValue, update); - var _upper = Upper.Add((TValue.t, _mid.v + _sd.v * _multiplier), update); - var _lower = Lower.Add((TValue.t, _mid.v - _sd.v * _multiplier), update); - double _pbdnd = TValue.v - _lower.v; - double _pbdvr = _upper.v - _lower.v; - PercentB.Add((TValue.t, _pbdnd/_pbdvr), update); - Zscore.Add((TValue.t, (TValue.v-_mid.v)/_sd.v), update); - Bandwidth.Add((TValue.t, _pbdvr / _mid.v), update); - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _pbdvr / _mid.v); - return base.Add(res, update); - } - - //variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - //reset calculation - public override void Reset() { - Mid.Clear(); - _sdev.Clear(); - Upper.Clear(); - Lower.Clear(); - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Linq; + +/* +BBANDS: Bollinger Bands® + Price channels created by John Bollinger, depict volatility as standard deviation boundary + line range from a moving average of price. The bands automatically widen when volatility + increases and contract when volatility decreases. Their dynamic nature allows them to be + used on different securities with the standard settings. + + Mid Band = simple moving average (SMA) + Upper Band = SMA + (standard deviation of price x multiplier) + Lower Band = SMA - (standard deviation of price x multiplier) + Bandwidth = Width of the channel: (Upper-Lower)/SMA + %B = The location of the data point within the channel: (Price-Lower)/(Upper/Lower) + Z-Score = number of standard deviations of the data point from SMA + +Sources: + https://www.investopedia.com/terms/b/bollingerbands.asp + https://school.stockcharts.com/doku.php?id=technical_indicators:bollinger_bands + +Note: + Bollinger Bands® is a registered trademark of John A. Bollinger. + + */ + +public class BBANDS_Series : TSeries { + protected readonly int _period; + protected readonly double _multiplier; + protected readonly bool _NaN; + protected readonly TSeries _data; + public SMA_Series Mid { get; } + public TSeries Upper { get; } + public TSeries Lower { get; } + public TSeries PercentB { get; } + public TSeries Bandwidth { get; } + public TSeries Zscore { get; } + private readonly SDEV_Series _sdev; + + //core constructors + public BBANDS_Series(int period, double multiplier, bool useNaN) { + _period = period; + _multiplier = multiplier; + _NaN = useNaN; + Name = $"BBANDS({period})"; + } + public BBANDS_Series(TSeries source, int period, double multiplier, bool useNaN) : this(period, multiplier, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + Upper = new("BB_Up"); + Lower = new("BB_Low"); + Bandwidth = new("BBandwidth"); + PercentB = new("%BBandwidth"); + Zscore = new("Zscore"); + + Mid = new(period, false); + _sdev = new(period, false); + + _data.Pub += Sub; + Add(_data); + } + + public BBANDS_Series() : this(period:0, multiplier: 2.0, useNaN: false) { } + public BBANDS_Series(int period) : this(period: period, multiplier: 2.0, useNaN:false) { } + public BBANDS_Series(TBars source) : this(source:source.Close, period:0, multiplier: 2.0, useNaN:false) { } + public BBANDS_Series(TBars source, int period) : this(source:source.Close, period:period, multiplier: 2.0, useNaN: false) { } + public BBANDS_Series(TBars source, int period, double multiplier, bool useNaN) : this(source.Close, period:period, multiplier:multiplier, useNaN: false) { } + public BBANDS_Series(TSeries source) : this(source, period:0, useNaN:false) { } + public BBANDS_Series(TSeries source, int period) : this(source:source, period:period, useNaN:false) { } + public BBANDS_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, multiplier: 2.0, useNaN: useNaN) { } + + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update=false) { + var _mid = Mid.Add(TValue,update); + var _sd = this._sdev.Add(TValue, update); + var _upper = Upper.Add((TValue.t, _mid.v + _sd.v * _multiplier), update); + var _lower = Lower.Add((TValue.t, _mid.v - _sd.v * _multiplier), update); + double _pbdnd = TValue.v - _lower.v; + double _pbdvr = _upper.v - _lower.v; + PercentB.Add((TValue.t, _pbdnd/_pbdvr), update); + Zscore.Add((TValue.t, (TValue.v-_mid.v)/_sd.v), update); + Bandwidth.Add((TValue.t, _pbdvr / _mid.v), update); + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _pbdvr / _mid.v); + return base.Add(res, update); + } + + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + //reset calculation + public override void Reset() { + Mid.Clear(); + _sdev.Clear(); + Upper.Clear(); + Lower.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/BIAS_Series.cs b/Calculations/_Updated/BIAS_Series.cs index df9bc5a5..0c859ee4 100644 --- a/Calculations/_Updated/BIAS_Series.cs +++ b/Calculations/_Updated/BIAS_Series.cs @@ -1,72 +1,72 @@ -namespace QuanTAlib; -using System; - -/* -BIAS: Rate of change between the source and a moving average. - Bias is a statistical term which means a systematic deviation from the actual value. - -BIAS = (close - SMA) / SMA - = (close / SMA) - 1 - -Sources: - https://en.wikipedia.org/wiki/Bias_of_an_estimator - - */ - -public class BIAS_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly SMA_Series _sma; - - //core constructors - public BIAS_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"BIAS({period})"; - _sma = new(period, false); - } - public BIAS_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public BIAS_Series() : this(period: 0, useNaN: false) { } - public BIAS_Series(int period) : this(period: period, useNaN: false) { } - public BIAS_Series(TBars source) : this(source.Close, 0, false) { } - public BIAS_Series(TBars source, int period) : this(source.Close, period, false) { } - public BIAS_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public BIAS_Series(TSeries source) : this(source, 0, false) { } - public BIAS_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - var _s = _sma.Add(TValue,update); - double _bias = (TValue.v / ((_s.v!=0)?_s.v:1)) - 1; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _bias); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _sma.Reset(); - } +namespace QuanTAlib; +using System; + +/* +BIAS: Rate of change between the source and a moving average. + Bias is a statistical term which means a systematic deviation from the actual value. + +BIAS = (close - SMA) / SMA + = (close / SMA) - 1 + +Sources: + https://en.wikipedia.org/wiki/Bias_of_an_estimator + + */ + +public class BIAS_Series : TSeries { + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly SMA_Series _sma; + + //core constructors + public BIAS_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"BIAS({period})"; + _sma = new(period, false); + } + public BIAS_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public BIAS_Series() : this(period: 0, useNaN: false) { } + public BIAS_Series(int period) : this(period: period, useNaN: false) { } + public BIAS_Series(TBars source) : this(source.Close, 0, false) { } + public BIAS_Series(TBars source, int period) : this(source.Close, period, false) { } + public BIAS_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public BIAS_Series(TSeries source) : this(source, 0, false) { } + public BIAS_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + var _s = _sma.Add(TValue,update); + double _bias = (TValue.v / ((_s.v!=0)?_s.v:1)) - 1; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _bias); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _sma.Reset(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/CCI_Series.cs b/Calculations/_Updated/CCI_Series.cs index 3bf2e553..89efbc20 100644 --- a/Calculations/_Updated/CCI_Series.cs +++ b/Calculations/_Updated/CCI_Series.cs @@ -1,86 +1,86 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Linq; - -/* -CCI: Commodity Channel Index - Commodity Channel Index is a momentum oscillator used to primarily identify overbought - and oversold levels relative to a mean. CCI measures the current price level relative - to an average price level over a given period of time: - - CCI is relatively high when prices are far above their average. - - CCI is relatively low when prices are far below their average. - Using this method, CCI can be used to identify overbought and oversold levels. - -Sources: - https://www.investopedia.com/terms/c/commoditychannelindex.asp - https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/cci - - */ - -public class CCI_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TBars _data; - private readonly System.Collections.Generic.List _tp = new(); - - //core constructors - public CCI_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"CCI({period})"; - } - public CCI_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(data: _data); - } - public CCI_Series() : this(period: 2, useNaN: false) { } - public CCI_Series(int period) : this(period: period, useNaN: false) { } - public CCI_Series(TBars source) : this(source, period: 2, useNaN: false) { } - public CCI_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - double _tpItem = (TBar.h + TBar.l + TBar.c) / 3.0; - if (update) { - this._tp[this._tp.Count - 1] = _tpItem; - } - else { - this._tp.Add(_tpItem); - } - if (this._tp.Count > this._period) { this._tp.RemoveAt(0); } - - // average TP over _tp buffer - double _avgTp = _tp.Average(); - - // average Deviation over _tp buffer - double _avgDv = 0; - for (int i = 0; i < this._tp.Count; i++) { _avgDv += Math.Abs(_avgTp - this._tp[i]); } - _avgDv /= this._tp.Count; - - double _cci = (_avgDv == 0) ? 0 : (this._tp[this._tp.Count - 1] - _avgTp) / (0.015 * _avgDv); - var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _cci); - return base.Add(res, update); - } - - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _tp.Clear(); - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Linq; + +/* +CCI: Commodity Channel Index + Commodity Channel Index is a momentum oscillator used to primarily identify overbought + and oversold levels relative to a mean. CCI measures the current price level relative + to an average price level over a given period of time: + - CCI is relatively high when prices are far above their average. + - CCI is relatively low when prices are far below their average. + Using this method, CCI can be used to identify overbought and oversold levels. + +Sources: + https://www.investopedia.com/terms/c/commoditychannelindex.asp + https://www.fidelity.com/learning-center/trading-investing/technical-analysis/technical-indicator-guide/cci + + */ + +public class CCI_Series : TSeries { + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TBars _data; + private readonly System.Collections.Generic.List _tp = new(); + + //core constructors + public CCI_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"CCI({period})"; + } + public CCI_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(data: _data); + } + public CCI_Series() : this(period: 2, useNaN: false) { } + public CCI_Series(int period) : this(period: period, useNaN: false) { } + public CCI_Series(TBars source) : this(source, period: 2, useNaN: false) { } + public CCI_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { + double _tpItem = (TBar.h + TBar.l + TBar.c) / 3.0; + if (update) { + this._tp[this._tp.Count - 1] = _tpItem; + } + else { + this._tp.Add(_tpItem); + } + if (this._tp.Count > this._period) { this._tp.RemoveAt(0); } + + // average TP over _tp buffer + double _avgTp = _tp.Average(); + + // average Deviation over _tp buffer + double _avgDv = 0; + for (int i = 0; i < this._tp.Count; i++) { _avgDv += Math.Abs(_avgTp - this._tp[i]); } + _avgDv /= this._tp.Count; + + double _cci = (_avgDv == 0) ? 0 : (this._tp[this._tp.Count - 1] - _avgTp) / (0.015 * _avgDv); + var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _cci); + return base.Add(res, update); + } + + public new void Add(TBars data) { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TBar: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _tp.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/CMO_Series.cs b/Calculations/_Updated/CMO_Series.cs index 6906067a..ea800ed2 100644 --- a/Calculations/_Updated/CMO_Series.cs +++ b/Calculations/_Updated/CMO_Series.cs @@ -1,90 +1,90 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -CMO: Chande Momentum Oscillator - Chande Momentum Oscillator (also known as CMO indicator) was developed by Tushar S. Chande - CMO is similar to other momentum oscillators (e.g. RSI or Stochastics). Alike RSI oscillator, - the CMO values move in the range from -100 to +100 points and its aim is to detect the - overbought and oversold market conditions. CMO calculates the price momentum on both the up - days as well as the down days. The CMO calculation is based on non-smoothed price values - meaning that it can reach its extremes more frequently and the short-time swings are more visible. - -Sources: - https://www.technicalindicators.net/indicators-technical-analysis/144-cmo-chande-momentum-oscillator - - */ - -public class CMO_Series : TSeries { - private readonly System.Collections.Generic.List _buff_up = new(); - private readonly System.Collections.Generic.List _buff_dn = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private double _plast_value, _last_value; - - //core constructors - public CMO_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"CMO({period})"; - } - public CMO_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public CMO_Series() : this(period: 0, useNaN: false) { } - public CMO_Series(int period) : this(period: period, useNaN: false) { } - public CMO_Series(TBars source) : this(source.Close, 0, false) { } - public CMO_Series(TBars source, int period) : this(source.Close, period, false) { } - public CMO_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public CMO_Series(TSeries source) : this(source, 0, false) { } - public CMO_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { _last_value = _plast_value; } else { _plast_value = _last_value; } - BufferTrim(buffer:_buff_up, (TValue.v > _last_value) ? TValue.v - _last_value : 0, period:_period, update: update); - BufferTrim(buffer: _buff_dn, (TValue.v < _last_value) ? _last_value - TValue.v : 0, period: _period, update: update); - _last_value = TValue.v; - double _cmo_up = 0; - double _cmo_dn = 0; - for (int i = 0; i < Math.Min(_buff_up.Count, _buff_dn.Count); i++) { - _cmo_up += _buff_up[i]; - _cmo_dn += _buff_dn[i]; - } - double _cmo = 100 * (_cmo_up - _cmo_dn) / (_cmo_up + _cmo_dn); - if (_cmo_up + _cmo_dn == 0) { _cmo = 0; } - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _cmo); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buff_up.Clear(); - _buff_dn.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +CMO: Chande Momentum Oscillator + Chande Momentum Oscillator (also known as CMO indicator) was developed by Tushar S. Chande + CMO is similar to other momentum oscillators (e.g. RSI or Stochastics). Alike RSI oscillator, + the CMO values move in the range from -100 to +100 points and its aim is to detect the + overbought and oversold market conditions. CMO calculates the price momentum on both the up + days as well as the down days. The CMO calculation is based on non-smoothed price values + meaning that it can reach its extremes more frequently and the short-time swings are more visible. + +Sources: + https://www.technicalindicators.net/indicators-technical-analysis/144-cmo-chande-momentum-oscillator + + */ + +public class CMO_Series : TSeries { + private readonly System.Collections.Generic.List _buff_up = new(); + private readonly System.Collections.Generic.List _buff_dn = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private double _plast_value, _last_value; + + //core constructors + public CMO_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"CMO({period})"; + } + public CMO_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public CMO_Series() : this(period: 0, useNaN: false) { } + public CMO_Series(int period) : this(period: period, useNaN: false) { } + public CMO_Series(TBars source) : this(source.Close, 0, false) { } + public CMO_Series(TBars source, int period) : this(source.Close, period, false) { } + public CMO_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public CMO_Series(TSeries source) : this(source, 0, false) { } + public CMO_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (update) { _last_value = _plast_value; } else { _plast_value = _last_value; } + BufferTrim(buffer:_buff_up, (TValue.v > _last_value) ? TValue.v - _last_value : 0, period:_period, update: update); + BufferTrim(buffer: _buff_dn, (TValue.v < _last_value) ? _last_value - TValue.v : 0, period: _period, update: update); + _last_value = TValue.v; + double _cmo_up = 0; + double _cmo_dn = 0; + for (int i = 0; i < Math.Min(_buff_up.Count, _buff_dn.Count); i++) { + _cmo_up += _buff_up[i]; + _cmo_dn += _buff_dn[i]; + } + double _cmo = 100 * (_cmo_up - _cmo_dn) / (_cmo_up + _cmo_dn); + if (_cmo_up + _cmo_dn == 0) { _cmo = 0; } + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _cmo); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buff_up.Clear(); + _buff_dn.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/CUSUM_Series.cs b/Calculations/_Updated/CUSUM_Series.cs index e61d288d..08897f07 100644 --- a/Calculations/_Updated/CUSUM_Series.cs +++ b/Calculations/_Updated/CUSUM_Series.cs @@ -1,71 +1,71 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -CUSUM: Cumulative Sum (aka Running Total) - SUM across a period provides a rolling sum of all values across the period. - If SUM values would be divided with period, the output would be SMA() - -Sources: - https://en.wikipedia.org/wiki/CUSUM - */ - -public class CUSUM_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public CUSUM_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"CUSUM({period})"; - } - public CUSUM_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public CUSUM_Series() : this(period: 0, useNaN: false) { } - public CUSUM_Series(int period) : this(period: period, useNaN: false) { } - public CUSUM_Series(TBars source) : this(source.Close, 0, false) { } - public CUSUM_Series(TBars source, int period) : this(source.Close, period, false) { } - public CUSUM_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public CUSUM_Series(TSeries source) : this(source, period: 0, useNaN: false) { } - public CUSUM_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _sum = 0; - for (int i = 0; i < _buffer.Count; i++) { _sum += _buffer[i]; } - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _sum); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +CUSUM: Cumulative Sum (aka Running Total) + SUM across a period provides a rolling sum of all values across the period. + If SUM values would be divided with period, the output would be SMA() + +Sources: + https://en.wikipedia.org/wiki/CUSUM + */ + +public class CUSUM_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public CUSUM_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"CUSUM({period})"; + } + public CUSUM_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public CUSUM_Series() : this(period: 0, useNaN: false) { } + public CUSUM_Series(int period) : this(period: period, useNaN: false) { } + public CUSUM_Series(TBars source) : this(source.Close, 0, false) { } + public CUSUM_Series(TBars source, int period) : this(source.Close, period, false) { } + public CUSUM_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public CUSUM_Series(TSeries source) : this(source, period: 0, useNaN: false) { } + public CUSUM_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _sum = 0; + for (int i = 0; i < _buffer.Count; i++) { _sum += _buffer[i]; } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _sum); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/DECAY_Series.cs b/Calculations/_Updated/DECAY_Series.cs index a0193dc2..263875c3 100644 --- a/Calculations/_Updated/DECAY_Series.cs +++ b/Calculations/_Updated/DECAY_Series.cs @@ -1,83 +1,83 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -DECAY: - Linear decay can be modeled by a straight line with a negative slope of 1/period. - The value decreases in a straight line from the last maximum to 0. - Decay = Last Max - distance/period - - Exponential decay is modeled as an exponential curve with diminishing factor of - 1-1/p - - */ - -public class DECAY_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly bool _exp; - private double _pdecay, _ppdecay; - private readonly double _dfactor; - - //core constructors - public DECAY_Series(int period, bool exponential, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"DECAY({period})"; - _exp = exponential; - _dfactor = (_exp) ? 1.0 - 1.0 / (double)_period : 1 / (double)_period; - _pdecay = _ppdecay = 0; - } - public DECAY_Series(TSeries source, int period, bool exponential, bool useNaN) : this(period, exponential, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public DECAY_Series() : this(period: 0, exponential: false, useNaN: false) { } - public DECAY_Series(int period) : this(period: period, exponential: false, useNaN: false) { } - public DECAY_Series(TBars source) : this(source.Close, period: 0, exponential: false, useNaN: false) { } - public DECAY_Series(TBars source, int period) : this(source.Close, period: period, exponential:false, useNaN:false) { } - public DECAY_Series(TBars source, int period, bool useNaN) : this(source.Close, period: period, exponential: false, useNaN) { } - public DECAY_Series(TSeries source) : this(source, period: 0, exponential: false, useNaN:false) { } - public DECAY_Series(TSeries source, int period) : this(source: source, period: period, exponential: false, useNaN: false) { } - public DECAY_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, exponential: false, useNaN: useNaN) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - if (update) { _pdecay = _ppdecay; } - else { _ppdecay = _pdecay; } - - if (this.Count == 0) { _pdecay = TValue.v; } - double _decay = Math.Max(TValue.v, Math.Max((_exp) ? _pdecay * _dfactor : _pdecay - _dfactor, 0)); - _pdecay = _decay; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _decay); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _pdecay = _ppdecay = 0; - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +DECAY: + Linear decay can be modeled by a straight line with a negative slope of 1/period. + The value decreases in a straight line from the last maximum to 0. + Decay = Last Max - distance/period + + Exponential decay is modeled as an exponential curve with diminishing factor of + 1-1/p + + */ + +public class DECAY_Series : TSeries { + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly bool _exp; + private double _pdecay, _ppdecay; + private readonly double _dfactor; + + //core constructors + public DECAY_Series(int period, bool exponential, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"DECAY({period})"; + _exp = exponential; + _dfactor = (_exp) ? 1.0 - 1.0 / (double)_period : 1 / (double)_period; + _pdecay = _ppdecay = 0; + } + public DECAY_Series(TSeries source, int period, bool exponential, bool useNaN) : this(period, exponential, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public DECAY_Series() : this(period: 0, exponential: false, useNaN: false) { } + public DECAY_Series(int period) : this(period: period, exponential: false, useNaN: false) { } + public DECAY_Series(TBars source) : this(source.Close, period: 0, exponential: false, useNaN: false) { } + public DECAY_Series(TBars source, int period) : this(source.Close, period: period, exponential:false, useNaN:false) { } + public DECAY_Series(TBars source, int period, bool useNaN) : this(source.Close, period: period, exponential: false, useNaN) { } + public DECAY_Series(TSeries source) : this(source, period: 0, exponential: false, useNaN:false) { } + public DECAY_Series(TSeries source, int period) : this(source: source, period: period, exponential: false, useNaN: false) { } + public DECAY_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, exponential: false, useNaN: useNaN) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (double.IsNaN(TValue.v)) { + return base.Add((TValue.t, Double.NaN), update); + } + if (update) { _pdecay = _ppdecay; } + else { _ppdecay = _pdecay; } + + if (this.Count == 0) { _pdecay = TValue.v; } + double _decay = Math.Max(TValue.v, Math.Max((_exp) ? _pdecay * _dfactor : _pdecay - _dfactor, 0)); + _pdecay = _decay; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _decay); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _pdecay = _ppdecay = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/DEMA_Series.cs b/Calculations/_Updated/DEMA_Series.cs index 0cdac2b0..dffd9f71 100644 --- a/Calculations/_Updated/DEMA_Series.cs +++ b/Calculations/_Updated/DEMA_Series.cs @@ -1,127 +1,127 @@ -namespace QuanTAlib; - -using System; -using System.Linq; - -/* -DEMA: Double Exponential Moving Average - DEMA uses EMA(EMA()) to calculate smoother Exponential moving average. - -Sources: - https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/double-exponential-moving-average-dema/ - -Remark: - ema1 = EMA(close, length) - ema2 = EMA(ema1, length) - DEMA = 2 * ema1 - ema2 - - */ - -public class DEMA_Series : TSeries { - private double _k; - private double _sum, _oldsum; - private double _lastema1, _oldema1, _lastema2, _oldema2; - private int _len; - private readonly bool _useSMA; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - -//core constructor - public DEMA_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - _useSMA = useSMA; - Name = $"DEMA({period})"; - _k = 2.0 / (_period + 1); - _len = 0; - _sum = _oldsum = _lastema1 = _lastema2 = 0; - } - //generic constructors (source) - - public DEMA_Series() : this(0, false, true) {} - public DEMA_Series(int period) : this(period, false, true) {} - public DEMA_Series(TBars source) : this(source.Close, 0, false) {} - public DEMA_Series(TBars source, int period) : this(source.Close, period, false) {} - public DEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} - public DEMA_Series(TSeries source, int period) : this(source, period, false, true) {} - public DEMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} - public DEMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - -// core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { - _lastema1 = _oldema1; - _lastema2 = _oldema2; - _sum = _oldsum; - } - else { - _oldema1 = _lastema1; - _oldema2 = _lastema2; - _oldsum = _sum; - _len++; - } - - if (_period == 0) { - _k = 2.0 / (_len + 1); - } - - double _ema1, _ema2, _dema; - if (Count == 0) { - _ema1 = _ema2 = _sum = TValue.v; - } - else if (_len <= _period && _useSMA && _period != 0) { - _sum += TValue.v; - _ema1 = _sum / Math.Min(_len, _period); - _ema2 = _ema1; - } - else { - _ema1 = (TValue.v - _lastema1) * _k + _lastema1; - _ema2 = (_ema1 - _lastema2) * _k + _lastema2; - } - - _dema = 2 * _ema1 - _ema2; - - _lastema1 = double.IsNaN(_ema1) ? _lastema1 : _ema1; - _lastema2 = double.IsNaN(_ema2) ? _lastema2 : _ema2; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _dema); - return base.Add(res, update); - } - -//variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { - return (DateTime.Today, double.NaN); - } - - foreach (var item in data) { - Add(item, false); - } - - return _data.Last; - } - - public (DateTime t, double v) Add(bool update) { - return Add(_data.Last, update); - } - - public (DateTime t, double v) Add() { - return Add(_data.Last, false); - } - - private new void Sub(object source, TSeriesEventArgs e) { - Add(_data.Last, e.update); - } - - //reset calculation - public override void Reset() { - _sum = _oldsum = _lastema1 = _lastema2 = 0; - _len = 0; - } +namespace QuanTAlib; + +using System; +using System.Linq; + +/* +DEMA: Double Exponential Moving Average + DEMA uses EMA(EMA()) to calculate smoother Exponential moving average. + +Sources: + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/double-exponential-moving-average-dema/ + +Remark: + ema1 = EMA(close, length) + ema2 = EMA(ema1, length) + DEMA = 2 * ema1 - ema2 + + */ + +public class DEMA_Series : TSeries { + private double _k; + private double _sum, _oldsum; + private double _lastema1, _oldema1, _lastema2, _oldema2; + private int _len; + private readonly bool _useSMA; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + +//core constructor + public DEMA_Series(int period, bool useNaN, bool useSMA) { + _period = period; + _NaN = useNaN; + _useSMA = useSMA; + Name = $"DEMA({period})"; + _k = 2.0 / (_period + 1); + _len = 0; + _sum = _oldsum = _lastema1 = _lastema2 = 0; + } + //generic constructors (source) + + public DEMA_Series() : this(0, false, true) {} + public DEMA_Series(int period) : this(period, false, true) {} + public DEMA_Series(TBars source) : this(source.Close, 0, false) {} + public DEMA_Series(TBars source, int period) : this(source.Close, period, false) {} + public DEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} + public DEMA_Series(TSeries source, int period) : this(source, period, false, true) {} + public DEMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} + public DEMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + +// core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (update) { + _lastema1 = _oldema1; + _lastema2 = _oldema2; + _sum = _oldsum; + } + else { + _oldema1 = _lastema1; + _oldema2 = _lastema2; + _oldsum = _sum; + _len++; + } + + if (_period == 0) { + _k = 2.0 / (_len + 1); + } + + double _ema1, _ema2, _dema; + if (Count == 0) { + _ema1 = _ema2 = _sum = TValue.v; + } + else if (_len <= _period && _useSMA && _period != 0) { + _sum += TValue.v; + _ema1 = _sum / Math.Min(_len, _period); + _ema2 = _ema1; + } + else { + _ema1 = (TValue.v - _lastema1) * _k + _lastema1; + _ema2 = (_ema1 - _lastema2) * _k + _lastema2; + } + + _dema = 2 * _ema1 - _ema2; + + _lastema1 = double.IsNaN(_ema1) ? _lastema1 : _ema1; + _lastema2 = double.IsNaN(_ema2) ? _lastema2 : _ema2; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _dema); + return base.Add(res, update); + } + +//variation of Add() + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { + return (DateTime.Today, double.NaN); + } + + foreach (var item in data) { + Add(item, false); + } + + return _data.Last; + } + + public (DateTime t, double v) Add(bool update) { + return Add(_data.Last, update); + } + + public (DateTime t, double v) Add() { + return Add(_data.Last, false); + } + + private new void Sub(object source, TSeriesEventArgs e) { + Add(_data.Last, e.update); + } + + //reset calculation + public override void Reset() { + _sum = _oldsum = _lastema1 = _lastema2 = 0; + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/DWMA_Series.cs b/Calculations/_Updated/DWMA_Series.cs index 3e5f4808..20283c34 100644 --- a/Calculations/_Updated/DWMA_Series.cs +++ b/Calculations/_Updated/DWMA_Series.cs @@ -1,122 +1,122 @@ -namespace QuanTAlib; - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -/* -DWMA: Double Weighted Moving Average - The weights are decreasing over the period with p^2 decay - and the most recent data has the heaviest weight. - - */ - -public class DWMA_Series : TSeries { - private readonly List _buffer = new(); - private List _weights; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - protected int _len; - -//core constructors - public DWMA_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"DWMA({period})"; - _len = 0; - _weights = CalculateWeights(_period); - } - - public DWMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - - public DWMA_Series() : this(0, false) { - } - - public DWMA_Series(int period) : this(period, false) { - } - - public DWMA_Series(TBars source) : this(source.Close, 0, false) { - } - - public DWMA_Series(TBars source, int period) : this(source.Close, period, false) { - } - - public DWMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { - } - - public DWMA_Series(TSeries source, int period) : this(source, period, false) { - } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(_buffer, TValue.v, _period, update); - if (_period == 0) { - _len++; - _weights = CalculateWeights(_len); - } - - double _dwma = 0, _wsum = 0; - var bufferCount = _buffer.Count; - - var lockObj = new object(); - Parallel.For(0, bufferCount, i => - { - var temp = _buffer[i] * _weights[i]; - lock (lockObj) { - _dwma += temp; - _wsum += _weights[i]; - } - }); - _dwma /= _wsum; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _dwma); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { - return (DateTime.Today, double.NaN); - } - - foreach (var item in data) { - Add(item, false); - } - - return _data.Last; - } - - public (DateTime t, double v) Add(bool update) { - return Add(_data.Last, update); - } - - public (DateTime t, double v) Add() { - return Add(_data.Last, false); - } - - private new void Sub(object source, TSeriesEventArgs e) { - Add(_data.Last, e.update); - } - - //calculating weights - private static List CalculateWeights(int period) { - var weights = new List(period); - for (var i = 0; i < period; i++) { - weights.Add((i + 1) * (i + 1)); - } - - return weights; - } - - //reset calculation - public override void Reset() { - _len = 0; - _buffer.Clear(); - _weights = CalculateWeights(_period); - } +namespace QuanTAlib; + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +/* +DWMA: Double Weighted Moving Average + The weights are decreasing over the period with p^2 decay + and the most recent data has the heaviest weight. + + */ + +public class DWMA_Series : TSeries { + private readonly List _buffer = new(); + private List _weights; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + protected int _len; + +//core constructors + public DWMA_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"DWMA({period})"; + _len = 0; + _weights = CalculateWeights(_period); + } + + public DWMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + + public DWMA_Series() : this(0, false) { + } + + public DWMA_Series(int period) : this(period, false) { + } + + public DWMA_Series(TBars source) : this(source.Close, 0, false) { + } + + public DWMA_Series(TBars source, int period) : this(source.Close, period, false) { + } + + public DWMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { + } + + public DWMA_Series(TSeries source, int period) : this(source, period, false) { + } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(_buffer, TValue.v, _period, update); + if (_period == 0) { + _len++; + _weights = CalculateWeights(_len); + } + + double _dwma = 0, _wsum = 0; + var bufferCount = _buffer.Count; + + var lockObj = new object(); + Parallel.For(0, bufferCount, i => + { + var temp = _buffer[i] * _weights[i]; + lock (lockObj) { + _dwma += temp; + _wsum += _weights[i]; + } + }); + _dwma /= _wsum; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _dwma); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { + return (DateTime.Today, double.NaN); + } + + foreach (var item in data) { + Add(item, false); + } + + return _data.Last; + } + + public (DateTime t, double v) Add(bool update) { + return Add(_data.Last, update); + } + + public (DateTime t, double v) Add() { + return Add(_data.Last, false); + } + + private new void Sub(object source, TSeriesEventArgs e) { + Add(_data.Last, e.update); + } + + //calculating weights + private static List CalculateWeights(int period) { + var weights = new List(period); + for (var i = 0; i < period; i++) { + weights.Add((i + 1) * (i + 1)); + } + + return weights; + } + + //reset calculation + public override void Reset() { + _len = 0; + _buffer.Clear(); + _weights = CalculateWeights(_period); + } } \ No newline at end of file diff --git a/Calculations/_Updated/EMA_Series.cs b/Calculations/_Updated/EMA_Series.cs index 138f04ea..174d978a 100644 --- a/Calculations/_Updated/EMA_Series.cs +++ b/Calculations/_Updated/EMA_Series.cs @@ -1,120 +1,120 @@ -namespace QuanTAlib; - -using System; -using System.Linq; - -/* -EMA: Exponential Moving Average - EMA needs very short history buffer and calculates the EMA value using just the - previous EMA value. The weight of the new datapoint (k) is k = 2 / (period-1) - -Sources: - https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages - https://www.investopedia.com/ask/answers/122314/what-exponential-moving-average-ema-formula-and-how-ema-calculated.asp - https://blog.fugue88.ws/archives/2017-01/The-correct-way-to-start-an-Exponential-Moving-Average-EMA - -Issues: - There is no consensus what the first EMA value should be - a zero, a first - datapoint, or an average of the initial Period bars. All three starting methods - converge within 20+ bars to the same moving average. Most implementations (including this one) - use SMA() for the first Period bars as a seeding value for EMA. - - */ - -public class EMA_Series : TSeries { - private double _k; - private double _lastema, _oldema; - private double _sum, _oldsum; - private int _len; - private readonly bool _useSMA; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - -//core constructors - - public EMA_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - _useSMA = useSMA; - Name = $"EMA({period})"; - _k = 2.0 / (_period + 1); - _len = 0; - _sum = _oldsum = _lastema = _oldema = 0; - } - public EMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public EMA_Series() : this(0, false, true) {} - public EMA_Series(int period) : this(period, false, true) {} - public EMA_Series(TBars source) : this(source.Close, 0, false) {} - public EMA_Series(TBars source, int period) : this(source.Close, period, false) {} - public EMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} - public EMA_Series(TSeries source, int period) : this(source, period, false, true) {} - public EMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} - - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { - _lastema = _oldema; - _sum = _oldsum; - } - else { - _oldema = _lastema; - _oldsum = _sum; - _len++; - } - - double _ema = 0; - if (_period == 0) { - _k = 2.0 / (_len + 1); - } - - if (Count == 0) { - _ema = _sum = TValue.v; - } - else if (_len <= _period && _useSMA && _period != 0) { - _sum += TValue.v; - if (_period != 0 && _len > _period) { - _sum -= _data[Count - _period - (update ? 1 : 0)].v; - } - - _ema = _sum / Math.Min(_len, _period); - } - else { - _ema = _k * (TValue.v - _lastema) + _lastema; - } - - _lastema = double.IsNaN(_ema) ? _lastema : _ema; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _ema); - return base.Add(res, update); - } - -//variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _sum = _oldsum = _lastema = _oldema = 0; - _len = 0; - } +namespace QuanTAlib; + +using System; +using System.Linq; + +/* +EMA: Exponential Moving Average + EMA needs very short history buffer and calculates the EMA value using just the + previous EMA value. The weight of the new datapoint (k) is k = 2 / (period-1) + +Sources: + https://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages + https://www.investopedia.com/ask/answers/122314/what-exponential-moving-average-ema-formula-and-how-ema-calculated.asp + https://blog.fugue88.ws/archives/2017-01/The-correct-way-to-start-an-Exponential-Moving-Average-EMA + +Issues: + There is no consensus what the first EMA value should be - a zero, a first + datapoint, or an average of the initial Period bars. All three starting methods + converge within 20+ bars to the same moving average. Most implementations (including this one) + use SMA() for the first Period bars as a seeding value for EMA. + + */ + +public class EMA_Series : TSeries { + private double _k; + private double _lastema, _oldema; + private double _sum, _oldsum; + private int _len; + private readonly bool _useSMA; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + +//core constructors + + public EMA_Series(int period, bool useNaN, bool useSMA) { + _period = period; + _NaN = useNaN; + _useSMA = useSMA; + Name = $"EMA({period})"; + _k = 2.0 / (_period + 1); + _len = 0; + _sum = _oldsum = _lastema = _oldema = 0; + } + public EMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public EMA_Series() : this(0, false, true) {} + public EMA_Series(int period) : this(period, false, true) {} + public EMA_Series(TBars source) : this(source.Close, 0, false) {} + public EMA_Series(TBars source, int period) : this(source.Close, period, false) {} + public EMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} + public EMA_Series(TSeries source, int period) : this(source, period, false, true) {} + public EMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} + + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (update) { + _lastema = _oldema; + _sum = _oldsum; + } + else { + _oldema = _lastema; + _oldsum = _sum; + _len++; + } + + double _ema = 0; + if (_period == 0) { + _k = 2.0 / (_len + 1); + } + + if (Count == 0) { + _ema = _sum = TValue.v; + } + else if (_len <= _period && _useSMA && _period != 0) { + _sum += TValue.v; + if (_period != 0 && _len > _period) { + _sum -= _data[Count - _period - (update ? 1 : 0)].v; + } + + _ema = _sum / Math.Min(_len, _period); + } + else { + _ema = _k * (TValue.v - _lastema) + _lastema; + } + + _lastema = double.IsNaN(_ema) ? _lastema : _ema; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _ema); + return base.Add(res, update); + } + +//variation of Add() + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _sum = _oldsum = _lastema = _oldema = 0; + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/ENTROPY_Series.cs b/Calculations/_Updated/ENTROPY_Series.cs index 23523a50..27592dfd 100644 --- a/Calculations/_Updated/ENTROPY_Series.cs +++ b/Calculations/_Updated/ENTROPY_Series.cs @@ -1,87 +1,87 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Linq; - -/* -ENTROPY: - Introduced by Claude Shannon in 1948, entropy measures the unpredictability - of the data, or equivalently, of its average information. - -Calculation: - P = close / Σ(close) - ENTROPY = Σ(-P * Log(P) / Log(base)) - -Sources: - https://en.wikipedia.org/wiki/Entropy_(information_theory) - https://math.stackexchange.com/questions/3428693/how-to-calculate-entropy-from-a-set-of-correlated-samples - - */ - -public class ENTROPY_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly double _logbase; - private readonly System.Collections.Generic.List _buffer = new(); - private readonly System.Collections.Generic.List _buff2 = new(); - - //core constructors - public ENTROPY_Series(int period, double logbase, bool useNaN) { - _period = period; - _NaN = useNaN; - _logbase = logbase; - Name = $"ENTROPY({period})"; - } - public ENTROPY_Series(TSeries source, int period, double logbase, bool useNaN) : this(period, logbase, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public ENTROPY_Series() : this(period: 0, logbase: 2.0, useNaN: false) { } - public ENTROPY_Series(int period) : this(period: period, logbase: 2.0, useNaN: false) { } - public ENTROPY_Series(TBars source) : this(source.Close, period: 0, logbase: 2.0, useNaN: false) { } - public ENTROPY_Series(TBars source, int period) : this(source.Close, period, logbase: 2.0, useNaN: false) { } - public ENTROPY_Series(TBars source, int period, bool useNaN) : this(source.Close, period: period, logbase: 2.0, useNaN: useNaN) { } - public ENTROPY_Series(TSeries source) : this(source, period: 0, logbase: 2.0, useNaN: false) { } - public ENTROPY_Series(TSeries source, int period) : this(source: source, period: period, logbase: 2.0, useNaN: false) { } - public ENTROPY_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, logbase: 2.0, useNaN: useNaN) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _sum = _buffer.Sum(); - double _pp = this._buffer[^1] / _sum; - double _ppp = -_pp * Math.Log(_pp) / Math.Log(this._logbase); - BufferTrim(_buff2, _ppp, _period, update); - double _entp = _buff2.Sum(); - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _entp); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - _buff2.Clear(); - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Linq; + +/* +ENTROPY: + Introduced by Claude Shannon in 1948, entropy measures the unpredictability + of the data, or equivalently, of its average information. + +Calculation: + P = close / Σ(close) + ENTROPY = Σ(-P * Log(P) / Log(base)) + +Sources: + https://en.wikipedia.org/wiki/Entropy_(information_theory) + https://math.stackexchange.com/questions/3428693/how-to-calculate-entropy-from-a-set-of-correlated-samples + + */ + +public class ENTROPY_Series : TSeries { + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly double _logbase; + private readonly System.Collections.Generic.List _buffer = new(); + private readonly System.Collections.Generic.List _buff2 = new(); + + //core constructors + public ENTROPY_Series(int period, double logbase, bool useNaN) { + _period = period; + _NaN = useNaN; + _logbase = logbase; + Name = $"ENTROPY({period})"; + } + public ENTROPY_Series(TSeries source, int period, double logbase, bool useNaN) : this(period, logbase, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public ENTROPY_Series() : this(period: 0, logbase: 2.0, useNaN: false) { } + public ENTROPY_Series(int period) : this(period: period, logbase: 2.0, useNaN: false) { } + public ENTROPY_Series(TBars source) : this(source.Close, period: 0, logbase: 2.0, useNaN: false) { } + public ENTROPY_Series(TBars source, int period) : this(source.Close, period, logbase: 2.0, useNaN: false) { } + public ENTROPY_Series(TBars source, int period, bool useNaN) : this(source.Close, period: period, logbase: 2.0, useNaN: useNaN) { } + public ENTROPY_Series(TSeries source) : this(source, period: 0, logbase: 2.0, useNaN: false) { } + public ENTROPY_Series(TSeries source, int period) : this(source: source, period: period, logbase: 2.0, useNaN: false) { } + public ENTROPY_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, logbase: 2.0, useNaN: useNaN) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (double.IsNaN(TValue.v)) { + return base.Add((TValue.t, Double.NaN), update); + } + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + double _sum = _buffer.Sum(); + double _pp = this._buffer[^1] / _sum; + double _ppp = -_pp * Math.Log(_pp) / Math.Log(this._logbase); + BufferTrim(_buff2, _ppp, _period, update); + double _entp = _buff2.Sum(); + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _entp); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + _buff2.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/FWMA_Series.cs b/Calculations/_Updated/FWMA_Series.cs index 78cfef0e..81134148 100644 --- a/Calculations/_Updated/FWMA_Series.cs +++ b/Calculations/_Updated/FWMA_Series.cs @@ -1,94 +1,94 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using System.Numerics; -using System.Linq; - -/* -FWMA: Fibonacci's Weighted Moving Average is similar to a Weighted Moving Average - (WMA) where the weights are based on the Fibonacci Sequence. - - */ -public class FWMA_Series : TSeries { - private readonly List _buffer = new(); - private List _weights; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - protected int _len; - - public FWMA_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"FWMA({period})"; - _len = 0; - _weights = CalculateWeights(_period); - } - - public FWMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - - public FWMA_Series() : this(period: 0, useNaN: false) { } - public FWMA_Series(int period) : this(period: period, useNaN: false) { } - public FWMA_Series(TBars source) : this(source.Close, 0, false) { } - public FWMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public FWMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public FWMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - if (_period == 0) { - _len++; - _weights = CalculateWeights(_len); - } - double _fwma = 0; - double totalWeights = _weights.Sum(); - object lockObj = new object(); - Parallel.For(0, _buffer.Count, i => - { - double temp = _buffer[i] * _weights[i]; - lock (lockObj) { _fwma += temp; } - }); - _fwma /= totalWeights; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _fwma); - return base.Add(res, update); - } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - private static List CalculateWeights(int period) { - //to prevent overflow, max period can be no more than 1476 - period = (period > 1476) ? 1476 : period; - List weights = new List(period); - BigInteger a = 0; - BigInteger b = 1; - for (int i = 0; i < period; i++) { - BigInteger temp = a; - a = b; - b = temp + b; - weights.Add((double)Decimal.Parse(a.ToString())); - } - return weights; - } - - public override void Reset() { - _weights = CalculateWeights(_period); - _buffer.Clear(); - } -} +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Numerics; +using System.Linq; + +/* +FWMA: Fibonacci's Weighted Moving Average is similar to a Weighted Moving Average + (WMA) where the weights are based on the Fibonacci Sequence. + + */ +public class FWMA_Series : TSeries { + private readonly List _buffer = new(); + private List _weights; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + protected int _len; + + public FWMA_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"FWMA({period})"; + _len = 0; + _weights = CalculateWeights(_period); + } + + public FWMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + + public FWMA_Series() : this(period: 0, useNaN: false) { } + public FWMA_Series(int period) : this(period: period, useNaN: false) { } + public FWMA_Series(TBars source) : this(source.Close, 0, false) { } + public FWMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public FWMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public FWMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + if (_period == 0) { + _len++; + _weights = CalculateWeights(_len); + } + double _fwma = 0; + double totalWeights = _weights.Sum(); + object lockObj = new object(); + Parallel.For(0, _buffer.Count, i => + { + double temp = _buffer[i] * _weights[i]; + lock (lockObj) { _fwma += temp; } + }); + _fwma /= totalWeights; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _fwma); + return base.Add(res, update); + } + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + private static List CalculateWeights(int period) { + //to prevent overflow, max period can be no more than 1476 + period = (period > 1476) ? 1476 : period; + List weights = new List(period); + BigInteger a = 0; + BigInteger b = 1; + for (int i = 0; i < period; i++) { + BigInteger temp = a; + a = b; + b = temp + b; + weights.Add((double)Decimal.Parse(a.ToString())); + } + return weights; + } + + public override void Reset() { + _weights = CalculateWeights(_period); + _buffer.Clear(); + } +} diff --git a/Calculations/_Updated/HEMA_Series.cs b/Calculations/_Updated/HEMA_Series.cs index b2f53114..60aacda4 100644 --- a/Calculations/_Updated/HEMA_Series.cs +++ b/Calculations/_Updated/HEMA_Series.cs @@ -1,116 +1,116 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -HEMA: Hull-EMA Moving Average - a hybrid indicator - Modified HUll Moving Average; instead of using WMA (Weighted MA) for calculation, - HEMA uses EMA for Hull's formula: - -EMA1 = EMA(n/2) of price - where k = 4/(n/2 +1) -EMA2 = EMA(n) of price - where k = 3/(n+1) -Raw HMA = (2 * EMA1) - EMA2 -EMA3 = EMA(sqrt(n)) of Raw HMA - where k = 2/(sqrt(n)+1) - */ - -public class HEMA_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private double _k1, _k2, _k3; - private int _len; - private double _lastema1, _oldema1; - private double _lastema2, _oldema2; - private double _lasthema, _oldhema; - - //core constructors - public HEMA_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"HEMA({period})"; - (_k1, _k2, _k3) = CalculateK(_period); - _len = 0; - _lastema1 = _oldema1 = _lastema2 = _oldema2 = _lasthema = _oldhema = 0; - } - public HEMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public HEMA_Series() : this(period: 0, useNaN: false) { } - public HEMA_Series(int period) : this(period: period, useNaN: false) { } - public HEMA_Series(TBars source) : this(source.Close, 0, false) { } - public HEMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public HEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public HEMA_Series(TSeries source) : this(source, 0, false) { } - public HEMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { - _lastema1 = _oldema1; - _lastema2 = _oldema2; - _lasthema = _oldhema; - } - else { - _oldema1 = _lastema1; - _oldema2 = _lastema2; - _oldhema = _lasthema; - } - double _ema1, _ema2, _hema; - if (_period == 0) { - _len++; - (_k1, _k2, _k3) = CalculateK(_len); - } - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, double.NaN), update); - } else if (this.Count == 0) { - _ema1 = _ema2 = _hema = TValue.v; - } - else { - _ema1 = _k1 * (TValue.v - _lastema1) + _lastema1; - _ema2 = _k2 * (TValue.v - _lastema2) + _lastema2; - _hema = _k3 * (((2 * _ema1) - _ema2) - _lasthema) + _lasthema; - } - - _lastema1 = _ema1; - _lastema2 = _ema2; - _lasthema = _hema; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _hema); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _lastema1 = _lastema2 = _lasthema = 0; - _oldema1 = _oldema2 = _oldhema = 0; - _len = 0; - } - - public static (double k1, double k2, double k3) CalculateK(int len) { - double k1 = 8 / (double)(len + 7); - double k2 = 3 / (double)(len + 2); - double k3 = 2 / Math.Sqrt(len + 3); - - return (k1, k2, k3); - } - +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +HEMA: Hull-EMA Moving Average - a hybrid indicator + Modified HUll Moving Average; instead of using WMA (Weighted MA) for calculation, + HEMA uses EMA for Hull's formula: + +EMA1 = EMA(n/2) of price - where k = 4/(n/2 +1) +EMA2 = EMA(n) of price - where k = 3/(n+1) +Raw HMA = (2 * EMA1) - EMA2 +EMA3 = EMA(sqrt(n)) of Raw HMA - where k = 2/(sqrt(n)+1) + */ + +public class HEMA_Series : TSeries { + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private double _k1, _k2, _k3; + private int _len; + private double _lastema1, _oldema1; + private double _lastema2, _oldema2; + private double _lasthema, _oldhema; + + //core constructors + public HEMA_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"HEMA({period})"; + (_k1, _k2, _k3) = CalculateK(_period); + _len = 0; + _lastema1 = _oldema1 = _lastema2 = _oldema2 = _lasthema = _oldhema = 0; + } + public HEMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public HEMA_Series() : this(period: 0, useNaN: false) { } + public HEMA_Series(int period) : this(period: period, useNaN: false) { } + public HEMA_Series(TBars source) : this(source.Close, 0, false) { } + public HEMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public HEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public HEMA_Series(TSeries source) : this(source, 0, false) { } + public HEMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (update) { + _lastema1 = _oldema1; + _lastema2 = _oldema2; + _lasthema = _oldhema; + } + else { + _oldema1 = _lastema1; + _oldema2 = _lastema2; + _oldhema = _lasthema; + } + double _ema1, _ema2, _hema; + if (_period == 0) { + _len++; + (_k1, _k2, _k3) = CalculateK(_len); + } + if (double.IsNaN(TValue.v)) { + return base.Add((TValue.t, double.NaN), update); + } else if (this.Count == 0) { + _ema1 = _ema2 = _hema = TValue.v; + } + else { + _ema1 = _k1 * (TValue.v - _lastema1) + _lastema1; + _ema2 = _k2 * (TValue.v - _lastema2) + _lastema2; + _hema = _k3 * (((2 * _ema1) - _ema2) - _lasthema) + _lasthema; + } + + _lastema1 = _ema1; + _lastema2 = _ema2; + _lasthema = _hema; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _hema); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _lastema1 = _lastema2 = _lasthema = 0; + _oldema1 = _oldema2 = _oldhema = 0; + _len = 0; + } + + public static (double k1, double k2, double k3) CalculateK(int len) { + double k1 = 8 / (double)(len + 7); + double k2 = 3 / (double)(len + 2); + double k3 = 2 / Math.Sqrt(len + 3); + + return (k1, k2, k3); + } + } \ No newline at end of file diff --git a/Calculations/_Updated/HMA_Series.cs b/Calculations/_Updated/HMA_Series.cs index 9230a76b..01736639 100644 --- a/Calculations/_Updated/HMA_Series.cs +++ b/Calculations/_Updated/HMA_Series.cs @@ -1,88 +1,88 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -HMA: Hull Moving Average - Developed by Alan Hull, an extremely fast and smooth moving average; almost - eliminates lag altogether and manages to improve smoothing at the same time. - -Sources: - https://alanhull.com/hull-moving-average - https://school.stockcharts.com/doku.php?id=technical_indicators:hull_moving_average - -WMA1 = WMA(n/2) of price -WMA2 = WMA(n) of price -Raw HMA = (2 * WMA1) - WMA2 -HMA = WMA(sqrt(n)) of Raw HMA - - */ - -public class HMA_Series : TSeries { - protected int _period, _period2, _psqrt; - protected readonly bool _NaN; - protected readonly TSeries _data; - protected WMA_Series _wma1, _wma2, _wma3; - - //core constructors - public HMA_Series(int period, bool useNaN) { - _period = period; - _period2 = period /2; - _psqrt = (int)Math.Sqrt(period); - _NaN = useNaN; - _wma1 = new(Math.Max(_period2,1), false); - _wma2 = new(Math.Max(_period,1), false); - _wma3 = new(Math.Max(_psqrt,1), useNaN); - Name = $"HMA({period})"; - } - public HMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public HMA_Series() : this(period: 0, useNaN: false) { } - public HMA_Series(int period) : this(period: period, useNaN: false) { } - public HMA_Series(TBars source) : this(source.Close, 0, false) { } - public HMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public HMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public HMA_Series(TSeries source) : this(source, 0, false) { } - public HMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (_period == 0) { - _wma1.Len = this.Count / 2; - _wma2.Len = this.Count; - _wma1.Len = (int)Math.Sqrt(this.Count); - } - double _w1 = _wma1.Add(TValue, update).v; - double _w2 = _wma2.Add(TValue, update).v; - double _hma = _wma3.Add((2 * _w1) - _w2, update).v; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _hma); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _wma1.Reset(); - _wma2.Reset(); - _wma3.Reset(); - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +HMA: Hull Moving Average + Developed by Alan Hull, an extremely fast and smooth moving average; almost + eliminates lag altogether and manages to improve smoothing at the same time. + +Sources: + https://alanhull.com/hull-moving-average + https://school.stockcharts.com/doku.php?id=technical_indicators:hull_moving_average + +WMA1 = WMA(n/2) of price +WMA2 = WMA(n) of price +Raw HMA = (2 * WMA1) - WMA2 +HMA = WMA(sqrt(n)) of Raw HMA + + */ + +public class HMA_Series : TSeries { + protected int _period, _period2, _psqrt; + protected readonly bool _NaN; + protected readonly TSeries _data; + protected WMA_Series _wma1, _wma2, _wma3; + + //core constructors + public HMA_Series(int period, bool useNaN) { + _period = period; + _period2 = period /2; + _psqrt = (int)Math.Sqrt(period); + _NaN = useNaN; + _wma1 = new(Math.Max(_period2,1), false); + _wma2 = new(Math.Max(_period,1), false); + _wma3 = new(Math.Max(_psqrt,1), useNaN); + Name = $"HMA({period})"; + } + public HMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public HMA_Series() : this(period: 0, useNaN: false) { } + public HMA_Series(int period) : this(period: period, useNaN: false) { } + public HMA_Series(TBars source) : this(source.Close, 0, false) { } + public HMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public HMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public HMA_Series(TSeries source) : this(source, 0, false) { } + public HMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (_period == 0) { + _wma1.Len = this.Count / 2; + _wma2.Len = this.Count; + _wma1.Len = (int)Math.Sqrt(this.Count); + } + double _w1 = _wma1.Add(TValue, update).v; + double _w2 = _wma2.Add(TValue, update).v; + double _hma = _wma3.Add((2 * _w1) - _w2, update).v; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _hma); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _wma1.Reset(); + _wma2.Reset(); + _wma3.Reset(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/HWMA_Series.cs b/Calculations/_Updated/HWMA_Series.cs index f67249e2..8f360b52 100644 --- a/Calculations/_Updated/HWMA_Series.cs +++ b/Calculations/_Updated/HWMA_Series.cs @@ -1,132 +1,132 @@ -namespace QuanTAlib; - -using System; -using System.Linq; - -/* -HWMA: Holt-Winter Moving Average - Indicator HWMA (Holt-Winter Moving Average) is a three-parameter moving - average by the Holt-Winter method; Holt-Winters Exponential Smoothing is - used for forecasting time series data that exhibits both a trend and a - seasonal variation. - - -Sources: - https://timeseriesreasoning.com/contents/holt-winters-exponential-smoothing/ - https://www.mql5.com/en/code/20856 - -nA - smoothed series (from 0 to 1) -nB - assess the trend (from 0 to 1) -nC - assess seasonality (from 0 to 1) - -Heuristic for determining alpha, beta, and gamma from period: - alpha = 2 / (1 + period) - beta = 1 / period - gamma = 1 / period - -F[i] = (1-nA) * (F[i-1] + V[i-1] + 0.5 * A[i-1]) + nA * Price[i] -V[i] = (1-nB) * (V[i-1] + A[i-1]) + nB * (F[i] - F[i-1]) -A[i] = (1-nC) * A[i-1] + nC * (V[i] - V[i-1]) -HWMA[i] = F[i] + V[i] + 0.5 * A[i] - - */ - -public class HWMA_Series : TSeries { - private int _len; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - double _nA, _nB, _nC; - double _pF, _pV, _pA; - double _ppF, _ppV, _ppA; - - //core constructors - - public HWMA_Series(double nA, double nB, double nC, bool useNaN) { - _period = (int)((2 - nA) / nA); - _nA = nA; - _nB = nB; - _nC = nC; - _NaN = useNaN; - Name = $"HWMA({_period})"; - _len = 0; - } - public HWMA_Series(TSeries source, double nA, double nB, double nC, bool useNaN = false) : this(nA, nB, nC, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public HWMA_Series() : this(period: 0, useNaN: false) { } - public HWMA_Series(int period) : this(period, useNaN: false) { } - public HWMA_Series(int period, bool useNaN) : this(nA: 2 / (1 + (double)period), nB: 1 / (double)period, nC: 1 / (double)period, useNaN) { - _period = period; - } - public HWMA_Series(TBars source) : this(source.Close, period: 0, useNaN: false) { } - public HWMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public HWMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public HWMA_Series(TSeries source, int period) : this(source, period, false) { } - public HWMA_Series(TSeries source, int period, bool useNaN) : this(source, nA: 2 / (1 + (double)period), nB: 1 / (double)period, nC: 1 / (double)period, useNaN: useNaN) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - double _F, _V, _A; - if (_len == 0) { _pF = TValue.v; _pA = _pV = 0; } - - if (update) { _pF = _ppF; _pV = _ppV; _pA = _ppA; } - else { - _ppF = _pF; - _ppV = _pV; - _ppA = _pA; - _len++; - } - - if (_period == 0) { - _nA = 2 / (1 + (double)_len); - _nB = 1 / (double)_len; - _nC = 1 / (double)_len; - } - if (_period == 1) { - _nA = 1; - _nB = 0; - _nC = 0; - } - - _F = (1 - _nA) * (_pF + _pV + 0.5 * _pA) + _nA * TValue.v; - _V = (1 - _nB) * (_pV + _pA) + _nB * (_F - _pF); - _A = (1 - _nC) * _pA + _nC * (_V - _pV); - - double _hwma = _F + _V + 0.5 * _A; - _pF = _F; - _pV = _V; - _pA = _A; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _hwma); - return base.Add(res, update); - } - - //variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _len = 0; - } +namespace QuanTAlib; + +using System; +using System.Linq; + +/* +HWMA: Holt-Winter Moving Average + Indicator HWMA (Holt-Winter Moving Average) is a three-parameter moving + average by the Holt-Winter method; Holt-Winters Exponential Smoothing is + used for forecasting time series data that exhibits both a trend and a + seasonal variation. + + +Sources: + https://timeseriesreasoning.com/contents/holt-winters-exponential-smoothing/ + https://www.mql5.com/en/code/20856 + +nA - smoothed series (from 0 to 1) +nB - assess the trend (from 0 to 1) +nC - assess seasonality (from 0 to 1) + +Heuristic for determining alpha, beta, and gamma from period: + alpha = 2 / (1 + period) + beta = 1 / period + gamma = 1 / period + +F[i] = (1-nA) * (F[i-1] + V[i-1] + 0.5 * A[i-1]) + nA * Price[i] +V[i] = (1-nB) * (V[i-1] + A[i-1]) + nB * (F[i] - F[i-1]) +A[i] = (1-nC) * A[i-1] + nC * (V[i] - V[i-1]) +HWMA[i] = F[i] + V[i] + 0.5 * A[i] + + */ + +public class HWMA_Series : TSeries { + private int _len; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + double _nA, _nB, _nC; + double _pF, _pV, _pA; + double _ppF, _ppV, _ppA; + + //core constructors + + public HWMA_Series(double nA, double nB, double nC, bool useNaN) { + _period = (int)((2 - nA) / nA); + _nA = nA; + _nB = nB; + _nC = nC; + _NaN = useNaN; + Name = $"HWMA({_period})"; + _len = 0; + } + public HWMA_Series(TSeries source, double nA, double nB, double nC, bool useNaN = false) : this(nA, nB, nC, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public HWMA_Series() : this(period: 0, useNaN: false) { } + public HWMA_Series(int period) : this(period, useNaN: false) { } + public HWMA_Series(int period, bool useNaN) : this(nA: 2 / (1 + (double)period), nB: 1 / (double)period, nC: 1 / (double)period, useNaN) { + _period = period; + } + public HWMA_Series(TBars source) : this(source.Close, period: 0, useNaN: false) { } + public HWMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public HWMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public HWMA_Series(TSeries source, int period) : this(source, period, false) { } + public HWMA_Series(TSeries source, int period, bool useNaN) : this(source, nA: 2 / (1 + (double)period), nB: 1 / (double)period, nC: 1 / (double)period, useNaN: useNaN) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (double.IsNaN(TValue.v)) { + return base.Add((TValue.t, Double.NaN), update); + } + double _F, _V, _A; + if (_len == 0) { _pF = TValue.v; _pA = _pV = 0; } + + if (update) { _pF = _ppF; _pV = _ppV; _pA = _ppA; } + else { + _ppF = _pF; + _ppV = _pV; + _ppA = _pA; + _len++; + } + + if (_period == 0) { + _nA = 2 / (1 + (double)_len); + _nB = 1 / (double)_len; + _nC = 1 / (double)_len; + } + if (_period == 1) { + _nA = 1; + _nB = 0; + _nC = 0; + } + + _F = (1 - _nA) * (_pF + _pV + 0.5 * _pA) + _nA * TValue.v; + _V = (1 - _nB) * (_pV + _pA) + _nB * (_F - _pF); + _A = (1 - _nC) * _pA + _nC * (_V - _pV); + + double _hwma = _F + _V + 0.5 * _A; + _pF = _F; + _pV = _V; + _pA = _A; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _hwma); + return base.Add(res, update); + } + + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/JMA_Series.cs b/Calculations/_Updated/JMA_Series.cs index 8d5b9af0..517bbbe3 100644 --- a/Calculations/_Updated/JMA_Series.cs +++ b/Calculations/_Updated/JMA_Series.cs @@ -1,176 +1,176 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Linq; - -/* -JMA: Jurik Moving Average - Mark Jurik's Moving Average (JMA) attempts to eliminate noise to see the - underlying activity. It has extremely low lag, is very smooth and is responsive - to market gaps. - -Sources: - https://c.mql5.com/forextsd/forum/164/jurik_1.pdf - https://www.prorealcode.com/prorealtime-indicators/jurik-volatility-bands/ - -Issues: - Real JMA algorithm is not published and this formula is derived through - deduction and reverse analysis of JMA behavior. It is really close, but not - exact - published JMA tests against JMA.CSV fail with small deviation. The - original algo is slightly different, yet this approximation is close enough. - - */ - -public class JMA_Series : TSeries -{ - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly System.Collections.Generic.List volty_short = new(); - private readonly System.Collections.Generic.List vsum_buff = new(); - private readonly double pr; - private double upperBand, lowerBand, vsum, Kv; - private double prev_ma1, prev_det0, prev_det1, prev_vsum, prev_jma; - private double p_upperBand, p_lowerBand, p_Kv, p_prev_ma1, p_prev_det0, p_prev_det1, p_prev_vsum, p_prev_jma; - private readonly int _voltyS, _voltyL; - - //core constructors - public JMA_Series(int period, double phase, int vshort, int vlong, bool useNaN) - { - _period = period; - _NaN = useNaN; - Name = $"JMA({period})"; - upperBand = lowerBand = prev_ma1 = prev_det0 = prev_det1 = prev_vsum = prev_jma = Kv = 0.0; - pr = (phase * 0.01) + 1.5; - if (phase < -100) { pr = 0.5; } - if (phase > 100) { pr = 2.5; } - _voltyS = vshort; - _voltyL = vlong; - } - - public JMA_Series(TSeries source, int period, double phase, int vshort, int vlong, bool useNaN) : this(period, phase, vshort, vlong, useNaN) - { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public JMA_Series() : this(period: 0, phase: 0, vshort: 10, vlong: 65, useNaN: false) { } - public JMA_Series(int period) : this(period: period, phase: 0, vshort: 10, vlong: 65, useNaN: false) { } - public JMA_Series(TBars source) : this(source.Close, period: 0, phase: 0.0, vshort: 10, vlong: 65, useNaN: false) { } - public JMA_Series(TBars source, int period) : this(source.Close, period, phase: 0.0, vshort: 10, vlong: 65, useNaN: false) { } - public JMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, phase: 0.0, vshort: 10, vlong: 65, useNaN: useNaN) { } - public JMA_Series(TSeries source) : this(source, period: 0, phase: 0.0, vshort: 10, vlong: 65, useNaN: false) { } - public JMA_Series(TSeries source, int period) : this(source: source, period: period, phase: 0.0, vshort: 10, vlong: 65, useNaN: false) { } - public JMA_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, phase: 0.0, vshort: 10, vlong: 65, useNaN: useNaN) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) - { - if (this.Count == 0) { prev_ma1 = prev_jma = TValue.v; } - if (update) - { - upperBand = p_upperBand; - lowerBand = p_lowerBand; - Kv = p_Kv; - prev_vsum = p_prev_vsum; - prev_ma1 = p_prev_ma1; - prev_det0 = p_prev_det0; - prev_det1 = p_prev_det1; - prev_jma = p_prev_jma; - } - else - { - p_upperBand = upperBand; - p_lowerBand = lowerBand; - p_Kv = Kv; - p_prev_vsum = prev_vsum; - p_prev_ma1 = prev_ma1; - p_prev_det0 = prev_det0; - p_prev_det1 = prev_det1; - p_prev_jma = prev_jma; - } - - if (double.IsNaN(TValue.v)) - { - return base.Add((TValue.t, double.NaN), update); - } - - // from Tvalue to volty - double del1 = TValue.v - upperBand; - double del2 = TValue.v - lowerBand; - upperBand = (del1 > 0) ? TValue.v : TValue.v - (Kv * del1); - lowerBand = (del2 < 0) ? TValue.v : TValue.v - (Kv * del2); - double volty = Math.Abs(del1) > Math.Abs(del2) ? Math.Abs(del1) : - (Math.Abs(del1) < Math.Abs(del2) ? Math.Abs(del2) : - Math.Abs(0.5 * (del1 + del2))); - - //// from volty to avolty - if (update) { volty_short[volty_short.Count - 1] = volty; } - else { volty_short.Add(volty); } - if (volty_short.Count > _voltyS) { volty_short.RemoveAt(0); } - vsum = prev_vsum + 0.1 * (volty - volty_short.First()); - prev_vsum = vsum; - if (update) { vsum_buff[vsum_buff.Count - 1] = vsum; } - else { vsum_buff.Add(vsum); } - if (vsum_buff.Count > _voltyL) { vsum_buff.RemoveAt(0); } - double avolty = 0; - for (int i = 0; i < vsum_buff.Count; i++) { avolty += vsum_buff[i]; } - avolty /= vsum_buff.Count; - - /// from avolty to rolty - double rvolty = (avolty != 0) ? volty / avolty : 0; - double len1 = (Math.Log(Math.Sqrt(_period)) / Math.Log(2.0)) + 2; - if (len1 < 0) { len1 = 0; } - - double pow1 = Math.Max(len1 - 2.0, 0.5); - if (rvolty > Math.Pow(len1, 1.0 / pow1)) { rvolty = Math.Pow(len1, 1.0 / pow1); } - if (rvolty < 1) { rvolty = 1; } - - //// from rvolty to second smoothing - double pow2 = Math.Pow(rvolty, pow1); - double beta = 0.45 * (_period - 1) / (0.45 * (_period - 1) + 2); - Kv = Math.Pow(beta, Math.Sqrt(pow2)); - double alpha = Math.Pow(beta, pow2); - double ma1 = (1 - alpha) * TValue.v + alpha * prev_ma1; - prev_ma1 = ma1; - - double det0 = (1 - beta) * (TValue.v - ma1) + beta * prev_det0; - prev_det0 = det0; - double ma2 = ma1 + pr * det0; - - double det1 = ((1 - alpha) * (1 - alpha) * (ma2 - prev_jma)) + (alpha * alpha * prev_det1); - prev_det1 = det1; - double jma = prev_jma + det1; - prev_jma = jma; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : jma); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) - { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) - { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() - { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) - { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() - { - upperBand = lowerBand = prev_ma1 = prev_det0 = prev_det1 = prev_vsum = prev_jma = Kv = 0.0; - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Linq; + +/* +JMA: Jurik Moving Average + Mark Jurik's Moving Average (JMA) attempts to eliminate noise to see the + underlying activity. It has extremely low lag, is very smooth and is responsive + to market gaps. + +Sources: + https://c.mql5.com/forextsd/forum/164/jurik_1.pdf + https://www.prorealcode.com/prorealtime-indicators/jurik-volatility-bands/ + +Issues: + Real JMA algorithm is not published and this formula is derived through + deduction and reverse analysis of JMA behavior. It is really close, but not + exact - published JMA tests against JMA.CSV fail with small deviation. The + original algo is slightly different, yet this approximation is close enough. + + */ + +public class JMA_Series : TSeries +{ + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly System.Collections.Generic.List volty_short = new(); + private readonly System.Collections.Generic.List vsum_buff = new(); + private readonly double pr; + private double upperBand, lowerBand, vsum, Kv; + private double prev_ma1, prev_det0, prev_det1, prev_vsum, prev_jma; + private double p_upperBand, p_lowerBand, p_Kv, p_prev_ma1, p_prev_det0, p_prev_det1, p_prev_vsum, p_prev_jma; + private readonly int _voltyS, _voltyL; + + //core constructors + public JMA_Series(int period, double phase, int vshort, int vlong, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"JMA({period})"; + upperBand = lowerBand = prev_ma1 = prev_det0 = prev_det1 = prev_vsum = prev_jma = Kv = 0.0; + pr = (phase * 0.01) + 1.5; + if (phase < -100) { pr = 0.5; } + if (phase > 100) { pr = 2.5; } + _voltyS = vshort; + _voltyL = vlong; + } + + public JMA_Series(TSeries source, int period, double phase, int vshort, int vlong, bool useNaN) : this(period, phase, vshort, vlong, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public JMA_Series() : this(period: 0, phase: 0, vshort: 10, vlong: 65, useNaN: false) { } + public JMA_Series(int period) : this(period: period, phase: 0, vshort: 10, vlong: 65, useNaN: false) { } + public JMA_Series(TBars source) : this(source.Close, period: 0, phase: 0.0, vshort: 10, vlong: 65, useNaN: false) { } + public JMA_Series(TBars source, int period) : this(source.Close, period, phase: 0.0, vshort: 10, vlong: 65, useNaN: false) { } + public JMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, phase: 0.0, vshort: 10, vlong: 65, useNaN: useNaN) { } + public JMA_Series(TSeries source) : this(source, period: 0, phase: 0.0, vshort: 10, vlong: 65, useNaN: false) { } + public JMA_Series(TSeries source, int period) : this(source: source, period: period, phase: 0.0, vshort: 10, vlong: 65, useNaN: false) { } + public JMA_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, phase: 0.0, vshort: 10, vlong: 65, useNaN: useNaN) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (this.Count == 0) { prev_ma1 = prev_jma = TValue.v; } + if (update) + { + upperBand = p_upperBand; + lowerBand = p_lowerBand; + Kv = p_Kv; + prev_vsum = p_prev_vsum; + prev_ma1 = p_prev_ma1; + prev_det0 = p_prev_det0; + prev_det1 = p_prev_det1; + prev_jma = p_prev_jma; + } + else + { + p_upperBand = upperBand; + p_lowerBand = lowerBand; + p_Kv = Kv; + p_prev_vsum = prev_vsum; + p_prev_ma1 = prev_ma1; + p_prev_det0 = prev_det0; + p_prev_det1 = prev_det1; + p_prev_jma = prev_jma; + } + + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, double.NaN), update); + } + + // from Tvalue to volty + double del1 = TValue.v - upperBand; + double del2 = TValue.v - lowerBand; + upperBand = (del1 > 0) ? TValue.v : TValue.v - (Kv * del1); + lowerBand = (del2 < 0) ? TValue.v : TValue.v - (Kv * del2); + double volty = Math.Abs(del1) > Math.Abs(del2) ? Math.Abs(del1) : + (Math.Abs(del1) < Math.Abs(del2) ? Math.Abs(del2) : + Math.Abs(0.5 * (del1 + del2))); + + //// from volty to avolty + if (update) { volty_short[volty_short.Count - 1] = volty; } + else { volty_short.Add(volty); } + if (volty_short.Count > _voltyS) { volty_short.RemoveAt(0); } + vsum = prev_vsum + 0.1 * (volty - volty_short.First()); + prev_vsum = vsum; + if (update) { vsum_buff[vsum_buff.Count - 1] = vsum; } + else { vsum_buff.Add(vsum); } + if (vsum_buff.Count > _voltyL) { vsum_buff.RemoveAt(0); } + double avolty = 0; + for (int i = 0; i < vsum_buff.Count; i++) { avolty += vsum_buff[i]; } + avolty /= vsum_buff.Count; + + /// from avolty to rolty + double rvolty = (avolty != 0) ? volty / avolty : 0; + double len1 = (Math.Log(Math.Sqrt(_period)) / Math.Log(2.0)) + 2; + if (len1 < 0) { len1 = 0; } + + double pow1 = Math.Max(len1 - 2.0, 0.5); + if (rvolty > Math.Pow(len1, 1.0 / pow1)) { rvolty = Math.Pow(len1, 1.0 / pow1); } + if (rvolty < 1) { rvolty = 1; } + + //// from rvolty to second smoothing + double pow2 = Math.Pow(rvolty, pow1); + double beta = 0.45 * (_period - 1) / (0.45 * (_period - 1) + 2); + Kv = Math.Pow(beta, Math.Sqrt(pow2)); + double alpha = Math.Pow(beta, pow2); + double ma1 = (1 - alpha) * TValue.v + alpha * prev_ma1; + prev_ma1 = ma1; + + double det0 = (1 - beta) * (TValue.v - ma1) + beta * prev_det0; + prev_det0 = det0; + double ma2 = ma1 + pr * det0; + + double det1 = ((1 - alpha) * (1 - alpha) * (ma2 - prev_jma)) + (alpha * alpha * prev_det1); + prev_det1 = det1; + double jma = prev_jma + det1; + prev_jma = jma; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : jma); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() + { + upperBand = lowerBand = prev_ma1 = prev_det0 = prev_det1 = prev_vsum = prev_jma = Kv = 0.0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/KAMA_Series.cs b/Calculations/_Updated/KAMA_Series.cs index a3bcf394..74065605 100644 --- a/Calculations/_Updated/KAMA_Series.cs +++ b/Calculations/_Updated/KAMA_Series.cs @@ -1,107 +1,107 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -KAMA: Kaufman's Adaptive Moving Average - Created in 1988 by American quantitative finance theorist Perry J. Kaufman and is known as - Kaufman's Adaptive Moving Average (KAMA). Even though the method was developed as early as 1972, - it was not until the popular book titled "Trading Systems and Methods" that it was made widely - available to the public. Unlike other conventional moving averages systems, the Kaufman's Adaptive - Moving Average, considers market volatility apart from price fluctuations. - - KAMA[i] = KAMA[i-1] + SC * ( price - KAMA[i-1] ) - -Sources: - https://www.tutorialspoint.com/kaufman-s-adaptive-moving-average-kama-formula-and-how-does-it-work - https://corporatefinanceinstitute.com/resources/knowledge/trading-investing/kaufmans-adaptive-moving-average-kama/ - https://www.technicalindicators.net/indicators-technical-analysis/152-kama-kaufman-adaptive-moving-average - -Remark: - If useNaN:true argument is provided, KAMA starts calculating values from [period] bar onwards. - Without useNaN argument (default setting), KAMA starts calculating values from bar 1 - and yields - slightly different results for the first 50 bars - and then converges with the other one. - - */ - -public class KAMA_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - private double _lastkama, _lastlastkama; - private readonly double _scFast, _scSlow; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public KAMA_Series(int period, int fast, int slow, bool useNaN) { - _period = period; - _NaN = useNaN; - _scFast = 2.0 / (((period < fast) ? period : fast) + 1); - _scSlow = 2.0 / (slow + 1); - _lastkama = _lastlastkama = 0; - Name = $"KAMA({period})"; - } - public KAMA_Series(TSeries source, int period, int fast, int slow, bool useNaN) : this(period, fast, slow, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public KAMA_Series() : this(period: 0, fast: 2, slow: 30, useNaN: false) { } - public KAMA_Series(int period) : this(period: period, fast: 2, slow: 30, useNaN: false) { } - public KAMA_Series(TBars source) : this(source.Close, period: 0, fast: 2, slow: 30, useNaN: false) { } - public KAMA_Series(TBars source, int period) : this(source.Close, period: period, fast: 2, slow: 30, useNaN: false) { } - public KAMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period: period, fast: 2, slow: 30, useNaN: useNaN) { } - public KAMA_Series(TSeries source) : this(source, period: 0, fast: 2, slow: 30, useNaN: false) { } - public KAMA_Series(TSeries source, int period) : this(source: source, period: period, fast: 2, slow: 30, useNaN: false) { } - public KAMA_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, fast: 2, slow: 30, useNaN: useNaN) { } - public KAMA_Series(TSeries source, int period, int fast, int slow) : this(source: source, period: period, fast: fast, slow: slow, useNaN: false) { } - - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - - if (update) { _lastkama = _lastlastkama; } - else { _lastlastkama = _lastkama; } - BufferTrim(buffer: _buffer, value: TValue.v, period: _period + 1, update: update); - - double _kama = 0; - if (this.Count < _period) { _kama = TValue.v; } - else { - double _change = Math.Abs(_buffer[^1] - _buffer[(_buffer.Count > _period + 1) ? 1 : 0]); - double _sumpv = 0; - for (int i = 1; i < _buffer.Count; i++) { _sumpv += Math.Abs(_buffer[(_buffer.Count > 0) ? i : 0] - _buffer[i - 1]); } - double _er = (_sumpv == 0) ? 0 : _change / _sumpv; - double _sc = (_er * (_scFast - _scSlow)) + _scSlow; - _kama = (_lastkama + (_sc * _sc * (TValue.v - _lastkama))); - } - _lastkama = _kama; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _kama); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - _lastkama = _lastlastkama = 0; - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +KAMA: Kaufman's Adaptive Moving Average + Created in 1988 by American quantitative finance theorist Perry J. Kaufman and is known as + Kaufman's Adaptive Moving Average (KAMA). Even though the method was developed as early as 1972, + it was not until the popular book titled "Trading Systems and Methods" that it was made widely + available to the public. Unlike other conventional moving averages systems, the Kaufman's Adaptive + Moving Average, considers market volatility apart from price fluctuations. + + KAMA[i] = KAMA[i-1] + SC * ( price - KAMA[i-1] ) + +Sources: + https://www.tutorialspoint.com/kaufman-s-adaptive-moving-average-kama-formula-and-how-does-it-work + https://corporatefinanceinstitute.com/resources/knowledge/trading-investing/kaufmans-adaptive-moving-average-kama/ + https://www.technicalindicators.net/indicators-technical-analysis/152-kama-kaufman-adaptive-moving-average + +Remark: + If useNaN:true argument is provided, KAMA starts calculating values from [period] bar onwards. + Without useNaN argument (default setting), KAMA starts calculating values from bar 1 - and yields + slightly different results for the first 50 bars - and then converges with the other one. + + */ + +public class KAMA_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + private double _lastkama, _lastlastkama; + private readonly double _scFast, _scSlow; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public KAMA_Series(int period, int fast, int slow, bool useNaN) { + _period = period; + _NaN = useNaN; + _scFast = 2.0 / (((period < fast) ? period : fast) + 1); + _scSlow = 2.0 / (slow + 1); + _lastkama = _lastlastkama = 0; + Name = $"KAMA({period})"; + } + public KAMA_Series(TSeries source, int period, int fast, int slow, bool useNaN) : this(period, fast, slow, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public KAMA_Series() : this(period: 0, fast: 2, slow: 30, useNaN: false) { } + public KAMA_Series(int period) : this(period: period, fast: 2, slow: 30, useNaN: false) { } + public KAMA_Series(TBars source) : this(source.Close, period: 0, fast: 2, slow: 30, useNaN: false) { } + public KAMA_Series(TBars source, int period) : this(source.Close, period: period, fast: 2, slow: 30, useNaN: false) { } + public KAMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period: period, fast: 2, slow: 30, useNaN: useNaN) { } + public KAMA_Series(TSeries source) : this(source, period: 0, fast: 2, slow: 30, useNaN: false) { } + public KAMA_Series(TSeries source, int period) : this(source: source, period: period, fast: 2, slow: 30, useNaN: false) { } + public KAMA_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, fast: 2, slow: 30, useNaN: useNaN) { } + public KAMA_Series(TSeries source, int period, int fast, int slow) : this(source: source, period: period, fast: fast, slow: slow, useNaN: false) { } + + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (double.IsNaN(TValue.v)) { + return base.Add((TValue.t, Double.NaN), update); + } + + if (update) { _lastkama = _lastlastkama; } + else { _lastlastkama = _lastkama; } + BufferTrim(buffer: _buffer, value: TValue.v, period: _period + 1, update: update); + + double _kama = 0; + if (this.Count < _period) { _kama = TValue.v; } + else { + double _change = Math.Abs(_buffer[^1] - _buffer[(_buffer.Count > _period + 1) ? 1 : 0]); + double _sumpv = 0; + for (int i = 1; i < _buffer.Count; i++) { _sumpv += Math.Abs(_buffer[(_buffer.Count > 0) ? i : 0] - _buffer[i - 1]); } + double _er = (_sumpv == 0) ? 0 : _change / _sumpv; + double _sc = (_er * (_scFast - _scSlow)) + _scSlow; + _kama = (_lastkama + (_sc * _sc * (TValue.v - _lastkama))); + } + _lastkama = _kama; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _kama); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + _lastkama = _lastlastkama = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/KURTOSIS_Series.cs b/Calculations/_Updated/KURTOSIS_Series.cs index 71b8cfba..2c4e10c8 100644 --- a/Calculations/_Updated/KURTOSIS_Series.cs +++ b/Calculations/_Updated/KURTOSIS_Series.cs @@ -1,96 +1,96 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Linq; - -/* -KURTOSIS: Kurtosis of population - Kurtosis characterizes the relative peakedness or flatness of a distribution - compared with the normal distribution. Positive kurtosis indicates a relatively - peaked distribution. Negative kurtosis indicates a relatively flat distribution. - - The normal curve is called Mesokurtic curve. If the curve of a distribution is - more outlier prone (or heavier-tailed) than a normal or mesokurtic curve then - it is referred to as a Leptokurtic curve. If a curve is less outlier prone (or - lighter-tailed) than a normal curve, it is called as a platykurtic curve. - -Calculation: - sum4 = Σ(close-SMA)^4 - sum2 = (Σ(close-SMA)^2)^2 - KURTOSIS = length * (sum4/sum2) - -Sources: - https://en.wikipedia.org/wiki/Kurtosis - https://stats.oarc.ucla.edu/other/mult-pkg/faq/general/faq-whats-with-the-different-formulas-for-kurtosis/ - - */ - -public class KURTOSIS_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly System.Collections.Generic.List _buffer = new(); - - //core constructors - public KURTOSIS_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"KURTOSIS({period})"; - } - public KURTOSIS_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public KURTOSIS_Series() : this(period: 0, useNaN: false) { } - public KURTOSIS_Series(int period) : this(period: period, useNaN: false) { } - public KURTOSIS_Series(TSeries source) : this(source, period: 0, useNaN: false) { } - public KURTOSIS_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _n = _buffer.Count; - double _avg = _buffer.Average(); - - double _s2 = 0; - double _s4 = 0; - for (int i = 0; i < this._buffer.Count; i++) { - _s2 += (_buffer[i] - _avg) * (_buffer[i] - _avg); - _s4 += (_buffer[i] - _avg) * (_buffer[i] - _avg) * (_buffer[i] - _avg) * (_buffer[i] - _avg); - } - - double _Vx = _s2 / (_n - 1); - double _kurt = (_n > 3) ? - (_n * (_n + 1) * _s4) / (_Vx * _Vx * (_n - 3) * (_n - 1) * (_n - 2)) - (3 * (_n - 1) * (_n - 1) / ((_n - 2) * (_n - 3))) //using Sheskin Algo - : (_s2 * _s2) / _n - 3; //using Snedecor and Cochran (1967) algo - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _kurt); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Linq; + +/* +KURTOSIS: Kurtosis of population + Kurtosis characterizes the relative peakedness or flatness of a distribution + compared with the normal distribution. Positive kurtosis indicates a relatively + peaked distribution. Negative kurtosis indicates a relatively flat distribution. + + The normal curve is called Mesokurtic curve. If the curve of a distribution is + more outlier prone (or heavier-tailed) than a normal or mesokurtic curve then + it is referred to as a Leptokurtic curve. If a curve is less outlier prone (or + lighter-tailed) than a normal curve, it is called as a platykurtic curve. + +Calculation: + sum4 = Σ(close-SMA)^4 + sum2 = (Σ(close-SMA)^2)^2 + KURTOSIS = length * (sum4/sum2) + +Sources: + https://en.wikipedia.org/wiki/Kurtosis + https://stats.oarc.ucla.edu/other/mult-pkg/faq/general/faq-whats-with-the-different-formulas-for-kurtosis/ + + */ + +public class KURTOSIS_Series : TSeries { + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly System.Collections.Generic.List _buffer = new(); + + //core constructors + public KURTOSIS_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"KURTOSIS({period})"; + } + public KURTOSIS_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public KURTOSIS_Series() : this(period: 0, useNaN: false) { } + public KURTOSIS_Series(int period) : this(period: period, useNaN: false) { } + public KURTOSIS_Series(TSeries source) : this(source, period: 0, useNaN: false) { } + public KURTOSIS_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (double.IsNaN(TValue.v)) { + return base.Add((TValue.t, Double.NaN), update); + } + + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + double _n = _buffer.Count; + double _avg = _buffer.Average(); + + double _s2 = 0; + double _s4 = 0; + for (int i = 0; i < this._buffer.Count; i++) { + _s2 += (_buffer[i] - _avg) * (_buffer[i] - _avg); + _s4 += (_buffer[i] - _avg) * (_buffer[i] - _avg) * (_buffer[i] - _avg) * (_buffer[i] - _avg); + } + + double _Vx = _s2 / (_n - 1); + double _kurt = (_n > 3) ? + (_n * (_n + 1) * _s4) / (_Vx * _Vx * (_n - 3) * (_n - 1) * (_n - 2)) - (3 * (_n - 1) * (_n - 1) / ((_n - 2) * (_n - 3))) //using Sheskin Algo + : (_s2 * _s2) / _n - 3; //using Snedecor and Cochran (1967) algo + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _kurt); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MACD_Series.cs b/Calculations/_Updated/MACD_Series.cs index a81ce1a9..20d8c86b 100644 --- a/Calculations/_Updated/MACD_Series.cs +++ b/Calculations/_Updated/MACD_Series.cs @@ -1,78 +1,78 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -MACD: Moving Average Convergence/Divergence - Moving average convergence divergence (MACD) is a trend-following momentum - indicator that shows the relationship between two moving averages of a series. - The MACD is calculated by subtracting the 26-period exponential moving average (EMA) - from the 12-period EMA. MACD Signal is 9-day EMA of MACD. - - */ - -public class MACD_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - - protected readonly int _slow, _fast, _signal; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly EMA_Series _TSlow; - private readonly EMA_Series _TFast; - public EMA_Series Signal { get; } - - //core constructors - public MACD_Series(int slow = 26, int fast = 12, int signal = 9, bool useNaN = false) { - _slow = slow; - _fast = fast; - _signal = signal; - _NaN = useNaN; - Name = $"MACD({slow},{fast},{signal})"; - _TSlow = new(slow, useNaN:false, useSMA:true); - _TFast = new(fast, useNaN: false, useSMA: true); - Signal = new(signal, useNaN: false, useSMA: true); - } - public MACD_Series(TSeries source, int slow, int fast, int signal, bool useNaN) : this(slow, fast, signal, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MACD_Series(TSeries source) : this(source:source, slow:26, fast:12, signal:9 , useNaN:false) { } - public MACD_Series(TSeries source, int slow, int fast, int signal) : this(source: source, slow: slow, fast:fast, signal:signal, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - - var _sslow = _TSlow.Add(TValue,update); - var _sfast = _TFast.Add(TValue, update); - Signal.Add((TValue.t, _sfast.v-_sslow.v)); - - var res = (TValue.t, Count < _fast - 1 && _NaN ? double.NaN : _sfast.v-_sslow.v); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +MACD: Moving Average Convergence/Divergence + Moving average convergence divergence (MACD) is a trend-following momentum + indicator that shows the relationship between two moving averages of a series. + The MACD is calculated by subtracting the 26-period exponential moving average (EMA) + from the 12-period EMA. MACD Signal is 9-day EMA of MACD. + + */ + +public class MACD_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + + protected readonly int _slow, _fast, _signal; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly EMA_Series _TSlow; + private readonly EMA_Series _TFast; + public EMA_Series Signal { get; } + + //core constructors + public MACD_Series(int slow = 26, int fast = 12, int signal = 9, bool useNaN = false) { + _slow = slow; + _fast = fast; + _signal = signal; + _NaN = useNaN; + Name = $"MACD({slow},{fast},{signal})"; + _TSlow = new(slow, useNaN:false, useSMA:true); + _TFast = new(fast, useNaN: false, useSMA: true); + Signal = new(signal, useNaN: false, useSMA: true); + } + public MACD_Series(TSeries source, int slow, int fast, int signal, bool useNaN) : this(slow, fast, signal, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MACD_Series(TSeries source) : this(source:source, slow:26, fast:12, signal:9 , useNaN:false) { } + public MACD_Series(TSeries source, int slow, int fast, int signal) : this(source: source, slow: slow, fast:fast, signal:signal, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (double.IsNaN(TValue.v)) { + return base.Add((TValue.t, Double.NaN), update); + } + + var _sslow = _TSlow.Add(TValue,update); + var _sfast = _TFast.Add(TValue, update); + Signal.Add((TValue.t, _sfast.v-_sslow.v)); + + var res = (TValue.t, Count < _fast - 1 && _NaN ? double.NaN : _sfast.v-_sslow.v); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MAD_Series.cs b/Calculations/_Updated/MAD_Series.cs index 0378f1c5..4f18a04b 100644 --- a/Calculations/_Updated/MAD_Series.cs +++ b/Calculations/_Updated/MAD_Series.cs @@ -1,79 +1,79 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -MAD: Mean Absolute Deviation - Also known as AAD - Average Absolute Deviation, to differentiate it from Median Absolute Deviation - MAD defines the degree of variation across the series. - -Calculation: - MAD = Σ(|close-SMA|) / period - -Sources: - https://en.wikipedia.org/wiki/Average_absolute_deviation - - */ - -public class MAD_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public MAD_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MAD({period})"; - } - public MAD_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MAD_Series() : this(period: 0, useNaN: false) { } - public MAD_Series(int period) : this(period: period, useNaN: false) { } - public MAD_Series(TBars source) : this(source.Close, 0, false) { } - public MAD_Series(TBars source, int period) : this(source.Close, period, false) { } - public MAD_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MAD_Series(TSeries source) : this(source, 0, false) { } - public MAD_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _sma = _buffer.Average(); - double _mad = 0; - for (int i = 0; i < _buffer.Count; i++) { _mad += Math.Abs(_buffer[i] - _sma); } - _mad /= this._buffer.Count; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mad); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +MAD: Mean Absolute Deviation + Also known as AAD - Average Absolute Deviation, to differentiate it from Median Absolute Deviation + MAD defines the degree of variation across the series. + +Calculation: + MAD = Σ(|close-SMA|) / period + +Sources: + https://en.wikipedia.org/wiki/Average_absolute_deviation + + */ + +public class MAD_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public MAD_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"MAD({period})"; + } + public MAD_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MAD_Series() : this(period: 0, useNaN: false) { } + public MAD_Series(int period) : this(period: period, useNaN: false) { } + public MAD_Series(TBars source) : this(source.Close, 0, false) { } + public MAD_Series(TBars source, int period) : this(source.Close, period, false) { } + public MAD_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MAD_Series(TSeries source) : this(source, 0, false) { } + public MAD_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _sma = _buffer.Average(); + double _mad = 0; + for (int i = 0; i < _buffer.Count; i++) { _mad += Math.Abs(_buffer[i] - _sma); } + _mad /= this._buffer.Count; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mad); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MAE_Series.cs b/Calculations/_Updated/MAE_Series.cs index b01839d2..9bf7a6a9 100644 --- a/Calculations/_Updated/MAE_Series.cs +++ b/Calculations/_Updated/MAE_Series.cs @@ -1,77 +1,77 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -MAE: Mean Absolute Error - Defined as a Mean (Average) of the absolute difference between actual and estimated values. - MAE = (1/n) * Σ|y_i - MA_i| - -Sources: - https://en.wikipedia.org/wiki/Mean_absolute_error - - */ - -public class MAE_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public MAE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MSE({period})"; - } - public MAE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MAE_Series() : this(period: 0, useNaN: false) { } - public MAE_Series(int period) : this(period: period, useNaN: false) { } - public MAE_Series(TBars source) : this(source.Close, 0, false) { } - public MAE_Series(TBars source, int period) : this(source.Close, period, false) { } - public MAE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MAE_Series(TSeries source) : this(source, 0, false) { } - public MAE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _sma = _buffer.Average(); - - double _mae = 0; - for (int i = 0; i < _buffer.Count; i++) { _mae += Math.Abs(_buffer[i] - _sma); } - _mae /= this._buffer.Count; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mae); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +MAE: Mean Absolute Error + Defined as a Mean (Average) of the absolute difference between actual and estimated values. + MAE = (1/n) * Σ|y_i - MA_i| + +Sources: + https://en.wikipedia.org/wiki/Mean_absolute_error + + */ + +public class MAE_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public MAE_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"MSE({period})"; + } + public MAE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MAE_Series() : this(period: 0, useNaN: false) { } + public MAE_Series(int period) : this(period: period, useNaN: false) { } + public MAE_Series(TBars source) : this(source.Close, 0, false) { } + public MAE_Series(TBars source, int period) : this(source.Close, period, false) { } + public MAE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MAE_Series(TSeries source) : this(source, 0, false) { } + public MAE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _sma = _buffer.Average(); + + double _mae = 0; + for (int i = 0; i < _buffer.Count; i++) { _mae += Math.Abs(_buffer[i] - _sma); } + _mae /= this._buffer.Count; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mae); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MAMA_Series.cs b/Calculations/_Updated/MAMA_Series.cs index 52bd5d7b..c4dd2a51 100644 --- a/Calculations/_Updated/MAMA_Series.cs +++ b/Calculations/_Updated/MAMA_Series.cs @@ -1,189 +1,189 @@ -namespace QuanTAlib; - -using System; -using System.Linq; - -/* -MAMA: MESA Adaptive Moving Average - Created by John Ehlers, the MAMA indicator is a 5-period adaptive moving average of - high/low price that uses classic electrical radio-frequency signal processing algorithms - to reduce noise. - - KAMAi = KAMAi - 1 + SC * ( price - KAMAi-1 ) - -Sources: - https://mesasoftware.com/papers/MAMA.pdf - https://www.tradingview.com/script/foQxLbU3-Ehlers-MESA-Adaptive-Moving-Average-LazyBear/ - - */ - -public class MAMA_Series : TSeries { - private int _len; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - private double sumPr; - private double fastl, slowl; - private (double i, double i1, double i2, double i3, double i4, double i5, double i6, double io) pr, i1, q1, sm, dt; - private (double i, double i1, double io) i2, q2, re, im, pd, ph, mama, fama; - public TSeries Fama { get; } - private double mamaseed, famaseed; - - //core constructors - - public MAMA_Series(double fastlimit, double slowlimit, bool useNaN) { - _period = (int)(2 / fastlimit) - 1; - fastl = fastlimit; - slowl = slowlimit; - Fama = new TSeries(); - _NaN = useNaN; - Name = $"MAMA({_period})"; - _len = 0; - } - public MAMA_Series(TSeries source, double fastlimit, double slowlimit, bool useNaN = false) : this(fastlimit, slowlimit, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MAMA_Series() : this(period: 0, useNaN: false) { } - public MAMA_Series(int period) : this(period, useNaN: false) { } - public MAMA_Series(int period, bool useNaN) : this(fastlimit: 2 / (period + 1), slowlimit: 0.2 / (period + 1), useNaN) { - _period = period; - } - public MAMA_Series(TBars source) : this(source.Close, period: 0, useNaN: false) { } - public MAMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public MAMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MAMA_Series(TSeries source, int period) : this(source, period, false) { } - public MAMA_Series(TSeries source, int period, bool useNaN) : this(source, fastlimit: 2 / ((double)period + 1), slowlimit: 0.2 / ((double)period + 1), useNaN: useNaN) { } - - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - if (!update) { - // roll forward (oldx = x) - pr.io = pr.i6; pr.i6 = pr.i5; pr.i5 = pr.i4; pr.i4 = pr.i3; pr.i3 = pr.i2; pr.i2 = pr.i1; pr.i1 = pr.i; - i1.io = i1.i6; i1.i6 = i1.i5; i1.i5 = i1.i4; i1.i4 = i1.i3; i1.i3 = i1.i2; i1.i2 = i1.i1; i1.i1 = i1.i; - q1.io = q1.i6; q1.i6 = q1.i5; q1.i5 = q1.i4; q1.i4 = q1.i3; q1.i3 = q1.i2; q1.i2 = q1.i1; q1.i1 = q1.i; - dt.io = dt.i6; dt.i6 = dt.i5; dt.i5 = dt.i4; dt.i4 = dt.i3; dt.i3 = dt.i2; dt.i2 = dt.i1; dt.i1 = dt.i; - sm.io = sm.i6; sm.i6 = sm.i5; sm.i5 = sm.i4; sm.i4 = sm.i3; sm.i3 = sm.i2; sm.i2 = sm.i1; sm.i1 = sm.i; - i2.io = i2.i1; i2.i1 = i2.i; q2.io = q2.i1; q2.i1 = q2.i; - re.io = re.i1; re.i1 = re.i; im.io = im.i1; im.i1 = im.i; - pd.io = pd.i1; pd.i1 = pd.i; ph.io = ph.i1; ph.i1 = ph.i; - mama.io = mama.i1; mama.i1 = mama.i; - fama.io = fama.i1; - fama.i1 = fama.i; - _len++; - } - if (_period == 0) { - fastl = 2 / (double)_len; - slowl = fastl * 0.1; - } - if (_period == 1) { - fastl = 1; - slowl = 1; - } - var i = _len - 1; - pr.i = TValue.v; - if (i > 5) { - var adj = 0.075 * pd.i1 + 0.54; - - // smooth and detrender - sm.i = (4 * pr.i + 3 * pr.i1 + 2 * pr.i2 + pr.i3) / 10; - dt.i = (0.0962 * sm.i + 0.5769 * sm.i2 - 0.5769 * sm.i4 - 0.0962 * sm.i6) * adj; - - // in-phase and quadrature - q1.i = (0.0962 * dt.i + 0.5769 * dt.i2 - 0.5769 * dt.i4 - 0.0962 * dt.i6) * adj; - i1.i = dt.i3; - - // advance the phases by 90 degrees - double jI = (0.0962 * i1.i + 0.5769 * i1.i2 - 0.5769 * i1.i4 - 0.0962 * i1.i6) * adj; - double jQ = (0.0962 * q1.i + 0.5769 * q1.i2 - 0.5769 * q1.i4 - 0.0962 * q1.i6) * adj; - - // phasor addition for 3-bar averaging - i2.i = i1.i - jQ; - q2.i = q1.i + jI; - - i2.i = 0.2 * i2.i + 0.8 * i2.i1; // smoothing it - q2.i = 0.2 * q2.i + 0.8 * q2.i1; - - // homodyne discriminator - re.i = i2.i * i2.i1 + q2.i * q2.i1; - im.i = i2.i * q2.i1 - q2.i * i2.i1; - - re.i = 0.2 * re.i + 0.8 * re.i1; // smoothing it - im.i = 0.2 * im.i + 0.8 * im.i1; - - // calculate period - pd.i = im.i != 0 && re.i != 0 ? 6.283185307179586 / Math.Atan(im.i / re.i) : 0d; - - // adjust period to thresholds - pd.i = pd.i > 1.5 * pd.i1 ? 1.5 * pd.i1 : pd.i; - pd.i = pd.i < 0.67 * pd.i1 ? 0.67 * pd.i1 : pd.i; - pd.i = pd.i < 6d ? 6d : pd.i; - pd.i = pd.i > 50d ? 50d : pd.i; - - // smooth the period - pd.i = 0.2 * pd.i + 0.8 * pd.i1; - - // determine phase position - ph.i = i1.i != 0 ? Math.Atan(q1.i / i1.i) * 57.29577951308232 : 0; - - // change in phase - var delta = Math.Max(ph.i1 - ph.i, 1d); - - // adaptive alpha value - var alpha = Math.Max(fastl / delta, slowl); - - // final indicators - mama.i = alpha * (pr.i - mama.i1) + mama.i1; - fama.i = 0.5d * alpha * (mama.i - fama.i1) + fama.i1; - } - else { - sumPr += pr.i; - pd.i = sm.i = dt.i = i1.i = q1.i = i2.i = q2.i = re.i = im.i = ph.i = 0; - mama.i = fama.i = sumPr / (i + 1); - - if (_len == 1) { - mamaseed = famaseed = TValue.v; - } - else { - mamaseed = fastl * (TValue.v - mamaseed) + mamaseed; - famaseed = slowl * (TValue.v - famaseed) + famaseed; - } - } - - double _fama = (i > 5) ? fama.i : famaseed; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _fama); - Fama.Add(res, update); - double _mama = (i > 5) ? mama.i : mamaseed; - res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mama); - return base.Add(res, update); - } - - //variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _len = 0; - } +namespace QuanTAlib; + +using System; +using System.Linq; + +/* +MAMA: MESA Adaptive Moving Average + Created by John Ehlers, the MAMA indicator is a 5-period adaptive moving average of + high/low price that uses classic electrical radio-frequency signal processing algorithms + to reduce noise. + + KAMAi = KAMAi - 1 + SC * ( price - KAMAi-1 ) + +Sources: + https://mesasoftware.com/papers/MAMA.pdf + https://www.tradingview.com/script/foQxLbU3-Ehlers-MESA-Adaptive-Moving-Average-LazyBear/ + + */ + +public class MAMA_Series : TSeries { + private int _len; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + private double sumPr; + private double fastl, slowl; + private (double i, double i1, double i2, double i3, double i4, double i5, double i6, double io) pr, i1, q1, sm, dt; + private (double i, double i1, double io) i2, q2, re, im, pd, ph, mama, fama; + public TSeries Fama { get; } + private double mamaseed, famaseed; + + //core constructors + + public MAMA_Series(double fastlimit, double slowlimit, bool useNaN) { + _period = (int)(2 / fastlimit) - 1; + fastl = fastlimit; + slowl = slowlimit; + Fama = new TSeries(); + _NaN = useNaN; + Name = $"MAMA({_period})"; + _len = 0; + } + public MAMA_Series(TSeries source, double fastlimit, double slowlimit, bool useNaN = false) : this(fastlimit, slowlimit, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MAMA_Series() : this(period: 0, useNaN: false) { } + public MAMA_Series(int period) : this(period, useNaN: false) { } + public MAMA_Series(int period, bool useNaN) : this(fastlimit: 2 / (period + 1), slowlimit: 0.2 / (period + 1), useNaN) { + _period = period; + } + public MAMA_Series(TBars source) : this(source.Close, period: 0, useNaN: false) { } + public MAMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public MAMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MAMA_Series(TSeries source, int period) : this(source, period, false) { } + public MAMA_Series(TSeries source, int period, bool useNaN) : this(source, fastlimit: 2 / ((double)period + 1), slowlimit: 0.2 / ((double)period + 1), useNaN: useNaN) { } + + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (double.IsNaN(TValue.v)) { + return base.Add((TValue.t, Double.NaN), update); + } + if (!update) { + // roll forward (oldx = x) + pr.io = pr.i6; pr.i6 = pr.i5; pr.i5 = pr.i4; pr.i4 = pr.i3; pr.i3 = pr.i2; pr.i2 = pr.i1; pr.i1 = pr.i; + i1.io = i1.i6; i1.i6 = i1.i5; i1.i5 = i1.i4; i1.i4 = i1.i3; i1.i3 = i1.i2; i1.i2 = i1.i1; i1.i1 = i1.i; + q1.io = q1.i6; q1.i6 = q1.i5; q1.i5 = q1.i4; q1.i4 = q1.i3; q1.i3 = q1.i2; q1.i2 = q1.i1; q1.i1 = q1.i; + dt.io = dt.i6; dt.i6 = dt.i5; dt.i5 = dt.i4; dt.i4 = dt.i3; dt.i3 = dt.i2; dt.i2 = dt.i1; dt.i1 = dt.i; + sm.io = sm.i6; sm.i6 = sm.i5; sm.i5 = sm.i4; sm.i4 = sm.i3; sm.i3 = sm.i2; sm.i2 = sm.i1; sm.i1 = sm.i; + i2.io = i2.i1; i2.i1 = i2.i; q2.io = q2.i1; q2.i1 = q2.i; + re.io = re.i1; re.i1 = re.i; im.io = im.i1; im.i1 = im.i; + pd.io = pd.i1; pd.i1 = pd.i; ph.io = ph.i1; ph.i1 = ph.i; + mama.io = mama.i1; mama.i1 = mama.i; + fama.io = fama.i1; + fama.i1 = fama.i; + _len++; + } + if (_period == 0) { + fastl = 2 / (double)_len; + slowl = fastl * 0.1; + } + if (_period == 1) { + fastl = 1; + slowl = 1; + } + var i = _len - 1; + pr.i = TValue.v; + if (i > 5) { + var adj = 0.075 * pd.i1 + 0.54; + + // smooth and detrender + sm.i = (4 * pr.i + 3 * pr.i1 + 2 * pr.i2 + pr.i3) / 10; + dt.i = (0.0962 * sm.i + 0.5769 * sm.i2 - 0.5769 * sm.i4 - 0.0962 * sm.i6) * adj; + + // in-phase and quadrature + q1.i = (0.0962 * dt.i + 0.5769 * dt.i2 - 0.5769 * dt.i4 - 0.0962 * dt.i6) * adj; + i1.i = dt.i3; + + // advance the phases by 90 degrees + double jI = (0.0962 * i1.i + 0.5769 * i1.i2 - 0.5769 * i1.i4 - 0.0962 * i1.i6) * adj; + double jQ = (0.0962 * q1.i + 0.5769 * q1.i2 - 0.5769 * q1.i4 - 0.0962 * q1.i6) * adj; + + // phasor addition for 3-bar averaging + i2.i = i1.i - jQ; + q2.i = q1.i + jI; + + i2.i = 0.2 * i2.i + 0.8 * i2.i1; // smoothing it + q2.i = 0.2 * q2.i + 0.8 * q2.i1; + + // homodyne discriminator + re.i = i2.i * i2.i1 + q2.i * q2.i1; + im.i = i2.i * q2.i1 - q2.i * i2.i1; + + re.i = 0.2 * re.i + 0.8 * re.i1; // smoothing it + im.i = 0.2 * im.i + 0.8 * im.i1; + + // calculate period + pd.i = im.i != 0 && re.i != 0 ? 6.283185307179586 / Math.Atan(im.i / re.i) : 0d; + + // adjust period to thresholds + pd.i = pd.i > 1.5 * pd.i1 ? 1.5 * pd.i1 : pd.i; + pd.i = pd.i < 0.67 * pd.i1 ? 0.67 * pd.i1 : pd.i; + pd.i = pd.i < 6d ? 6d : pd.i; + pd.i = pd.i > 50d ? 50d : pd.i; + + // smooth the period + pd.i = 0.2 * pd.i + 0.8 * pd.i1; + + // determine phase position + ph.i = i1.i != 0 ? Math.Atan(q1.i / i1.i) * 57.29577951308232 : 0; + + // change in phase + var delta = Math.Max(ph.i1 - ph.i, 1d); + + // adaptive alpha value + var alpha = Math.Max(fastl / delta, slowl); + + // final indicators + mama.i = alpha * (pr.i - mama.i1) + mama.i1; + fama.i = 0.5d * alpha * (mama.i - fama.i1) + fama.i1; + } + else { + sumPr += pr.i; + pd.i = sm.i = dt.i = i1.i = q1.i = i2.i = q2.i = re.i = im.i = ph.i = 0; + mama.i = fama.i = sumPr / (i + 1); + + if (_len == 1) { + mamaseed = famaseed = TValue.v; + } + else { + mamaseed = fastl * (TValue.v - mamaseed) + mamaseed; + famaseed = slowl * (TValue.v - famaseed) + famaseed; + } + } + + double _fama = (i > 5) ? fama.i : famaseed; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _fama); + Fama.Add(res, update); + double _mama = (i > 5) ? mama.i : mamaseed; + res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mama); + return base.Add(res, update); + } + + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/MAPE_Series.cs b/Calculations/_Updated/MAPE_Series.cs index 43a6a9c6..944daf86 100644 --- a/Calculations/_Updated/MAPE_Series.cs +++ b/Calculations/_Updated/MAPE_Series.cs @@ -1,85 +1,85 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -MAPE: Mean Absolute Percentage Error - Measures the size of the error in percentage terms - -Calculation: - MAPE = Σ(|close – SMA| / |close|) / n - -Sources: - https://en.wikipedia.org/wiki/Mean_absolute_percentage_error - -Remark: - returns infinity if any of observations is 0. - Use SMAPE or WMAPE instead to avoid division-by-zero in MAPE - - */ - -public class MAPE_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public MAPE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MAPE({period})"; - } - public MAPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MAPE_Series() : this(period: 0, useNaN: false) { } - public MAPE_Series(int period) : this(period: period, useNaN: false) { } - public MAPE_Series(TBars source) : this(source.Close, 0, false) { } - public MAPE_Series(TBars source, int period) : this(source.Close, period, false) { } - public MAPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MAPE_Series(TSeries source) : this(source, 0, false) { } - public MAPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _sma = _buffer.Average(); - - double _mape = 0; - for (int i = 0; i < _buffer.Count; i++) { - _mape += (_buffer[i] != 0) ? Math.Abs(_buffer[i] - _sma) / Math.Abs(_buffer[i]) : double.PositiveInfinity; - } - _mape /= (_buffer.Count > 0) ? _buffer.Count : 1; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mape); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +MAPE: Mean Absolute Percentage Error + Measures the size of the error in percentage terms + +Calculation: + MAPE = Σ(|close – SMA| / |close|) / n + +Sources: + https://en.wikipedia.org/wiki/Mean_absolute_percentage_error + +Remark: + returns infinity if any of observations is 0. + Use SMAPE or WMAPE instead to avoid division-by-zero in MAPE + + */ + +public class MAPE_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public MAPE_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"MAPE({period})"; + } + public MAPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MAPE_Series() : this(period: 0, useNaN: false) { } + public MAPE_Series(int period) : this(period: period, useNaN: false) { } + public MAPE_Series(TBars source) : this(source.Close, 0, false) { } + public MAPE_Series(TBars source, int period) : this(source.Close, period, false) { } + public MAPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MAPE_Series(TSeries source) : this(source, 0, false) { } + public MAPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _sma = _buffer.Average(); + + double _mape = 0; + for (int i = 0; i < _buffer.Count; i++) { + _mape += (_buffer[i] != 0) ? Math.Abs(_buffer[i] - _sma) / Math.Abs(_buffer[i]) : double.PositiveInfinity; + } + _mape /= (_buffer.Count > 0) ? _buffer.Count : 1; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mape); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MAX_Series.cs b/Calculations/_Updated/MAX_Series.cs index f199a329..8a2facff 100644 --- a/Calculations/_Updated/MAX_Series.cs +++ b/Calculations/_Updated/MAX_Series.cs @@ -1,68 +1,68 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -MAX - Maximum value in the given period in the series. - If period = 0 => period = full length of the series - - */ - -public class MAX_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public MAX_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MAX({period})"; - } - public MAX_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MAX_Series() : this(period: 0, useNaN: false) { } - public MAX_Series(int period) : this(period: period, useNaN: false) { } - public MAX_Series(TBars source) : this(source.Close, 0, false) { } - public MAX_Series(TBars source, int period) : this(source.Close, period, false) { } - public MAX_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MAX_Series(TSeries source) : this(source, 0, false) { } - public MAX_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _max= _buffer.Max(); - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _max); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +MAX - Maximum value in the given period in the series. + If period = 0 => period = full length of the series + + */ + +public class MAX_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public MAX_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"MAX({period})"; + } + public MAX_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MAX_Series() : this(period: 0, useNaN: false) { } + public MAX_Series(int period) : this(period: period, useNaN: false) { } + public MAX_Series(TBars source) : this(source.Close, 0, false) { } + public MAX_Series(TBars source, int period) : this(source.Close, period, false) { } + public MAX_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MAX_Series(TSeries source) : this(source, 0, false) { } + public MAX_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _max= _buffer.Max(); + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _max); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MEDIAN_Series.cs b/Calculations/_Updated/MEDIAN_Series.cs index 0df36399..fde7c634 100644 --- a/Calculations/_Updated/MEDIAN_Series.cs +++ b/Calculations/_Updated/MEDIAN_Series.cs @@ -1,86 +1,86 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -MED - Median value - Median of numbers is the middlemost value of the given set of numbers. - It separates the higher half and the lower half of a given data sample. - At least half of the observations are smaller than or equal to median - and at least half of the observations are greater than or equal to the median. - - If the number of values is odd, the middlemost observation of the sorted - list is the median of the given data. If the number of values is even, - median is the average of (n/2)th and [(n/2) + 1]th values of the sorted list. - - If period = 0 => period is max - -Sources: - https://corporatefinanceinstitute.com/resources/knowledge/other/median/ - https://en.wikipedia.org/wiki/Median - - */ - -public class MEDIAN_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public MEDIAN_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MEDIAN({period})"; - } - public MEDIAN_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MEDIAN_Series() : this(period: 0, useNaN: false) { } - public MEDIAN_Series(int period) : this(period: period, useNaN: false) { } - public MEDIAN_Series(TBars source) : this(source.Close, 0, false) { } - public MEDIAN_Series(TBars source, int period) : this(source.Close, period, false) { } - public MEDIAN_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MEDIAN_Series(TSeries source) : this(source, 0, false) { } - public MEDIAN_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - System.Collections.Generic.List _s = new(this._buffer); - _s.Sort(); - int _p1 = _s.Count / 2; - int _p2 = Math.Max(0, (_s.Count / 2) - 1); - double _med = (_s.Count % 2 != 0) ? _s[_p1] : (_s[_p1] + _s[_p2]) / 2; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _med); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +MED - Median value + Median of numbers is the middlemost value of the given set of numbers. + It separates the higher half and the lower half of a given data sample. + At least half of the observations are smaller than or equal to median + and at least half of the observations are greater than or equal to the median. + + If the number of values is odd, the middlemost observation of the sorted + list is the median of the given data. If the number of values is even, + median is the average of (n/2)th and [(n/2) + 1]th values of the sorted list. + + If period = 0 => period is max + +Sources: + https://corporatefinanceinstitute.com/resources/knowledge/other/median/ + https://en.wikipedia.org/wiki/Median + + */ + +public class MEDIAN_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public MEDIAN_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"MEDIAN({period})"; + } + public MEDIAN_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MEDIAN_Series() : this(period: 0, useNaN: false) { } + public MEDIAN_Series(int period) : this(period: period, useNaN: false) { } + public MEDIAN_Series(TBars source) : this(source.Close, 0, false) { } + public MEDIAN_Series(TBars source, int period) : this(source.Close, period, false) { } + public MEDIAN_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MEDIAN_Series(TSeries source) : this(source, 0, false) { } + public MEDIAN_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + System.Collections.Generic.List _s = new(this._buffer); + _s.Sort(); + int _p1 = _s.Count / 2; + int _p2 = Math.Max(0, (_s.Count / 2) - 1); + double _med = (_s.Count % 2 != 0) ? _s[_p1] : (_s[_p1] + _s[_p2]) / 2; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _med); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MIDPOINT_Series.cs b/Calculations/_Updated/MIDPOINT_Series.cs index 46bdd221..c410be23 100644 --- a/Calculations/_Updated/MIDPOINT_Series.cs +++ b/Calculations/_Updated/MIDPOINT_Series.cs @@ -1,72 +1,72 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -MIDPOINT: Midpoint value (max+min)/2 in the given period in the series. - If period = 0 => period = full length of the series - -Sources: - https://thefaqblog.com/what-is-the-midpoint-in-statistics/ - - */ - -public class MIDPOINT_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public MIDPOINT_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MIDPOINT({period})"; - } - public MIDPOINT_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MIDPOINT_Series() : this(period: 0, useNaN: false) { } - public MIDPOINT_Series(int period) : this(period: period, useNaN: false) { } - public MIDPOINT_Series(TBars source) : this(source.Close, 0, false) { } - public MIDPOINT_Series(TBars source, int period) : this(source.Close, period, false) { } - public MIDPOINT_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MIDPOINT_Series(TSeries source) : this(source, 0, false) { } - public MIDPOINT_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _max= _buffer.Max(); - double _min = _buffer.Min(); - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : (_max+_min)*0.5); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +MIDPOINT: Midpoint value (max+min)/2 in the given period in the series. + If period = 0 => period = full length of the series + +Sources: + https://thefaqblog.com/what-is-the-midpoint-in-statistics/ + + */ + +public class MIDPOINT_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public MIDPOINT_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"MIDPOINT({period})"; + } + public MIDPOINT_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MIDPOINT_Series() : this(period: 0, useNaN: false) { } + public MIDPOINT_Series(int period) : this(period: period, useNaN: false) { } + public MIDPOINT_Series(TBars source) : this(source.Close, 0, false) { } + public MIDPOINT_Series(TBars source, int period) : this(source.Close, period, false) { } + public MIDPOINT_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MIDPOINT_Series(TSeries source) : this(source, 0, false) { } + public MIDPOINT_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _max= _buffer.Max(); + double _min = _buffer.Min(); + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : (_max+_min)*0.5); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MIDPRICE_Series.cs b/Calculations/_Updated/MIDPRICE_Series.cs index 1bb914a8..5b2bf73f 100644 --- a/Calculations/_Updated/MIDPRICE_Series.cs +++ b/Calculations/_Updated/MIDPRICE_Series.cs @@ -1,65 +1,65 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Linq; - -/* -MIDPRICE: Midpoint price (highhest high + lowest low)/2 in the given period in the series. - If period = 0 => period = full length of the series - - */ - -public class MIDPRICE_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TBars _data; - private readonly System.Collections.Generic.List _bufferhi = new(); - private readonly System.Collections.Generic.List _bufferlo = new(); - - //core constructors - public MIDPRICE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MIDPRICE({period})"; - } - public MIDPRICE_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(data: _data); - } - public MIDPRICE_Series() : this(period: 2, useNaN: false) { } - public MIDPRICE_Series(int period) : this(period: period, useNaN: false) { } - public MIDPRICE_Series(TBars source) : this(source, period: 2, useNaN: false) { } - public MIDPRICE_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - BufferTrim(_bufferhi, TBar.h, _period, update); - BufferTrim(_bufferlo, TBar.l, _period, update); - double _mid = (_bufferhi.Max() + _bufferlo.Min()) * 0.5; - - var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _mid); - return base.Add(res, update); - } - - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _bufferhi.Clear(); - _bufferlo.Clear(); - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Linq; + +/* +MIDPRICE: Midpoint price (highhest high + lowest low)/2 in the given period in the series. + If period = 0 => period = full length of the series + + */ + +public class MIDPRICE_Series : TSeries { + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TBars _data; + private readonly System.Collections.Generic.List _bufferhi = new(); + private readonly System.Collections.Generic.List _bufferlo = new(); + + //core constructors + public MIDPRICE_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"MIDPRICE({period})"; + } + public MIDPRICE_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(data: _data); + } + public MIDPRICE_Series() : this(period: 2, useNaN: false) { } + public MIDPRICE_Series(int period) : this(period: period, useNaN: false) { } + public MIDPRICE_Series(TBars source) : this(source, period: 2, useNaN: false) { } + public MIDPRICE_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { + BufferTrim(_bufferhi, TBar.h, _period, update); + BufferTrim(_bufferlo, TBar.l, _period, update); + double _mid = (_bufferhi.Max() + _bufferlo.Min()) * 0.5; + + var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _mid); + return base.Add(res, update); + } + + public new void Add(TBars data) { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TBar: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _bufferhi.Clear(); + _bufferlo.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MIN_Series.cs b/Calculations/_Updated/MIN_Series.cs index 3f30ffbe..2b34aa7b 100644 --- a/Calculations/_Updated/MIN_Series.cs +++ b/Calculations/_Updated/MIN_Series.cs @@ -1,68 +1,68 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -MIN - Minimum value in the given period in the series. - If period = 0 => period = full length of the series - - */ - -public class MIN_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public MIN_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MAX({period})"; - } - public MIN_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MIN_Series() : this(period: 0, useNaN: false) { } - public MIN_Series(int period) : this(period: period, useNaN: false) { } - public MIN_Series(TBars source) : this(source.Close, 0, false) { } - public MIN_Series(TBars source, int period) : this(source.Close, period, false) { } - public MIN_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MIN_Series(TSeries source) : this(source, 0, false) { } - public MIN_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _max= _buffer.Min(); - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _max); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +MIN - Minimum value in the given period in the series. + If period = 0 => period = full length of the series + + */ + +public class MIN_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public MIN_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"MAX({period})"; + } + public MIN_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MIN_Series() : this(period: 0, useNaN: false) { } + public MIN_Series(int period) : this(period: period, useNaN: false) { } + public MIN_Series(TBars source) : this(source.Close, 0, false) { } + public MIN_Series(TBars source, int period) : this(source.Close, period, false) { } + public MIN_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MIN_Series(TSeries source) : this(source, 0, false) { } + public MIN_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _max= _buffer.Min(); + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _max); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MSE_Series.cs b/Calculations/_Updated/MSE_Series.cs index 262cbd6a..a918962e 100644 --- a/Calculations/_Updated/MSE_Series.cs +++ b/Calculations/_Updated/MSE_Series.cs @@ -1,76 +1,76 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -MSE: Mean Square Error - Defined as a Mean (Average) of the Square of the difference between actual and estimated values. - -Sources: - https://en.wikipedia.org/wiki/Mean_squared_error - - */ - -public class MSE_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public MSE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MSE({period})"; - } - public MSE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MSE_Series() : this(period: 0, useNaN: false) { } - public MSE_Series(int period) : this(period: period, useNaN: false) { } - public MSE_Series(TBars source) : this(source.Close, 0, false) { } - public MSE_Series(TBars source, int period) : this(source.Close, period, false) { } - public MSE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MSE_Series(TSeries source) : this(source, 0, false) { } - public MSE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _sma = _buffer.Average(); - - double _mse = 0; - for (int i = 0; i < _buffer.Count; i++) { _mse += (_buffer[i] - _sma) * (_buffer[i] - _sma); } - _mse /= this._buffer.Count; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mse); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +MSE: Mean Square Error + Defined as a Mean (Average) of the Square of the difference between actual and estimated values. + +Sources: + https://en.wikipedia.org/wiki/Mean_squared_error + + */ + +public class MSE_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public MSE_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"MSE({period})"; + } + public MSE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MSE_Series() : this(period: 0, useNaN: false) { } + public MSE_Series(int period) : this(period: period, useNaN: false) { } + public MSE_Series(TBars source) : this(source.Close, 0, false) { } + public MSE_Series(TBars source, int period) : this(source.Close, period, false) { } + public MSE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MSE_Series(TSeries source) : this(source, 0, false) { } + public MSE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _sma = _buffer.Average(); + + double _mse = 0; + for (int i = 0; i < _buffer.Count; i++) { _mse += (_buffer[i] - _sma) * (_buffer[i] - _sma); } + _mse /= this._buffer.Count; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mse); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/OBV_Series.cs b/Calculations/_Updated/OBV_Series.cs index 57531bdd..40aa2c6e 100644 --- a/Calculations/_Updated/OBV_Series.cs +++ b/Calculations/_Updated/OBV_Series.cs @@ -1,96 +1,96 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Linq; - -/* -OBV: On-Balance Volume - On-balance volume (OBV) is a technical trading momentum indicator that uses volume flow to predict - changes in stock price. Joseph Granville first developed the OBV metric in the 1963 book - Granville's New Key to Stock Market Profits. - - | +volume; if close > close[previous] - OBV = OBV[previous] + | 0; if close = close[previous] - | -volume; if close < close[previous] - -Sources: - https://www.investopedia.com/terms/o/onbalancevolume.asp - https://www.tradingview.com/wiki/On_Balance_Volume_(OBV) - https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/on-balance-volume-obv/ - https://www.motivewave.com/studies/on_balance_volume.htm - -Note: - There is no consensus on what is the first OBV value in the series: - - TA-LIB uses the first volume: OBV[0] = volume[0] - - Skender stock library uses 0: OBV[0] = 0 - - */ - -public class OBV_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TBars _data; - private double _lastobv, _lastlastobv; - private double _lastclose, _lastlastclose; - - //core constructors - public OBV_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"OBV({period})"; - this._lastobv = this._lastlastobv = 0; - this._lastclose = this._lastlastclose = 0; - } - public OBV_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(data: _data); - } - public OBV_Series() : this(period: 2, useNaN: false) { } - public OBV_Series(int period) : this(period: period, useNaN: false) { } - public OBV_Series(TBars source) : this(source, period: 2, useNaN: false) { } - public OBV_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - - if (update) { - this._lastobv = this._lastlastobv; - this._lastclose = this._lastlastclose; - } - - double _obv = this._lastobv; - if (TBar.c > this._lastclose) { _obv += TBar.v; } - if (TBar.c < this._lastclose) { _obv -= TBar.v; } - - this._lastlastobv = this._lastobv; - this._lastobv = _obv; - - this._lastlastclose = this._lastclose; - this._lastclose = TBar.c; - - var res = (TBar.t, (this.Count < this._period && this._NaN) ? double.NaN : _obv); - return base.Add(res, update); - } - - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - this._lastobv = this._lastlastobv = 0; - this._lastclose = this._lastlastclose = 0; - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Linq; + +/* +OBV: On-Balance Volume + On-balance volume (OBV) is a technical trading momentum indicator that uses volume flow to predict + changes in stock price. Joseph Granville first developed the OBV metric in the 1963 book + Granville's New Key to Stock Market Profits. + + | +volume; if close > close[previous] + OBV = OBV[previous] + | 0; if close = close[previous] + | -volume; if close < close[previous] + +Sources: + https://www.investopedia.com/terms/o/onbalancevolume.asp + https://www.tradingview.com/wiki/On_Balance_Volume_(OBV) + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/on-balance-volume-obv/ + https://www.motivewave.com/studies/on_balance_volume.htm + +Note: + There is no consensus on what is the first OBV value in the series: + - TA-LIB uses the first volume: OBV[0] = volume[0] + - Skender stock library uses 0: OBV[0] = 0 + + */ + +public class OBV_Series : TSeries { + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TBars _data; + private double _lastobv, _lastlastobv; + private double _lastclose, _lastlastclose; + + //core constructors + public OBV_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"OBV({period})"; + this._lastobv = this._lastlastobv = 0; + this._lastclose = this._lastlastclose = 0; + } + public OBV_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(data: _data); + } + public OBV_Series() : this(period: 2, useNaN: false) { } + public OBV_Series(int period) : this(period: period, useNaN: false) { } + public OBV_Series(TBars source) : this(source, period: 2, useNaN: false) { } + public OBV_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { + + if (update) { + this._lastobv = this._lastlastobv; + this._lastclose = this._lastlastclose; + } + + double _obv = this._lastobv; + if (TBar.c > this._lastclose) { _obv += TBar.v; } + if (TBar.c < this._lastclose) { _obv -= TBar.v; } + + this._lastlastobv = this._lastobv; + this._lastobv = _obv; + + this._lastlastclose = this._lastclose; + this._lastclose = TBar.c; + + var res = (TBar.t, (this.Count < this._period && this._NaN) ? double.NaN : _obv); + return base.Add(res, update); + } + + public new void Add(TBars data) { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TBar: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + this._lastobv = this._lastlastobv = 0; + this._lastclose = this._lastlastclose = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/RMA_Series.cs b/Calculations/_Updated/RMA_Series.cs index bd84105f..5a624bd9 100644 --- a/Calculations/_Updated/RMA_Series.cs +++ b/Calculations/_Updated/RMA_Series.cs @@ -1,116 +1,116 @@ -namespace QuanTAlib; - -using System; -using System.Linq; - -/* -RMA: wildeR Moving Average - J. Welles Wilder introduced RMA as an alternative to EMA. RMA's weight (k) is - set as 1/period, giving less weight to the new data compared to EMA. - -Sources: - https://archive.org/details/newconceptsintec00wild/page/23/mode/2up - https://tlc.thinkorswim.com/center/reference/Tech-Indicators/studies-library/V-Z/WildersSmoothing - https://www.incrediblecharts.com/indicators/wilder_moving_average.php - -Issues: - Pandas-TA library calculates RMA using straight Exponential Weighted Mean: - pandas.ewm().mean() and returns incorrect first (period) of bars compared to - published formula. This implementation passess the validation test in Wilder's book. - - */ - -public class RMA_Series : TSeries { - private double _k; - private double _lastrma, _oldrma; - private double _sum, _oldsum; - private readonly bool _useSMA; - private int _len; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - -//core constructor - public RMA_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - _useSMA = useSMA; - Name = $"RMA({period})"; - _k = 1.0 / (double)(this._period); - _len = 0; - _sum = _oldsum = _lastrma = _oldrma = 0; - } - //generic constructors (source) - - public RMA_Series() : this(0, false, true) {} - public RMA_Series(int period) : this(period, false, true) {} - public RMA_Series(TBars source) : this(source.Close, 0, false) {} - public RMA_Series(TBars source, int period) : this(source.Close, period, false) {} - public RMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} - public RMA_Series(TSeries source, int period) : this(source, period, false, true) {} - public RMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} - public RMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - -// core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { - _lastrma = _oldrma; - _sum = _oldsum; - } - else { - _oldrma = _lastrma; - _oldsum = _sum; - _len++; - } - - double _rma = 0; - if (_period == 0) { - _k = 1.0 / (double)(this._len); - } - - if (Count == 0) { - _rma = _sum = TValue.v; - - } else if (_len <= _period && _useSMA && _period != 0) { - _sum += TValue.v; - if (_period != 0 && _len > _period) { - _sum -= _data[Count - _period - (update ? 1 : 0)].v; - } - _rma = _sum / Math.Min(_len, _period); - } - else { - _rma = _k * (TValue.v - _lastrma) + _lastrma; - } - - _lastrma = double.IsNaN(_rma) ? _lastrma : _rma; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _rma); - return base.Add(res, update); - } - -//variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _sum = _oldsum = _lastrma = _oldrma = 0; - _len = 0; - } +namespace QuanTAlib; + +using System; +using System.Linq; + +/* +RMA: wildeR Moving Average + J. Welles Wilder introduced RMA as an alternative to EMA. RMA's weight (k) is + set as 1/period, giving less weight to the new data compared to EMA. + +Sources: + https://archive.org/details/newconceptsintec00wild/page/23/mode/2up + https://tlc.thinkorswim.com/center/reference/Tech-Indicators/studies-library/V-Z/WildersSmoothing + https://www.incrediblecharts.com/indicators/wilder_moving_average.php + +Issues: + Pandas-TA library calculates RMA using straight Exponential Weighted Mean: + pandas.ewm().mean() and returns incorrect first (period) of bars compared to + published formula. This implementation passess the validation test in Wilder's book. + + */ + +public class RMA_Series : TSeries { + private double _k; + private double _lastrma, _oldrma; + private double _sum, _oldsum; + private readonly bool _useSMA; + private int _len; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + +//core constructor + public RMA_Series(int period, bool useNaN, bool useSMA) { + _period = period; + _NaN = useNaN; + _useSMA = useSMA; + Name = $"RMA({period})"; + _k = 1.0 / (double)(this._period); + _len = 0; + _sum = _oldsum = _lastrma = _oldrma = 0; + } + //generic constructors (source) + + public RMA_Series() : this(0, false, true) {} + public RMA_Series(int period) : this(period, false, true) {} + public RMA_Series(TBars source) : this(source.Close, 0, false) {} + public RMA_Series(TBars source, int period) : this(source.Close, period, false) {} + public RMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} + public RMA_Series(TSeries source, int period) : this(source, period, false, true) {} + public RMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} + public RMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + +// core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (update) { + _lastrma = _oldrma; + _sum = _oldsum; + } + else { + _oldrma = _lastrma; + _oldsum = _sum; + _len++; + } + + double _rma = 0; + if (_period == 0) { + _k = 1.0 / (double)(this._len); + } + + if (Count == 0) { + _rma = _sum = TValue.v; + + } else if (_len <= _period && _useSMA && _period != 0) { + _sum += TValue.v; + if (_period != 0 && _len > _period) { + _sum -= _data[Count - _period - (update ? 1 : 0)].v; + } + _rma = _sum / Math.Min(_len, _period); + } + else { + _rma = _k * (TValue.v - _lastrma) + _lastrma; + } + + _lastrma = double.IsNaN(_rma) ? _lastrma : _rma; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _rma); + return base.Add(res, update); + } + +//variation of Add() + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _sum = _oldsum = _lastrma = _oldrma = 0; + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/RSI_Series.cs b/Calculations/_Updated/RSI_Series.cs index 7acb6daa..8d3979a1 100644 --- a/Calculations/_Updated/RSI_Series.cs +++ b/Calculations/_Updated/RSI_Series.cs @@ -1,120 +1,120 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -RSI: Relative Strength Index - Created by J. Welles Wilder, the Relative Strength Index measures strength - of the winning/losing streak over N lookback periods on a scale of 0 to 100, - to depict overbought and oversold conditions. - -Sources: - https://www.investopedia.com/terms/r/rsi.asp - - */ - -public class RSI_Series : TSeries { - private readonly System.Collections.Generic.List _gain = new(); - private readonly System.Collections.Generic.List _loss = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private double _avgGain, _avgLoss, _lastValue; - private double _avgGain_o, _avgLoss_o, _lastValue_o; - private int i; - - //core constructors - public RSI_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"RSI({period})"; - i = 0; - } - public RSI_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public RSI_Series() : this(period: 0, useNaN: false) { } - public RSI_Series(int period) : this(period: period, useNaN: false) { } - public RSI_Series(TBars source) : this(source.Close, 0, false) { } - public RSI_Series(TBars source, int period) : this(source.Close, period, false) { } - public RSI_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public RSI_Series(TSeries source) : this(source, 0, false) { } - public RSI_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - - double _rsi = 0; - if (update) { - _lastValue = _lastValue_o; - _avgGain = _avgGain_o; - _avgLoss = _avgLoss_o; - } - else { - _lastValue_o = _lastValue; - _avgGain_o = _avgGain; - _avgLoss_o = _avgLoss; - } - - if (i == 0) { _lastValue = TValue.v; } - - double _gainval = (TValue.v > _lastValue) ? TValue.v - _lastValue : 0; - BufferTrim(_gain, _gainval, _period, update); - double _lossval = (TValue.v < _lastValue) ? _lastValue - TValue.v : 0; - BufferTrim(_loss, _lossval, _period, update); - _lastValue = TValue.v; - - // calculate RSI - if (i > _period && _period != 0) { - _avgGain = ((_avgGain * (_period - 1)) + _gain[^1]) / _period; - _avgLoss = ((_avgLoss * (_period - 1)) + _loss[^1]) / _period; - if (_avgLoss > 0) { - double rs = _avgGain / _avgLoss; - _rsi = 100 - (100 / (1 + rs)); - } - else { _rsi = 100; } - } - // initialize average gain - else { - double _sumGain = 0; - for (int p = 0; p < _gain.Count; p++) { _sumGain += _gain[p]; } - double _sumLoss = 0; - for (int p = 0; p < _loss.Count; p++) { _sumLoss += _loss[p]; } - - _avgGain = _sumGain / _gain.Count; - _avgLoss = _sumLoss / _loss.Count; - - _rsi = (_avgLoss > 0) ? 100 - (100 / (1 + (_avgGain / _avgLoss))) : 100; - } - if (!update) { i++; } - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _rsi); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - i = 0; - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +RSI: Relative Strength Index + Created by J. Welles Wilder, the Relative Strength Index measures strength + of the winning/losing streak over N lookback periods on a scale of 0 to 100, + to depict overbought and oversold conditions. + +Sources: + https://www.investopedia.com/terms/r/rsi.asp + + */ + +public class RSI_Series : TSeries { + private readonly System.Collections.Generic.List _gain = new(); + private readonly System.Collections.Generic.List _loss = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private double _avgGain, _avgLoss, _lastValue; + private double _avgGain_o, _avgLoss_o, _lastValue_o; + private int i; + + //core constructors + public RSI_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"RSI({period})"; + i = 0; + } + public RSI_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public RSI_Series() : this(period: 0, useNaN: false) { } + public RSI_Series(int period) : this(period: period, useNaN: false) { } + public RSI_Series(TBars source) : this(source.Close, 0, false) { } + public RSI_Series(TBars source, int period) : this(source.Close, period, false) { } + public RSI_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public RSI_Series(TSeries source) : this(source, 0, false) { } + public RSI_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + + double _rsi = 0; + if (update) { + _lastValue = _lastValue_o; + _avgGain = _avgGain_o; + _avgLoss = _avgLoss_o; + } + else { + _lastValue_o = _lastValue; + _avgGain_o = _avgGain; + _avgLoss_o = _avgLoss; + } + + if (i == 0) { _lastValue = TValue.v; } + + double _gainval = (TValue.v > _lastValue) ? TValue.v - _lastValue : 0; + BufferTrim(_gain, _gainval, _period, update); + double _lossval = (TValue.v < _lastValue) ? _lastValue - TValue.v : 0; + BufferTrim(_loss, _lossval, _period, update); + _lastValue = TValue.v; + + // calculate RSI + if (i > _period && _period != 0) { + _avgGain = ((_avgGain * (_period - 1)) + _gain[^1]) / _period; + _avgLoss = ((_avgLoss * (_period - 1)) + _loss[^1]) / _period; + if (_avgLoss > 0) { + double rs = _avgGain / _avgLoss; + _rsi = 100 - (100 / (1 + rs)); + } + else { _rsi = 100; } + } + // initialize average gain + else { + double _sumGain = 0; + for (int p = 0; p < _gain.Count; p++) { _sumGain += _gain[p]; } + double _sumLoss = 0; + for (int p = 0; p < _loss.Count; p++) { _sumLoss += _loss[p]; } + + _avgGain = _sumGain / _gain.Count; + _avgLoss = _sumLoss / _loss.Count; + + _rsi = (_avgLoss > 0) ? 100 - (100 / (1 + (_avgGain / _avgLoss))) : 100; + } + if (!update) { i++; } + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _rsi); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + i = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/SDEV_Series.cs b/Calculations/_Updated/SDEV_Series.cs index 4b5a91be..ab5ca503 100644 --- a/Calculations/_Updated/SDEV_Series.cs +++ b/Calculations/_Updated/SDEV_Series.cs @@ -1,82 +1,82 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -SDEV: Population Standard Deviation - Population Standard Deviation is the square root of the biased variance, also knons as - Uncorrected Sample Standard Deviation - -Sources: - https://en.wikipedia.org/wiki/Standard_deviation#Uncorrected_sample_standard_deviation - -Remark: - SDEV (Population Standard Deviation) is also known as a biased/uncorrected Standard Deviation. - For unbiased version that uses Bessel's correction, use SDEV instead. - - */ - -public class SDEV_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public SDEV_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"SDEV({period})"; - } - public SDEV_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SDEV_Series() : this(period: 0, useNaN: false) { } - public SDEV_Series(int period) : this(period: period, useNaN: false) { } - public SDEV_Series(TBars source) : this(source.Close, 0, false) { } - public SDEV_Series(TBars source, int period) : this(source.Close, period, false) { } - public SDEV_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public SDEV_Series(TSeries source) : this(source, 0, false) { } - public SDEV_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _sma = _buffer.Average(); - - double _var = 0; - for (int i = 0; i < _buffer.Count; i++) { _var += (_buffer[i] - _sma) * (_buffer[i] - _sma); } - _var /= this._buffer.Count; - double _sdev = Math.Sqrt(_var); - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _sdev); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +SDEV: Population Standard Deviation + Population Standard Deviation is the square root of the biased variance, also knons as + Uncorrected Sample Standard Deviation + +Sources: + https://en.wikipedia.org/wiki/Standard_deviation#Uncorrected_sample_standard_deviation + +Remark: + SDEV (Population Standard Deviation) is also known as a biased/uncorrected Standard Deviation. + For unbiased version that uses Bessel's correction, use SDEV instead. + + */ + +public class SDEV_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public SDEV_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"SDEV({period})"; + } + public SDEV_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SDEV_Series() : this(period: 0, useNaN: false) { } + public SDEV_Series(int period) : this(period: period, useNaN: false) { } + public SDEV_Series(TBars source) : this(source.Close, 0, false) { } + public SDEV_Series(TBars source, int period) : this(source.Close, period, false) { } + public SDEV_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public SDEV_Series(TSeries source) : this(source, 0, false) { } + public SDEV_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _sma = _buffer.Average(); + + double _var = 0; + for (int i = 0; i < _buffer.Count; i++) { _var += (_buffer[i] - _sma) * (_buffer[i] - _sma); } + _var /= this._buffer.Count; + double _sdev = Math.Sqrt(_var); + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _sdev); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/SLOPE_Series.cs b/Calculations/_Updated/SLOPE_Series.cs index c9fc3eae..4aef642e 100644 --- a/Calculations/_Updated/SLOPE_Series.cs +++ b/Calculations/_Updated/SLOPE_Series.cs @@ -1,122 +1,122 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -SLOPE: Slope of linear regression (using Least Square Method) - Linear Regression provides a slope of a straight line that is the best approximation of the given set of data. - The method of least squares is a standard approach in linear regression analysis to approximate the solution - by minimizing the sum of the squares of the residuals made in the results of each individual equation. - -Additional outputs provided by LINREG: - .Intercept - y-intercept point of the best fit line - .RSquared - R-Squared (R²), Coefficient of Determination - .StdDev - Standard Deviation of data over given periods - - y = Slope * x + Intercept - -Sources: - https://en.wikipedia.org/wiki/Least_squares - - */ - -public class SLOPE_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly TSeries p_Intercept = new(); - private readonly TSeries p_RSquared = new(); - private readonly TSeries p_StdDev = new(); - private readonly System.Collections.Generic.List _buffer = new(); - public TSeries Intercept => p_Intercept; - public TSeries RSquared => p_RSquared; - public TSeries StdDev => p_StdDev; - //core constructors - public SLOPE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"SLOPE({period})"; - } - public SLOPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SLOPE_Series() : this(period: 0, useNaN: false) { } - public SLOPE_Series(int period) : this(period: period, useNaN: false) { } - public SLOPE_Series(TBars source) : this(source.Close, 0, false) { } - public SLOPE_Series(TBars source, int period) : this(source.Close, period, false) { } - public SLOPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public SLOPE_Series(TSeries source) : this(source, 0, false) { } - public SLOPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - int _len = this._buffer.Count; - - // get averages for period - double sumX = 0; - double sumY = 0; - - for (int p = 0; p < _len; p++) { - sumX += this.Count - _len + 2 + p; - sumY += _buffer[p]; - } - double avgX = sumX / _len; - double avgY = sumY / _len; - - // least squares method - double sumSqX = 0; - double sumSqY = 0; - double sumSqXY = 0; - - for (int p = 0; p < _len; p++) { - double devX = this.Count - _len + 2 + p - avgX; - double devY = _buffer[p] - avgY; - - sumSqX += devX * devX; - sumSqY += devY * devY; - sumSqXY += devX * devY; - } - - double _slope = sumSqXY / sumSqX; - double _intercept = avgY - (_slope * avgX); - - // calculate Standard Deviation and R-Squared - double stdDevX = Math.Sqrt(sumSqX / _len); - double stdDevY = Math.Sqrt(sumSqY / _len); - double _StdDev = stdDevY; - - double arrr = (stdDevX * stdDevY != 0) ? sumSqXY / (stdDevX * stdDevY) / _len : 0; - double _RSquared = arrr * arrr; - - var ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _intercept); - p_Intercept.Add(ret, update); - - ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _StdDev); - p_StdDev.Add(ret, update); - - ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _RSquared); - p_RSquared.Add(ret, update); - - ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _slope); - return base.Add(ret, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +SLOPE: Slope of linear regression (using Least Square Method) + Linear Regression provides a slope of a straight line that is the best approximation of the given set of data. + The method of least squares is a standard approach in linear regression analysis to approximate the solution + by minimizing the sum of the squares of the residuals made in the results of each individual equation. + +Additional outputs provided by LINREG: + .Intercept - y-intercept point of the best fit line + .RSquared - R-Squared (R²), Coefficient of Determination + .StdDev - Standard Deviation of data over given periods + + y = Slope * x + Intercept + +Sources: + https://en.wikipedia.org/wiki/Least_squares + + */ + +public class SLOPE_Series : TSeries { + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly TSeries p_Intercept = new(); + private readonly TSeries p_RSquared = new(); + private readonly TSeries p_StdDev = new(); + private readonly System.Collections.Generic.List _buffer = new(); + public TSeries Intercept => p_Intercept; + public TSeries RSquared => p_RSquared; + public TSeries StdDev => p_StdDev; + //core constructors + public SLOPE_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"SLOPE({period})"; + } + public SLOPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SLOPE_Series() : this(period: 0, useNaN: false) { } + public SLOPE_Series(int period) : this(period: period, useNaN: false) { } + public SLOPE_Series(TBars source) : this(source.Close, 0, false) { } + public SLOPE_Series(TBars source, int period) : this(source.Close, period, false) { } + public SLOPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public SLOPE_Series(TSeries source) : this(source, 0, false) { } + public SLOPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + int _len = this._buffer.Count; + + // get averages for period + double sumX = 0; + double sumY = 0; + + for (int p = 0; p < _len; p++) { + sumX += this.Count - _len + 2 + p; + sumY += _buffer[p]; + } + double avgX = sumX / _len; + double avgY = sumY / _len; + + // least squares method + double sumSqX = 0; + double sumSqY = 0; + double sumSqXY = 0; + + for (int p = 0; p < _len; p++) { + double devX = this.Count - _len + 2 + p - avgX; + double devY = _buffer[p] - avgY; + + sumSqX += devX * devX; + sumSqY += devY * devY; + sumSqXY += devX * devY; + } + + double _slope = sumSqXY / sumSqX; + double _intercept = avgY - (_slope * avgX); + + // calculate Standard Deviation and R-Squared + double stdDevX = Math.Sqrt(sumSqX / _len); + double stdDevY = Math.Sqrt(sumSqY / _len); + double _StdDev = stdDevY; + + double arrr = (stdDevX * stdDevY != 0) ? sumSqXY / (stdDevX * stdDevY) / _len : 0; + double _RSquared = arrr * arrr; + + var ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _intercept); + p_Intercept.Add(ret, update); + + ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _StdDev); + p_StdDev.Add(ret, update); + + ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _RSquared); + p_RSquared.Add(ret, update); + + ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _slope); + return base.Add(ret, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/SMAPE_Series.cs b/Calculations/_Updated/SMAPE_Series.cs index 00ff1237..b79a3442 100644 --- a/Calculations/_Updated/SMAPE_Series.cs +++ b/Calculations/_Updated/SMAPE_Series.cs @@ -1,75 +1,75 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -SMAPE: Symmetric Mean Absolute Percentage Error - Measures the size of the error in percentage terms - -Sources: - https://en.wikipedia.org/wiki/Symmetric_mean_absolute_percentage_error - - */ - -public class SMAPE_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public SMAPE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"SMAPE({period})"; - } - public SMAPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SMAPE_Series() : this(period: 0, useNaN: false) { } - public SMAPE_Series(int period) : this(period: period, useNaN: false) { } - public SMAPE_Series(TBars source) : this(source.Close, 0, false) { } - public SMAPE_Series(TBars source, int period) : this(source.Close, period, false) { } - public SMAPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public SMAPE_Series(TSeries source) : this(source, 0, false) { } - public SMAPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _sma = _buffer.Average(); - double _smape = 0; - for (int i = 0; i < _buffer.Count; i++) { _smape += Math.Abs(_buffer[i] - _sma) / (Math.Abs(_buffer[i]) + Math.Abs(_sma)); } - _smape /= this._buffer.Count; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _smape); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +SMAPE: Symmetric Mean Absolute Percentage Error + Measures the size of the error in percentage terms + +Sources: + https://en.wikipedia.org/wiki/Symmetric_mean_absolute_percentage_error + + */ + +public class SMAPE_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public SMAPE_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"SMAPE({period})"; + } + public SMAPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SMAPE_Series() : this(period: 0, useNaN: false) { } + public SMAPE_Series(int period) : this(period: period, useNaN: false) { } + public SMAPE_Series(TBars source) : this(source.Close, 0, false) { } + public SMAPE_Series(TBars source, int period) : this(source.Close, period, false) { } + public SMAPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public SMAPE_Series(TSeries source) : this(source, 0, false) { } + public SMAPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _sma = _buffer.Average(); + double _smape = 0; + for (int i = 0; i < _buffer.Count; i++) { _smape += Math.Abs(_buffer[i] - _sma) / (Math.Abs(_buffer[i]) + Math.Abs(_sma)); } + _smape /= this._buffer.Count; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _smape); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/SMA_Series.cs b/Calculations/_Updated/SMA_Series.cs index ba25a3f6..c9a817f0 100644 --- a/Calculations/_Updated/SMA_Series.cs +++ b/Calculations/_Updated/SMA_Series.cs @@ -1,96 +1,96 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -SMA: Simple Moving Average - The weights are equally distributed across the period, resulting in a mean() of - the data within the period - -Sources: - https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/simple-moving-average-sma/ - https://stats.stackexchange.com/a/24739 - -Remark: - This calc doesn't use LINQ or SUM() or any of (slow) iterative methods. It is not as fast as TA-LIB - implementation, but it does allow incremental additions of inputs and real-time calculations of SMA() - - */ -public class SMA_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - - private double _sum, _oldsum; - private readonly int _period; - private readonly TSeries _data; - protected readonly bool _NaN; - - //core constructor - public SMA_Series(int period, bool useNaN) { - _period = Math.Max(0, period); - _NaN = useNaN; - Name = $"SMA({period})"; - _sum = _oldsum = 0; - } - public SMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SMA_Series() : this(0, false) {} - public SMA_Series(int period) : this(period, false) {} - public SMA_Series(TBars source) : this(source.Close, 0, false) {} - public SMA_Series(TBars source, int period) : this(source.Close, period, false) {} - public SMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} - public SMA_Series(TSeries source) : this(source, 0, false) {} - public SMA_Series(TSeries source, int period) : this(source, period, false) {} - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { return (TValue.t, double.NaN); - } else { - if (update && _buffer.Count > 0) { - _sum -= _buffer[^1]; - _buffer[^1] = TValue.v; - _oldsum = _sum; - } - else { - _buffer.Add(TValue.v); - _oldsum = _sum; - } - - _sum += TValue.v; - if (_period != 0 && _buffer.Count > _period) { - _sum -= _buffer[0]; - _buffer.RemoveAt(0); - } - } - - double _div = _period == 0 ? _buffer.Count : Math.Min(_buffer.Count, _period); - var _sma = _sum / _div; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _sma); - return base.Add(res, update); - } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - - //reset calculation - public override void Reset() { - _sum = _oldsum = 0; - _buffer.Clear(); - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +SMA: Simple Moving Average + The weights are equally distributed across the period, resulting in a mean() of + the data within the period + +Sources: + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/simple-moving-average-sma/ + https://stats.stackexchange.com/a/24739 + +Remark: + This calc doesn't use LINQ or SUM() or any of (slow) iterative methods. It is not as fast as TA-LIB + implementation, but it does allow incremental additions of inputs and real-time calculations of SMA() + + */ +public class SMA_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + + private double _sum, _oldsum; + private readonly int _period; + private readonly TSeries _data; + protected readonly bool _NaN; + + //core constructor + public SMA_Series(int period, bool useNaN) { + _period = Math.Max(0, period); + _NaN = useNaN; + Name = $"SMA({period})"; + _sum = _oldsum = 0; + } + public SMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SMA_Series() : this(0, false) {} + public SMA_Series(int period) : this(period, false) {} + public SMA_Series(TBars source) : this(source.Close, 0, false) {} + public SMA_Series(TBars source, int period) : this(source.Close, period, false) {} + public SMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} + public SMA_Series(TSeries source) : this(source, 0, false) {} + public SMA_Series(TSeries source, int period) : this(source, period, false) {} + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (double.IsNaN(TValue.v)) { return (TValue.t, double.NaN); + } else { + if (update && _buffer.Count > 0) { + _sum -= _buffer[^1]; + _buffer[^1] = TValue.v; + _oldsum = _sum; + } + else { + _buffer.Add(TValue.v); + _oldsum = _sum; + } + + _sum += TValue.v; + if (_period != 0 && _buffer.Count > _period) { + _sum -= _buffer[0]; + _buffer.RemoveAt(0); + } + } + + double _div = _period == 0 ? _buffer.Count : Math.Min(_buffer.Count, _period); + var _sma = _sum / _div; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _sma); + return base.Add(res, update); + } + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + + //reset calculation + public override void Reset() { + _sum = _oldsum = 0; + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/SMMA_Series.cs b/Calculations/_Updated/SMMA_Series.cs index f653eab3..8f464cc1 100644 --- a/Calculations/_Updated/SMMA_Series.cs +++ b/Calculations/_Updated/SMMA_Series.cs @@ -1,93 +1,93 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Linq; - -/* -SMMA: Smoothed Moving Average - The Smoothed Moving Average (SMMA) is a combination of a SMA and an EMA. It gives the recent prices - an equal weighting as the historic prices as it takes all available price data into account. - The main advantage of a smoothed moving average is that it removes short-term fluctuations. - - SMMA(i) = (SMMA-1*(N-1) + CLOSE (i)) / N - -Sources: - https://blog.earn2trade.com/smoothed-moving-average - https://guide.traderevolution.com/traderevolution/mobile-applications/phone/android/technical-indicators/moving-averages/smma-smoothed-moving-average - https://www.chartmill.com/documentation/technical-analysis-indicators/217-MOVING-AVERAGES-%7C-The-Smoothed-Moving-Average-%28SMMA%29 - - */ - -public class SMMA_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private double _lastsmma, _lastlastsmma; - - //core constructors - public SMMA_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"SMMA({period})"; - } - public SMMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SMMA_Series() : this(period: 0, useNaN: false) { } - public SMMA_Series(int period) : this(period: period, useNaN: false) { } - public SMMA_Series(TBars source) : this(source.Close, 0, false) { } - public SMMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public SMMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public SMMA_Series(TSeries source) : this(source, 0, false) { } - public SMMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, double.NaN),update); - } - - double _smma = 0; - if (update) { this._lastsmma = this._lastlastsmma; } - - if (this.Count < this._period) { - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - _smma = _buffer.Average(); - } - else { - _smma = ((_lastsmma * (_period - 1)) + TValue.v) / _period; - } - - this._lastlastsmma = this._lastsmma; - this._lastsmma = _smma; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _smma); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - this._lastsmma = this._lastlastsmma = 0; - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Linq; + +/* +SMMA: Smoothed Moving Average + The Smoothed Moving Average (SMMA) is a combination of a SMA and an EMA. It gives the recent prices + an equal weighting as the historic prices as it takes all available price data into account. + The main advantage of a smoothed moving average is that it removes short-term fluctuations. + + SMMA(i) = (SMMA-1*(N-1) + CLOSE (i)) / N + +Sources: + https://blog.earn2trade.com/smoothed-moving-average + https://guide.traderevolution.com/traderevolution/mobile-applications/phone/android/technical-indicators/moving-averages/smma-smoothed-moving-average + https://www.chartmill.com/documentation/technical-analysis-indicators/217-MOVING-AVERAGES-%7C-The-Smoothed-Moving-Average-%28SMMA%29 + + */ + +public class SMMA_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private double _lastsmma, _lastlastsmma; + + //core constructors + public SMMA_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"SMMA({period})"; + } + public SMMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SMMA_Series() : this(period: 0, useNaN: false) { } + public SMMA_Series(int period) : this(period: period, useNaN: false) { } + public SMMA_Series(TBars source) : this(source.Close, 0, false) { } + public SMMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public SMMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public SMMA_Series(TSeries source) : this(source, 0, false) { } + public SMMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (double.IsNaN(TValue.v)) { + return base.Add((TValue.t, double.NaN),update); + } + + double _smma = 0; + if (update) { this._lastsmma = this._lastlastsmma; } + + if (this.Count < this._period) { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + _smma = _buffer.Average(); + } + else { + _smma = ((_lastsmma * (_period - 1)) + TValue.v) / _period; + } + + this._lastlastsmma = this._lastsmma; + this._lastsmma = _smma; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _smma); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + this._lastsmma = this._lastlastsmma = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/SSDEV_Series.cs b/Calculations/_Updated/SSDEV_Series.cs index 20d41b95..2b5f62d3 100644 --- a/Calculations/_Updated/SSDEV_Series.cs +++ b/Calculations/_Updated/SSDEV_Series.cs @@ -1,82 +1,82 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -SSDEV: (Corrected) Sample Standard Deviation - Sample Standard Deviaton uses Bessel's correction to correct the bias in the variance. - -Sources: - https://en.wikipedia.org/wiki/Standard_deviation#Corrected_sample_standard_deviation - Bessel's correction: https://en.wikipedia.org/wiki/Bessel%27s_correction - -Remark: - SSDEV (Sample Standard Deviation) is also known as a unbiased/corrected Standard Deviation. - For a population/biased/uncorrected Standard Deviation, use PSDEV instead - - */ - -public class SSDEV_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public SSDEV_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"SSDEV({period})"; - } - public SSDEV_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SSDEV_Series() : this(period: 0, useNaN: false) { } - public SSDEV_Series(int period) : this(period: period, useNaN: false) { } - public SSDEV_Series(TBars source) : this(source.Close, 0, false) { } - public SSDEV_Series(TBars source, int period) : this(source.Close, period, false) { } - public SSDEV_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public SSDEV_Series(TSeries source) : this(source, 0, false) { } - public SSDEV_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _sma = _buffer.Average(); - - double _svar = 0; - for (int i = 0; i < this._buffer.Count; i++) { _svar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } - _svar /= (_buffer.Count > 1) ? _buffer.Count - 1 : 1; // Bessel's correction - double _ssdev = Math.Sqrt(_svar); - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _ssdev); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +SSDEV: (Corrected) Sample Standard Deviation + Sample Standard Deviaton uses Bessel's correction to correct the bias in the variance. + +Sources: + https://en.wikipedia.org/wiki/Standard_deviation#Corrected_sample_standard_deviation + Bessel's correction: https://en.wikipedia.org/wiki/Bessel%27s_correction + +Remark: + SSDEV (Sample Standard Deviation) is also known as a unbiased/corrected Standard Deviation. + For a population/biased/uncorrected Standard Deviation, use PSDEV instead + + */ + +public class SSDEV_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public SSDEV_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"SSDEV({period})"; + } + public SSDEV_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SSDEV_Series() : this(period: 0, useNaN: false) { } + public SSDEV_Series(int period) : this(period: period, useNaN: false) { } + public SSDEV_Series(TBars source) : this(source.Close, 0, false) { } + public SSDEV_Series(TBars source, int period) : this(source.Close, period, false) { } + public SSDEV_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public SSDEV_Series(TSeries source) : this(source, 0, false) { } + public SSDEV_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _sma = _buffer.Average(); + + double _svar = 0; + for (int i = 0; i < this._buffer.Count; i++) { _svar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } + _svar /= (_buffer.Count > 1) ? _buffer.Count - 1 : 1; // Bessel's correction + double _ssdev = Math.Sqrt(_svar); + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _ssdev); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/SVAR_Series.cs b/Calculations/_Updated/SVAR_Series.cs index b121fe8b..bb693c54 100644 --- a/Calculations/_Updated/SVAR_Series.cs +++ b/Calculations/_Updated/SVAR_Series.cs @@ -1,81 +1,81 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -VAR: Population Variance - Population variance without Bessel's correction - -Sources: - https://en.wikipedia.org/wiki/Variance - Bessel's correction: https://en.wikipedia.org/wiki/Bessel%27s_correction - -Remark: - VAR (Population Variance) is also known as a biased Sample Variance. For unbiased - sample variance use SVAR instead. - - */ - -public class SVAR_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public SVAR_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"SVAR({period})"; - } - public SVAR_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SVAR_Series() : this(period: 0, useNaN: false) { } - public SVAR_Series(int period) : this(period: period, useNaN: false) { } - public SVAR_Series(TBars source) : this(source.Close, 0, false) { } - public SVAR_Series(TBars source, int period) : this(source.Close, period, false) { } - public SVAR_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public SVAR_Series(TSeries source) : this(source, 0, false) { } - public SVAR_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _sma = _buffer.Average(); - - double _svar = 0; - for (int i = 0; i < this._buffer.Count; i++) { _svar += (this._buffer[i] - _sma) * (this._buffer[i] - _sma); } - _svar /= (this._buffer.Count > 1) ? this._buffer.Count - 1 : 1; // Bessel's correction - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _svar); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +VAR: Population Variance + Population variance without Bessel's correction + +Sources: + https://en.wikipedia.org/wiki/Variance + Bessel's correction: https://en.wikipedia.org/wiki/Bessel%27s_correction + +Remark: + VAR (Population Variance) is also known as a biased Sample Variance. For unbiased + sample variance use SVAR instead. + + */ + +public class SVAR_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public SVAR_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"SVAR({period})"; + } + public SVAR_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SVAR_Series() : this(period: 0, useNaN: false) { } + public SVAR_Series(int period) : this(period: period, useNaN: false) { } + public SVAR_Series(TBars source) : this(source.Close, 0, false) { } + public SVAR_Series(TBars source, int period) : this(source.Close, period, false) { } + public SVAR_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public SVAR_Series(TSeries source) : this(source, 0, false) { } + public SVAR_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _sma = _buffer.Average(); + + double _svar = 0; + for (int i = 0; i < this._buffer.Count; i++) { _svar += (this._buffer[i] - _sma) * (this._buffer[i] - _sma); } + _svar /= (this._buffer.Count > 1) ? this._buffer.Count - 1 : 1; // Bessel's correction + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _svar); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/T3_Series.cs b/Calculations/_Updated/T3_Series.cs index 1ffac970..7cba0dcd 100644 --- a/Calculations/_Updated/T3_Series.cs +++ b/Calculations/_Updated/T3_Series.cs @@ -1,161 +1,161 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Numerics; - -/* -T3: Tillson T3 Moving Average - Tim Tillson described it in "Technical Analysis of Stocks and Commodities", January 1998 in the - article "Better Moving Averages". Tillson’s moving average becomes a popular indicator of - technical analysis as it gets less lag with the price chart and its curve is considerably smoother. - -Sources: - https://technicalindicators.net/indicators-technical-analysis/150-t3-moving-average - http://www.binarytribune.com/forex-trading-indicators/t3-moving-average-indicator/ - */ - -public class T3_Series : TSeries { - private readonly double _k, _k1m, _c1, _c2, _c3, _c4; - private readonly System.Collections.Generic.List _buffer1 = new(); - private readonly System.Collections.Generic.List _buffer2 = new(); - private readonly System.Collections.Generic.List _buffer3 = new(); - private readonly System.Collections.Generic.List _buffer4 = new(); - private readonly System.Collections.Generic.List _buffer5 = new(); - private readonly System.Collections.Generic.List _buffer6 = new(); - private readonly bool _useSMA; - private double _lastema1, _lastema2, _lastema3, _lastema4, _lastema5, _lastema6; - private double _llastema1, _llastema2, _llastema3, _llastema4, _llastema5, _llastema6; - protected int _len; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public T3_Series(int period, double vfactor, bool useSMA, bool useNaN) { - _period = period; - _len = 0; - _NaN = useNaN; - Name = $"T3({period})"; - _useSMA = useSMA; - double _a = vfactor; //0.7; //0.618 - _c1 = -_a * _a * _a; - _c2 = 3 * _a * _a + 3 * _a * _a * _a; - _c3 = -6 * _a * _a - 3 * _a - 3 * _a * _a * _a; - _c4 = 1 + 3 * _a + _a * _a * _a + 3 * _a * _a; - - _k = 2.0 / (_period + 1); - _k1m = 1.0 - _k; - _lastema1 = _llastema1 = _lastema2 = _llastema2 = _lastema3 = _llastema3 = _lastema4 = _llastema4 = _lastema5 = _llastema5 = _lastema5 = _llastema5 = 0; - } - public T3_Series(TSeries source, int period, double vfactor, bool useSMA, bool useNaN) : this(period, vfactor, useSMA, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public T3_Series() : this(period: 0, vfactor: 0.7, useSMA: true, useNaN: false) { } - public T3_Series(int period) : this(period: period, vfactor: 0.7, useSMA: true, useNaN: false) { } - public T3_Series(TBars source) : this(source.Close, 0, vfactor: 0.7, useSMA: true, useNaN: false) { } - public T3_Series(TBars source, int period) : this(source.Close, period, vfactor: 0.7, useSMA: true, useNaN: false) { } - public T3_Series(TBars source, int period, bool useNaN) : this(source.Close, period, vfactor: 0.7, useSMA: true, useNaN: useNaN) { } - public T3_Series(TBars source, int period, double vfactor, bool useNaN) : this(source.Close, period, vfactor: vfactor, useSMA: true, useNaN: useNaN) { } - public T3_Series(TBars source, int period, bool useSMA, bool useNaN) : this(source.Close, period, vfactor: 0.7, useSMA: useSMA, useNaN: useNaN) { } - public T3_Series(TSeries source) : this(source, 0, vfactor: 0.7, useSMA: true, useNaN: false) { } - public T3_Series(TSeries source, int period) : this(source: source, period: period, vfactor: 0.7, useSMA: true, useNaN: false) { } - public T3_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, vfactor: 0.7, useSMA: true, useNaN: useNaN) { } - public T3_Series(TSeries source, int period, double vfactor) : this(source: source, period: period, vfactor: vfactor, useSMA: true, useNaN: false) { } - public T3_Series(TSeries source, int period, double vfactor, bool useNaN) : this(source: source, period: period, vfactor: vfactor, useSMA: true, useNaN: useNaN) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - double _ema1, _ema2, _ema3, _ema4, _ema5, _ema6; - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN),update); - } - - if (update) { _lastema1 = _llastema1; _lastema2 = _llastema2; _lastema3 = _llastema3; _lastema4 = _llastema4; _lastema5 = _llastema5; _lastema6 = _llastema6; } - else { _llastema1 = _lastema1; _llastema2 = _lastema2; _llastema3 = _lastema3; _llastema4 = _lastema4; _llastema5 = _lastema5; _llastema6 = _lastema6; } - - if (_len == 0) { _lastema1 = _lastema2 = _lastema3 = _lastema4 = _lastema5 = _lastema6 = TValue.v; } - - - if ((_len < _period) && _useSMA) { - BufferTrim(_buffer1, TValue.v, _period, update); - _ema1 = 0; - for (int i = 0; i < _buffer1.Count; i++) { _ema1 += _buffer1[i]; } - _ema1 /= _buffer1.Count; - - BufferTrim(_buffer2, _ema1, _period, update); - _ema2 = 0; - for (int i = 0; i < _buffer2.Count; i++) { _ema2 += _buffer2[i]; } - _ema2 /= _buffer2.Count; - - BufferTrim(_buffer3, _ema2, _period, update); - _ema3 = 0; - for (int i = 0; i < _buffer3.Count; i++) { _ema3 += _buffer3[i]; } - _ema3 /= _buffer3.Count; - - BufferTrim(_buffer4, _ema3, _period, update); - _ema4 = 0; - for (int i = 0; i < _buffer4.Count; i++) { _ema4 += _buffer4[i]; } - _ema4 /= _buffer4.Count; - - BufferTrim(_buffer5, _ema4, _period, update); - _ema5 = 0; - for (int i = 0; i < _buffer5.Count; i++) { _ema5 += _buffer5[i]; } - _ema5 /= _buffer5.Count; - - BufferTrim(_buffer6, _ema5, _period, update); - _ema6 = 0; - for (int i = 0; i < _buffer6.Count; i++) { _ema6 += _buffer6[i]; } - _ema6 /= _buffer6.Count; - } - else { - _ema1 = (TValue.v * this._k) + (this._lastema1 * this._k1m); - _ema2 = (_ema1 * this._k) + (this._lastema2 * this._k1m); - _ema3 = (_ema2 * this._k) + (this._lastema3 * this._k1m); - _ema4 = (_ema3 * this._k) + (this._lastema4 * this._k1m); - _ema5 = (_ema4 * this._k) + (this._lastema5 * this._k1m); - _ema6 = (_ema5 * this._k) + (this._lastema6 * this._k1m); - } - _len++; - _lastema1 = _ema1; - _lastema2 = _ema2; - _lastema3 = _ema3; - _lastema4 = _ema4; - _lastema5 = _ema5; - _lastema6 = _ema6; - - double _T3 = _c1 * _ema6 + _c2 * _ema5 + _c3 * _ema4 + _c4 * _ema3; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _T3); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _lastema1 = _llastema1 = _lastema2 = _llastema2 = _lastema3 = _llastema3 = _lastema4 = _llastema4 = _lastema5 = _llastema5 = _lastema5 = _llastema5 = 0; - _buffer1.Clear(); - _buffer2.Clear(); - _buffer3.Clear(); - _buffer4.Clear(); - _buffer5.Clear(); - _buffer6.Clear(); - _len = 0; - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Numerics; + +/* +T3: Tillson T3 Moving Average + Tim Tillson described it in "Technical Analysis of Stocks and Commodities", January 1998 in the + article "Better Moving Averages". Tillson’s moving average becomes a popular indicator of + technical analysis as it gets less lag with the price chart and its curve is considerably smoother. + +Sources: + https://technicalindicators.net/indicators-technical-analysis/150-t3-moving-average + http://www.binarytribune.com/forex-trading-indicators/t3-moving-average-indicator/ + */ + +public class T3_Series : TSeries { + private readonly double _k, _k1m, _c1, _c2, _c3, _c4; + private readonly System.Collections.Generic.List _buffer1 = new(); + private readonly System.Collections.Generic.List _buffer2 = new(); + private readonly System.Collections.Generic.List _buffer3 = new(); + private readonly System.Collections.Generic.List _buffer4 = new(); + private readonly System.Collections.Generic.List _buffer5 = new(); + private readonly System.Collections.Generic.List _buffer6 = new(); + private readonly bool _useSMA; + private double _lastema1, _lastema2, _lastema3, _lastema4, _lastema5, _lastema6; + private double _llastema1, _llastema2, _llastema3, _llastema4, _llastema5, _llastema6; + protected int _len; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public T3_Series(int period, double vfactor, bool useSMA, bool useNaN) { + _period = period; + _len = 0; + _NaN = useNaN; + Name = $"T3({period})"; + _useSMA = useSMA; + double _a = vfactor; //0.7; //0.618 + _c1 = -_a * _a * _a; + _c2 = 3 * _a * _a + 3 * _a * _a * _a; + _c3 = -6 * _a * _a - 3 * _a - 3 * _a * _a * _a; + _c4 = 1 + 3 * _a + _a * _a * _a + 3 * _a * _a; + + _k = 2.0 / (_period + 1); + _k1m = 1.0 - _k; + _lastema1 = _llastema1 = _lastema2 = _llastema2 = _lastema3 = _llastema3 = _lastema4 = _llastema4 = _lastema5 = _llastema5 = _lastema5 = _llastema5 = 0; + } + public T3_Series(TSeries source, int period, double vfactor, bool useSMA, bool useNaN) : this(period, vfactor, useSMA, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public T3_Series() : this(period: 0, vfactor: 0.7, useSMA: true, useNaN: false) { } + public T3_Series(int period) : this(period: period, vfactor: 0.7, useSMA: true, useNaN: false) { } + public T3_Series(TBars source) : this(source.Close, 0, vfactor: 0.7, useSMA: true, useNaN: false) { } + public T3_Series(TBars source, int period) : this(source.Close, period, vfactor: 0.7, useSMA: true, useNaN: false) { } + public T3_Series(TBars source, int period, bool useNaN) : this(source.Close, period, vfactor: 0.7, useSMA: true, useNaN: useNaN) { } + public T3_Series(TBars source, int period, double vfactor, bool useNaN) : this(source.Close, period, vfactor: vfactor, useSMA: true, useNaN: useNaN) { } + public T3_Series(TBars source, int period, bool useSMA, bool useNaN) : this(source.Close, period, vfactor: 0.7, useSMA: useSMA, useNaN: useNaN) { } + public T3_Series(TSeries source) : this(source, 0, vfactor: 0.7, useSMA: true, useNaN: false) { } + public T3_Series(TSeries source, int period) : this(source: source, period: period, vfactor: 0.7, useSMA: true, useNaN: false) { } + public T3_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, vfactor: 0.7, useSMA: true, useNaN: useNaN) { } + public T3_Series(TSeries source, int period, double vfactor) : this(source: source, period: period, vfactor: vfactor, useSMA: true, useNaN: false) { } + public T3_Series(TSeries source, int period, double vfactor, bool useNaN) : this(source: source, period: period, vfactor: vfactor, useSMA: true, useNaN: useNaN) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + double _ema1, _ema2, _ema3, _ema4, _ema5, _ema6; + if (double.IsNaN(TValue.v)) { + return base.Add((TValue.t, Double.NaN),update); + } + + if (update) { _lastema1 = _llastema1; _lastema2 = _llastema2; _lastema3 = _llastema3; _lastema4 = _llastema4; _lastema5 = _llastema5; _lastema6 = _llastema6; } + else { _llastema1 = _lastema1; _llastema2 = _lastema2; _llastema3 = _lastema3; _llastema4 = _lastema4; _llastema5 = _lastema5; _llastema6 = _lastema6; } + + if (_len == 0) { _lastema1 = _lastema2 = _lastema3 = _lastema4 = _lastema5 = _lastema6 = TValue.v; } + + + if ((_len < _period) && _useSMA) { + BufferTrim(_buffer1, TValue.v, _period, update); + _ema1 = 0; + for (int i = 0; i < _buffer1.Count; i++) { _ema1 += _buffer1[i]; } + _ema1 /= _buffer1.Count; + + BufferTrim(_buffer2, _ema1, _period, update); + _ema2 = 0; + for (int i = 0; i < _buffer2.Count; i++) { _ema2 += _buffer2[i]; } + _ema2 /= _buffer2.Count; + + BufferTrim(_buffer3, _ema2, _period, update); + _ema3 = 0; + for (int i = 0; i < _buffer3.Count; i++) { _ema3 += _buffer3[i]; } + _ema3 /= _buffer3.Count; + + BufferTrim(_buffer4, _ema3, _period, update); + _ema4 = 0; + for (int i = 0; i < _buffer4.Count; i++) { _ema4 += _buffer4[i]; } + _ema4 /= _buffer4.Count; + + BufferTrim(_buffer5, _ema4, _period, update); + _ema5 = 0; + for (int i = 0; i < _buffer5.Count; i++) { _ema5 += _buffer5[i]; } + _ema5 /= _buffer5.Count; + + BufferTrim(_buffer6, _ema5, _period, update); + _ema6 = 0; + for (int i = 0; i < _buffer6.Count; i++) { _ema6 += _buffer6[i]; } + _ema6 /= _buffer6.Count; + } + else { + _ema1 = (TValue.v * this._k) + (this._lastema1 * this._k1m); + _ema2 = (_ema1 * this._k) + (this._lastema2 * this._k1m); + _ema3 = (_ema2 * this._k) + (this._lastema3 * this._k1m); + _ema4 = (_ema3 * this._k) + (this._lastema4 * this._k1m); + _ema5 = (_ema4 * this._k) + (this._lastema5 * this._k1m); + _ema6 = (_ema5 * this._k) + (this._lastema6 * this._k1m); + } + _len++; + _lastema1 = _ema1; + _lastema2 = _ema2; + _lastema3 = _ema3; + _lastema4 = _ema4; + _lastema5 = _ema5; + _lastema6 = _ema6; + + double _T3 = _c1 * _ema6 + _c2 * _ema5 + _c3 * _ema4 + _c4 * _ema3; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _T3); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _lastema1 = _llastema1 = _lastema2 = _llastema2 = _lastema3 = _llastema3 = _lastema4 = _llastema4 = _lastema5 = _llastema5 = _lastema5 = _llastema5 = 0; + _buffer1.Clear(); + _buffer2.Clear(); + _buffer3.Clear(); + _buffer4.Clear(); + _buffer5.Clear(); + _buffer6.Clear(); + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/TBars.cs b/Calculations/_Updated/TBars.cs index 920dd17c..0c0274d4 100644 --- a/Calculations/_Updated/TBars.cs +++ b/Calculations/_Updated/TBars.cs @@ -1,138 +1,138 @@ -namespace QuanTAlib; -using System; - -/* -TBars class - includes all series for common data used in indicators and other calculations. - Has a bit limited overloading and casting (compared to TSeries) - Includes Select(int) method to simplify choosing the most optimal data source for indicators - Includes the most basic pricing calcs: HL2, OC2, OHL3, HLC3, OHLC4, HLCC4 - (it is 'cheaper' to calculate them once during data capture than each time during data analysis) - - */ - -public class TBars : System.Collections.Generic.List<(DateTime t, double o, double h, double l, double c, double v)> -{ - public string Name { get; set; } - private readonly TSeries _open = new("open"); - private readonly TSeries _high = new("high"); - private readonly TSeries _low = new("low"); - private readonly TSeries _close = new("close"); - private readonly TSeries _volume = new("volume"); - private readonly TSeries _hl2 = new("HL2"); - private readonly TSeries _oc2 = new("OC2"); - private readonly TSeries _ohl3 = new("OHL3"); - private readonly TSeries _hlc3 = new("HLC3"); - private readonly TSeries _ohlc4 = new("OHLC4"); - private readonly TSeries _hlcc4 = new("HLCC4"); - - public TSeries Open => this._open; - public TSeries High => this._high; - public TSeries Low => this._low; - public TSeries Close => this._close; - public TSeries Volume => this._volume; - public TSeries HL2 => this._hl2; - public TSeries OC2 => this._oc2; - public TSeries OHL3 => this._ohl3; - public TSeries HLC3 => this._hlc3; - public TSeries OHLC4 => this._ohlc4; - public TSeries HLCC4 => this._hlcc4; - - public TBars() { } - - public TBars(string Name) { - this.Name = Name; - } - - public (DateTime t, double o, double h, double l, double c, double v) Last => this[^1]; - public TBars Tail(int count = 10) - { - TBars outBars = new(); - if (count > this.Count) { count = this.Count; } - for (int i = this.Count - count; i < this.Count; i++) { outBars.Add(this[i]); } - return outBars; - } - public TSeries Select(int source) - { - return source switch - { - 0 => _open, - 1 => _high, - 2 => _low, - 3 => _close, - 4 => _hl2, - 5 => _oc2, - 6 => _ohl3, - 7 => _hlc3, - 8 => _ohlc4, - _ => _hlcc4, - }; - } - public static string SelectStr(int source) - { - return source switch - { - 0 => "Open", - 1 => "High", - 2 => "Low", - 3 => "Close", - 4 => "HL2", - 5 => "OC2", - 6 => "OHL3", - 7 => "HLC3", - 8 => "OHLC4", - _ => "HLCC4", - }; - } - - public virtual (DateTime t, double v) Add((double o, double h, double l, double c, double v) p, bool update = false) => - Add((t: (this.Count == 0) ? DateTime.Today : this[^1].t.AddDays(1),p.o,p.h,p.l,p.c,p.v),update); - - public virtual (DateTime t, double v) Add(double o, double h, double l, double c, double v, bool update = false) => - Add((o,h,l,c,v),update); - - public virtual (DateTime t, double v) Add(DateTime t, double o, double h, double l, double c, double v, bool update = false) => - this.Add((t, o, h, l, c, v), update); - - public virtual (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - if (update) { this[^1] = TBar; } else { base.Add(TBar); } - - _open.Add((TBar.t, TBar.o), update); - _high.Add((TBar.t, TBar.h), update); - _low.Add((TBar.t, TBar.l), update); - _close.Add((TBar.t, TBar.c), update); - _volume.Add((TBar.t, TBar.v), update); - _hl2.Add((TBar.t, (TBar.h + TBar.l) * 0.5), update); - _oc2.Add((TBar.t, (TBar.o + TBar.c) * 0.5), update); - _ohl3.Add((TBar.t, (TBar.o + TBar.h + TBar.l) * 0.333333333333333), update); - _hlc3.Add((TBar.t, (TBar.h + TBar.l + TBar.c) * 0.333333333333333), update); - _ohlc4.Add((TBar.t, (TBar.o + TBar.h + TBar.l + TBar.c) * 0.25), update); - _hlcc4.Add((TBar.t, (TBar.h + TBar.l + TBar.c + TBar.c) * 0.25), update); - - this.OnEvent(update); - return (TBar.t, (TBar.o + TBar.h + TBar.l + TBar.c) * 0.25); - } - - public delegate void NewDataEventHandler(object source, TSeriesEventArgs args); - public event NewDataEventHandler Pub; - protected virtual void OnEvent(bool update = false) { if (Pub != null && Pub.Target != this) { - Pub(this, new TSeriesEventArgs { update = update }); } } - - public void Sub(object source, TSeriesEventArgs e) { TBars ss = (TBars)source; if (ss.Count > 1) { - for (int i = 0; i < ss.Count; i++) { this.Add(ss[i]); } - } else { - this.Add(ss[^1], e.update); - } - } - - /// common helpers - public static void BufferTrim(System.Collections.Generic.List buffer, double value, int period, bool update) { - if (!update) { - buffer.Add(value); - if (buffer.Count > period && period > 0) { buffer.RemoveAt(0); } - return; - } - buffer[^1] = value; - } - public virtual void Reset() { - } -} +namespace QuanTAlib; +using System; + +/* +TBars class - includes all series for common data used in indicators and other calculations. + Has a bit limited overloading and casting (compared to TSeries) + Includes Select(int) method to simplify choosing the most optimal data source for indicators + Includes the most basic pricing calcs: HL2, OC2, OHL3, HLC3, OHLC4, HLCC4 + (it is 'cheaper' to calculate them once during data capture than each time during data analysis) + + */ + +public class TBars : System.Collections.Generic.List<(DateTime t, double o, double h, double l, double c, double v)> +{ + public string Name { get; set; } + private readonly TSeries _open = new("open"); + private readonly TSeries _high = new("high"); + private readonly TSeries _low = new("low"); + private readonly TSeries _close = new("close"); + private readonly TSeries _volume = new("volume"); + private readonly TSeries _hl2 = new("HL2"); + private readonly TSeries _oc2 = new("OC2"); + private readonly TSeries _ohl3 = new("OHL3"); + private readonly TSeries _hlc3 = new("HLC3"); + private readonly TSeries _ohlc4 = new("OHLC4"); + private readonly TSeries _hlcc4 = new("HLCC4"); + + public TSeries Open => this._open; + public TSeries High => this._high; + public TSeries Low => this._low; + public TSeries Close => this._close; + public TSeries Volume => this._volume; + public TSeries HL2 => this._hl2; + public TSeries OC2 => this._oc2; + public TSeries OHL3 => this._ohl3; + public TSeries HLC3 => this._hlc3; + public TSeries OHLC4 => this._ohlc4; + public TSeries HLCC4 => this._hlcc4; + + public TBars() { } + + public TBars(string Name) { + this.Name = Name; + } + + public (DateTime t, double o, double h, double l, double c, double v) Last => this[^1]; + public TBars Tail(int count = 10) + { + TBars outBars = new(); + if (count > this.Count) { count = this.Count; } + for (int i = this.Count - count; i < this.Count; i++) { outBars.Add(this[i]); } + return outBars; + } + public TSeries Select(int source) + { + return source switch + { + 0 => _open, + 1 => _high, + 2 => _low, + 3 => _close, + 4 => _hl2, + 5 => _oc2, + 6 => _ohl3, + 7 => _hlc3, + 8 => _ohlc4, + _ => _hlcc4, + }; + } + public static string SelectStr(int source) + { + return source switch + { + 0 => "Open", + 1 => "High", + 2 => "Low", + 3 => "Close", + 4 => "HL2", + 5 => "OC2", + 6 => "OHL3", + 7 => "HLC3", + 8 => "OHLC4", + _ => "HLCC4", + }; + } + + public virtual (DateTime t, double v) Add((double o, double h, double l, double c, double v) p, bool update = false) => + Add((t: (this.Count == 0) ? DateTime.Today : this[^1].t.AddDays(1),p.o,p.h,p.l,p.c,p.v),update); + + public virtual (DateTime t, double v) Add(double o, double h, double l, double c, double v, bool update = false) => + Add((o,h,l,c,v),update); + + public virtual (DateTime t, double v) Add(DateTime t, double o, double h, double l, double c, double v, bool update = false) => + this.Add((t, o, h, l, c, v), update); + + public virtual (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { + if (update) { this[^1] = TBar; } else { base.Add(TBar); } + + _open.Add((TBar.t, TBar.o), update); + _high.Add((TBar.t, TBar.h), update); + _low.Add((TBar.t, TBar.l), update); + _close.Add((TBar.t, TBar.c), update); + _volume.Add((TBar.t, TBar.v), update); + _hl2.Add((TBar.t, (TBar.h + TBar.l) * 0.5), update); + _oc2.Add((TBar.t, (TBar.o + TBar.c) * 0.5), update); + _ohl3.Add((TBar.t, (TBar.o + TBar.h + TBar.l) * 0.333333333333333), update); + _hlc3.Add((TBar.t, (TBar.h + TBar.l + TBar.c) * 0.333333333333333), update); + _ohlc4.Add((TBar.t, (TBar.o + TBar.h + TBar.l + TBar.c) * 0.25), update); + _hlcc4.Add((TBar.t, (TBar.h + TBar.l + TBar.c + TBar.c) * 0.25), update); + + this.OnEvent(update); + return (TBar.t, (TBar.o + TBar.h + TBar.l + TBar.c) * 0.25); + } + + public delegate void NewDataEventHandler(object source, TSeriesEventArgs args); + public event NewDataEventHandler Pub; + protected virtual void OnEvent(bool update = false) { if (Pub != null && Pub.Target != this) { + Pub(this, new TSeriesEventArgs { update = update }); } } + + public void Sub(object source, TSeriesEventArgs e) { TBars ss = (TBars)source; if (ss.Count > 1) { + for (int i = 0; i < ss.Count; i++) { this.Add(ss[i]); } + } else { + this.Add(ss[^1], e.update); + } + } + + /// common helpers + public static void BufferTrim(System.Collections.Generic.List buffer, double value, int period, bool update) { + if (!update) { + buffer.Add(value); + if (buffer.Count > period && period > 0) { buffer.RemoveAt(0); } + return; + } + buffer[^1] = value; + } + public virtual void Reset() { + } +} diff --git a/Calculations/_Updated/TEMA_Series.cs b/Calculations/_Updated/TEMA_Series.cs index a9653f6b..f48019fa 100644 --- a/Calculations/_Updated/TEMA_Series.cs +++ b/Calculations/_Updated/TEMA_Series.cs @@ -1,120 +1,120 @@ -namespace QuanTAlib; - -using System; -using System.Linq; - -/* -TEMA: Triple Exponential Moving Average - TEMA uses EMA(EMA(EMA())) to calculate less laggy Exponential moving average. - -Sources: - https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/triple-exponential-moving-average-tema/ - -Remark: - ema1 = EMA(close, length) - ema2 = EMA(ema1, length) - ema3 = EMA(ema2, length) - TEMA = 3 * (ema1 - ema2) + ema3 - - */ - -public class TEMA_Series : TSeries { - private double _k; - private double _sum, _oldsum; - private double _lastema1, _oldema1, _lastema2, _oldema2, _lastema3, _oldema3; - private int _len; - private readonly bool _useSMA; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - -//core constructor - public TEMA_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - _useSMA = useSMA; - Name = $"TEMA({period})"; - _k = 2.0 / (_period + 1); - _len = 0; - _sum = _oldsum = _lastema1 = _lastema2 = _lastema3 = 0; - } - public TEMA_Series() : this(0, false, true) {} - public TEMA_Series(int period) : this(period, false, true) {} - public TEMA_Series(TBars source) : this(source.Close, 0, false) {} - public TEMA_Series(TBars source, int period) : this(source.Close, period, false) {} - public TEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} - public TEMA_Series(TSeries source, int period) : this(source, period, false, true) {} - public TEMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} - public TEMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - -// core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { - _lastema1 = _oldema1; - _lastema2 = _oldema2; - _lastema3 = _oldema3; - _sum = _oldsum; - } - else { - _oldema1 = _lastema1; - _oldema2 = _lastema2; - _oldema3 = _lastema3; - _oldsum = _sum; - _len++; - } - - if (_period == 0) { _k = 2.0 / (_len + 1); } - - double _ema1, _ema2, _ema3, _tema; - if (this.Count == 0) { - _ema1 = _ema2 = _ema3 =_sum = TValue.v; - } - else if (_len <= _period && _useSMA && _period != 0) { - _sum += TValue.v; - _ema1 = _sum / Math.Min(_len, _period); - _ema2 = _ema1; - _ema3 = _ema2; - } - else { - _ema1 = (TValue.v - _lastema1) * _k + _lastema1; - _ema2 = (_ema1 - _lastema2) * _k + _lastema2; - _ema3 = (_ema2 - _lastema3) * _k + _lastema3; - } - - _tema = (3 * (_ema1 - _ema2)) + _ema3; - - _lastema1 = Double.IsNaN(_ema1)?_lastema1:_ema1; - _lastema2 = Double.IsNaN(_ema2)?_lastema2:_ema2; - _lastema3 = Double.IsNaN(_ema3) ? _lastema3 : _ema3; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _tema); - return base.Add(res, update); - } - -//variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _sum = _oldsum = _lastema1 = _lastema2 = 0; - _len = 0; - } +namespace QuanTAlib; + +using System; +using System.Linq; + +/* +TEMA: Triple Exponential Moving Average + TEMA uses EMA(EMA(EMA())) to calculate less laggy Exponential moving average. + +Sources: + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/triple-exponential-moving-average-tema/ + +Remark: + ema1 = EMA(close, length) + ema2 = EMA(ema1, length) + ema3 = EMA(ema2, length) + TEMA = 3 * (ema1 - ema2) + ema3 + + */ + +public class TEMA_Series : TSeries { + private double _k; + private double _sum, _oldsum; + private double _lastema1, _oldema1, _lastema2, _oldema2, _lastema3, _oldema3; + private int _len; + private readonly bool _useSMA; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + +//core constructor + public TEMA_Series(int period, bool useNaN, bool useSMA) { + _period = period; + _NaN = useNaN; + _useSMA = useSMA; + Name = $"TEMA({period})"; + _k = 2.0 / (_period + 1); + _len = 0; + _sum = _oldsum = _lastema1 = _lastema2 = _lastema3 = 0; + } + public TEMA_Series() : this(0, false, true) {} + public TEMA_Series(int period) : this(period, false, true) {} + public TEMA_Series(TBars source) : this(source.Close, 0, false) {} + public TEMA_Series(TBars source, int period) : this(source.Close, period, false) {} + public TEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} + public TEMA_Series(TSeries source, int period) : this(source, period, false, true) {} + public TEMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} + public TEMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + +// core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (update) { + _lastema1 = _oldema1; + _lastema2 = _oldema2; + _lastema3 = _oldema3; + _sum = _oldsum; + } + else { + _oldema1 = _lastema1; + _oldema2 = _lastema2; + _oldema3 = _lastema3; + _oldsum = _sum; + _len++; + } + + if (_period == 0) { _k = 2.0 / (_len + 1); } + + double _ema1, _ema2, _ema3, _tema; + if (this.Count == 0) { + _ema1 = _ema2 = _ema3 =_sum = TValue.v; + } + else if (_len <= _period && _useSMA && _period != 0) { + _sum += TValue.v; + _ema1 = _sum / Math.Min(_len, _period); + _ema2 = _ema1; + _ema3 = _ema2; + } + else { + _ema1 = (TValue.v - _lastema1) * _k + _lastema1; + _ema2 = (_ema1 - _lastema2) * _k + _lastema2; + _ema3 = (_ema2 - _lastema3) * _k + _lastema3; + } + + _tema = (3 * (_ema1 - _ema2)) + _ema3; + + _lastema1 = Double.IsNaN(_ema1)?_lastema1:_ema1; + _lastema2 = Double.IsNaN(_ema2)?_lastema2:_ema2; + _lastema3 = Double.IsNaN(_ema3) ? _lastema3 : _ema3; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _tema); + return base.Add(res, update); + } + +//variation of Add() + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _sum = _oldsum = _lastema1 = _lastema2 = 0; + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/TRIMA_Series.cs b/Calculations/_Updated/TRIMA_Series.cs index 210be772..050a4b88 100644 --- a/Calculations/_Updated/TRIMA_Series.cs +++ b/Calculations/_Updated/TRIMA_Series.cs @@ -1,84 +1,84 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -TRIMA: Triangular Moving Average - A weighted moving average where the shape of the weights are triangular and the greatest - weight is in the middle of the period, - -Sources: - https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/triangular-moving-average-trima/ - -Remark: - trima = sma(sma(signal, n/2), n/2) - - */ - -public class TRIMA_Series : TSeries { - private readonly int _p1a, _p1b; - private readonly SMA_Series sma, trima; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public TRIMA_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"xMA({period})"; - _p1a = (int)Math.Floor((period * 0.5) + 1); - _p1b = (int)Math.Ceiling(0.5 * period); - sma = new(_p1a); - trima = new(_p1b); - - } - public TRIMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public TRIMA_Series() : this(period: 0, useNaN: false) { } - public TRIMA_Series(int period) : this(period: period, useNaN: false) { } - public TRIMA_Series(TBars source) : this(source.Close, 0, false) { } - public TRIMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public TRIMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public TRIMA_Series(TSeries source) : this(source, 0, false) { } - public TRIMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - - var _sma = sma.Add(TValue, update); - var _trima = trima.Add(_sma, update); - - var res = (_trima.t, Count < _period - 1 && _NaN ? double.NaN : _trima.v); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - sma.Reset(); - trima.Reset(); - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +TRIMA: Triangular Moving Average + A weighted moving average where the shape of the weights are triangular and the greatest + weight is in the middle of the period, + +Sources: + https://www.tradingtechnologies.com/help/x-study/technical-indicator-definitions/triangular-moving-average-trima/ + +Remark: + trima = sma(sma(signal, n/2), n/2) + + */ + +public class TRIMA_Series : TSeries { + private readonly int _p1a, _p1b; + private readonly SMA_Series sma, trima; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public TRIMA_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"xMA({period})"; + _p1a = (int)Math.Floor((period * 0.5) + 1); + _p1b = (int)Math.Ceiling(0.5 * period); + sma = new(_p1a); + trima = new(_p1b); + + } + public TRIMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public TRIMA_Series() : this(period: 0, useNaN: false) { } + public TRIMA_Series(int period) : this(period: period, useNaN: false) { } + public TRIMA_Series(TBars source) : this(source.Close, 0, false) { } + public TRIMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public TRIMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public TRIMA_Series(TSeries source) : this(source, 0, false) { } + public TRIMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (double.IsNaN(TValue.v)) { + return base.Add((TValue.t, Double.NaN), update); + } + + var _sma = sma.Add(TValue, update); + var _trima = trima.Add(_sma, update); + + var res = (_trima.t, Count < _period - 1 && _NaN ? double.NaN : _trima.v); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + sma.Reset(); + trima.Reset(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/TRIX_Series.cs b/Calculations/_Updated/TRIX_Series.cs index 30d9f58e..f955bf04 100644 --- a/Calculations/_Updated/TRIX_Series.cs +++ b/Calculations/_Updated/TRIX_Series.cs @@ -1,118 +1,118 @@ -namespace QuanTAlib; - -using System; -using System.Linq; - -/* -TRIX: Triple Exponential Average Oscillator - Developed by Jack Hutson in the early 1980s, the triple exponential average (TRIX) - has become a popular technical analysis tool to aid chartists in spotting diversions - and directional cues in stock trading patterns. - -Sources: - https://www.investopedia.com/terms/t/trix.asp - - */ - -public class TRIX_Series : TSeries { - private readonly double _k; - private readonly System.Collections.Generic.List _buffer1 = new(); - private readonly System.Collections.Generic.List _buffer2 = new(); - private readonly System.Collections.Generic.List _buffer3 = new(); - private double _lastema1, _lastema2, _lastema3; - private double _llastema1, _llastema2, _llastema3; - private int _len; - private readonly bool _useSMA; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - -//core constructors - - public TRIX_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - _useSMA = useSMA; - Name = $"TRIX({period})"; - _k = 2.0 / (_period + 1); - _len = 0; - _lastema1 = _llastema1 = _lastema2 = _llastema2 = _lastema3 = _llastema3 = 0; - } - public TRIX_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public TRIX_Series() : this(0, false, true) {} - public TRIX_Series(int period) : this(period, false, true) {} - public TRIX_Series(TBars source) : this(source.Close, 0, false) {} - public TRIX_Series(TBars source, int period) : this(source.Close, period, false) {} - public TRIX_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} - public TRIX_Series(TSeries source, int period) : this(source, period, false, true) {} - public TRIX_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} - - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - if (_len == 0) { _lastema1 = _lastema2 = _lastema3 = TValue.v; } - if (update) { _lastema1 = _llastema1; _lastema2 = _llastema2; _lastema3 = _llastema3; } - else { _llastema1 = _lastema1; _llastema2 = _lastema2; _llastema3 = _lastema3; _len++; - } - - double _ema1, _ema2, _ema3; - if ((this.Count < _period) && _useSMA) { - BufferTrim(_buffer1, TValue.v, _period, update); - _ema1 = 0; - for (int i = 0; i < _buffer1.Count; i++) { _ema1 += _buffer1[i]; } - _ema1 /= _buffer1.Count; - - BufferTrim(_buffer2, _ema1, _period, update); - _ema2 = 0; - for (int i = 0; i < _buffer2.Count; i++) { _ema2 += _buffer2[i]; } - _ema2 /= _buffer2.Count; - - BufferTrim(_buffer3, _ema2, _period, update); - _ema3 = 0; - for (int i = 0; i < _buffer3.Count; i++) { _ema3 += _buffer3[i]; } - _ema3 /= _buffer3.Count; - } - else { - _ema1 = (TValue.v - _lastema1) * _k + _lastema1; - _ema2 = (_ema1 - _lastema2) * _k + _lastema2; - _ema3 = (_ema2 - _lastema3) * _k + _lastema3; - } - double _trix = 100 * (_ema3 - _lastema3) / _lastema3; - _lastema1 = _ema1; - _lastema2 = _ema2; - _lastema3 = _ema3; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _trix); - return base.Add(res, update); - } - -//variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _len = 0; - } +namespace QuanTAlib; + +using System; +using System.Linq; + +/* +TRIX: Triple Exponential Average Oscillator + Developed by Jack Hutson in the early 1980s, the triple exponential average (TRIX) + has become a popular technical analysis tool to aid chartists in spotting diversions + and directional cues in stock trading patterns. + +Sources: + https://www.investopedia.com/terms/t/trix.asp + + */ + +public class TRIX_Series : TSeries { + private readonly double _k; + private readonly System.Collections.Generic.List _buffer1 = new(); + private readonly System.Collections.Generic.List _buffer2 = new(); + private readonly System.Collections.Generic.List _buffer3 = new(); + private double _lastema1, _lastema2, _lastema3; + private double _llastema1, _llastema2, _llastema3; + private int _len; + private readonly bool _useSMA; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + +//core constructors + + public TRIX_Series(int period, bool useNaN, bool useSMA) { + _period = period; + _NaN = useNaN; + _useSMA = useSMA; + Name = $"TRIX({period})"; + _k = 2.0 / (_period + 1); + _len = 0; + _lastema1 = _llastema1 = _lastema2 = _llastema2 = _lastema3 = _llastema3 = 0; + } + public TRIX_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public TRIX_Series() : this(0, false, true) {} + public TRIX_Series(int period) : this(period, false, true) {} + public TRIX_Series(TBars source) : this(source.Close, 0, false) {} + public TRIX_Series(TBars source, int period) : this(source.Close, period, false) {} + public TRIX_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} + public TRIX_Series(TSeries source, int period) : this(source, period, false, true) {} + public TRIX_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} + + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (double.IsNaN(TValue.v)) { + return base.Add((TValue.t, Double.NaN), update); + } + if (_len == 0) { _lastema1 = _lastema2 = _lastema3 = TValue.v; } + if (update) { _lastema1 = _llastema1; _lastema2 = _llastema2; _lastema3 = _llastema3; } + else { _llastema1 = _lastema1; _llastema2 = _lastema2; _llastema3 = _lastema3; _len++; + } + + double _ema1, _ema2, _ema3; + if ((this.Count < _period) && _useSMA) { + BufferTrim(_buffer1, TValue.v, _period, update); + _ema1 = 0; + for (int i = 0; i < _buffer1.Count; i++) { _ema1 += _buffer1[i]; } + _ema1 /= _buffer1.Count; + + BufferTrim(_buffer2, _ema1, _period, update); + _ema2 = 0; + for (int i = 0; i < _buffer2.Count; i++) { _ema2 += _buffer2[i]; } + _ema2 /= _buffer2.Count; + + BufferTrim(_buffer3, _ema2, _period, update); + _ema3 = 0; + for (int i = 0; i < _buffer3.Count; i++) { _ema3 += _buffer3[i]; } + _ema3 /= _buffer3.Count; + } + else { + _ema1 = (TValue.v - _lastema1) * _k + _lastema1; + _ema2 = (_ema1 - _lastema2) * _k + _lastema2; + _ema3 = (_ema2 - _lastema3) * _k + _lastema3; + } + double _trix = 100 * (_ema3 - _lastema3) / _lastema3; + _lastema1 = _ema1; + _lastema2 = _ema2; + _lastema3 = _ema3; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _trix); + return base.Add(res, update); + } + +//variation of Add() + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/TR_Series.cs b/Calculations/_Updated/TR_Series.cs index e3c61058..9365d5f6 100644 --- a/Calculations/_Updated/TR_Series.cs +++ b/Calculations/_Updated/TR_Series.cs @@ -1,79 +1,79 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -TR: True Range - True Range was introduced by J. Welles Wilder in his book New Concepts in Technical Trading Systems. - It measures the daily range plus any gap from the closing price of the preceding day. - -Calculation: - d1 = ABS(High - Low) - d2 = ABS(High - Previous close) - d3 = ABS(Previous close - Low) - TR = MAX(d1,d2,d3) - -Sources: - https://www.macroption.com/true-range/ - - */ - -public class TR_Series : TSeries { - protected readonly TBars _data; - private double _cm1, _cm1_o; - - //core constructors - public TR_Series() { - Name = $"TR()"; - _cm1 = _cm1_o = double.NaN; - } - public TR_Series(TBars source) { - _data = source; - Name = $"TR({(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _cm1 = _cm1_o = double.NaN; - _data.Pub += Sub; - Add(data: _data); - } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - - if (update) { - _cm1 = _cm1_o; - } - else { - _cm1_o = _cm1; - } - - if (_cm1 is double.NaN) { - _cm1 = TBar.c; - } - - double d1 = Math.Abs(TBar.h - TBar.l); - double d2 = Math.Abs(_cm1 - TBar.h); - double d3 = Math.Abs(_cm1 - TBar.l); - _cm1 = TBar.c; - var ret = (TBar.t, Math.Max(d1, Math.Max(d2, d3))); - return base.Add(ret, update); - - } - - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _cm1 = _cm1_o = double.NaN; - } +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +TR: True Range + True Range was introduced by J. Welles Wilder in his book New Concepts in Technical Trading Systems. + It measures the daily range plus any gap from the closing price of the preceding day. + +Calculation: + d1 = ABS(High - Low) + d2 = ABS(High - Previous close) + d3 = ABS(Previous close - Low) + TR = MAX(d1,d2,d3) + +Sources: + https://www.macroption.com/true-range/ + + */ + +public class TR_Series : TSeries { + protected readonly TBars _data; + private double _cm1, _cm1_o; + + //core constructors + public TR_Series() { + Name = $"TR()"; + _cm1 = _cm1_o = double.NaN; + } + public TR_Series(TBars source) { + _data = source; + Name = $"TR({(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _cm1 = _cm1_o = double.NaN; + _data.Pub += Sub; + Add(data: _data); + } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { + + if (update) { + _cm1 = _cm1_o; + } + else { + _cm1_o = _cm1; + } + + if (_cm1 is double.NaN) { + _cm1 = TBar.c; + } + + double d1 = Math.Abs(TBar.h - TBar.l); + double d2 = Math.Abs(_cm1 - TBar.h); + double d3 = Math.Abs(_cm1 - TBar.l); + _cm1 = TBar.c; + var ret = (TBar.t, Math.Max(d1, Math.Max(d2, d3))); + return base.Add(ret, update); + + } + + public new void Add(TBars data) { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TBar: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _cm1 = _cm1_o = double.NaN; + } } \ No newline at end of file diff --git a/Calculations/_Updated/TSeries.cs b/Calculations/_Updated/TSeries.cs index ccde468d..4a8a6312 100644 --- a/Calculations/_Updated/TSeries.cs +++ b/Calculations/_Updated/TSeries.cs @@ -1,103 +1,117 @@ -namespace QuanTAlib; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Data; -using System.Linq; - -/* -TSeries is the cornerstone of all QuanTAlib classes. - TSeries is a single List of tuples (time, value) and contains several operators, casts, overloads - and other helpers that simplify usage of library. - Think of TSeries as an equivalent of Numpy array. - - - includes Length property (to mimic array's method) - - includes publishing and subscribing methods that attach to events - - */ -public class TSeriesEventArgs : EventArgs { - public bool update { get; set; } -} - -public class TSeries : List<(DateTime t, double v)> { - private readonly (DateTime t, double v) Default = (DateTime.MinValue, double.NaN); - public IEnumerable t => this.Select(item => item.t); - public IEnumerable v => this.Select(item => item.v); - public (DateTime t, double v) Last => Count > 0 ? this[^1] : Default; - - public int Length => Count; - public string Name { get; set; } - - public TSeries() { - this.Name = "data"; - } - - public TSeries(string Name) { - this.Name = Name; - } - - public virtual (DateTime t, double v) Add(double v, bool update = false) { - return Add((t: Count == 0 ? DateTime.Today : this[^1].t.AddDays(1), v), update); - } - - public virtual (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { - this[^1] = TValue; - } - else { - base.Add(TValue); - } - - OnEvent(update); - return TValue; - } - - public virtual (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - if (update) { - this[this.Count - 1] = (TBar.t, TBar.c); - } - else { - base.Add((TBar.t, TBar.c)); - } - - OnEvent(update); - return (TBar.t, TBar.c); - } - - public virtual (DateTime t, double v) Add(TSeries data) { - foreach (var item in data) { Add(item); } - return data.Last; - } - - public virtual (DateTime t, double v) Add(TBars data) { - foreach (var item in data) { Add(item.c, false); } - return (data.Last.t, data.Last.c); - } - - public void Sub(object source, TSeriesEventArgs e) { - var data = (TSeries) source; - if (data == null) { return; } - foreach (var item in data) { Add(item); } - } - - public delegate void NewEventHandler(object source, TSeriesEventArgs args); - - public event NewEventHandler Pub; - - protected virtual void OnEvent(bool update = false) - { - Pub?.Invoke(this, new TSeriesEventArgs {update = update}); - } - - /// common helpers - public static void BufferTrim(List buffer, double value, int period, bool update) { - if (!update) { - buffer.Add(value); - if (buffer.Count > period && period > 0) { buffer.RemoveAt(0); } - return; - } - buffer[^1] = value; - } - public virtual void Reset() { - } -} +namespace QuanTAlib; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Data; +using System.Linq; + +/* +TSeries is the cornerstone of all QuanTAlib classes. + TSeries is a single List of tuples (time, value) and contains several operators, casts, overloads + and other helpers that simplify usage of library. + Think of TSeries as an equivalent of Numpy array. + + - includes Length property (to mimic array's method) + - includes publishing and subscribing methods that attach to events + + */ +public class TSeriesEventArgs : EventArgs { + public bool update { get; set; } +} + +public class TSeries : List<(DateTime t, double v)> { + private readonly (DateTime t, double v) Default = (DateTime.MinValue, double.NaN); + public IEnumerable t => this.Select(item => item.t); + public IEnumerable v => this.Select(item => item.v); + public (DateTime t, double v) Last => Count > 0 ? this[^1] : Default; + + public int Length => Count; + public string Name { get; set; } + public int Keep = 0; + + public TSeries() { + this.Name = "data"; + } + + public TSeries(string Name) { + this.Name = Name; + } + + public virtual (DateTime t, double v) Add(double v, bool update = false) { + return Add((t: Count == 0 ? DateTime.Today : this[^1].t.AddDays(1), v), update); + } + + public virtual (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + if (update) { + this[^1] = TValue; + } + else { + base.Add(TValue); + } + + OnEvent(update); + return TValue; + } + + public virtual (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { + if (update) { + this[this.Count - 1] = (TBar.t, TBar.c); + } + else { + base.Add((TBar.t, TBar.c)); + } + + OnEvent(update); + return (TBar.t, TBar.c); + } + + public virtual (DateTime t, double v) Add(TSeries data) { + foreach (var item in data) { Add(item); } + return data.Last; + } + + public virtual (DateTime t, double v) Add(TBars data) { + foreach (var item in data) { Add(item.c, false); } + return (data.Last.t, data.Last.c); + } + + public void Sub(object source, TSeriesEventArgs e) { + var data = (TSeries) source; + if (data == null) { return; } + foreach (var item in data) { Add(item); } + } + + public delegate void NewEventHandler(object source, TSeriesEventArgs args); + + public event NewEventHandler Pub; + + protected virtual void OnEvent(bool update = false) + { + if (Keep > 0) { + TrimToSize(keep:Keep); + } + Pub?.Invoke(this, new TSeriesEventArgs {update = update}); + } + + /// common helpers + public static void BufferTrim(List buffer, double value, int period, bool update) { + if (!update) { + buffer.Add(value); + if (buffer.Count > period && period > 0) { buffer.RemoveAt(0); } + return; + } + buffer[^1] = value; + } + public virtual void Reset() { + } + + public void TrimToSize(int keep) { + if (keep >= this.Count) { + return; // No need to trim if the series is already smaller than or equal to n + } + + // Remove elements from the beginning of the list + int elementsToRemove = this.Count - keep; + RemoveRange(0, elementsToRemove); + } +} diff --git a/Calculations/_Updated/VAR_Series.cs b/Calculations/_Updated/VAR_Series.cs index 72562277..fcd0caba 100644 --- a/Calculations/_Updated/VAR_Series.cs +++ b/Calculations/_Updated/VAR_Series.cs @@ -1,81 +1,81 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -VAR: Population Variance - Population variance without Bessel's correction - -Sources: - https://en.wikipedia.org/wiki/Variance - Bessel's correction: https://en.wikipedia.org/wiki/Bessel%27s_correction - -Remark: - VAR (Population Variance) is also known as a biased Sample Variance. For unbiased - sample variance use SVAR instead. - - */ - -public class VAR_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public VAR_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"VAR({period})"; - } - public VAR_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public VAR_Series() : this(period: 0, useNaN: false) { } - public VAR_Series(int period) : this(period: period, useNaN: false) { } - public VAR_Series(TBars source) : this(source.Close, 0, false) { } - public VAR_Series(TBars source, int period) : this(source.Close, period, false) { } - public VAR_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public VAR_Series(TSeries source) : this(source, 0, false) { } - public VAR_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _sma = _buffer.Average(); - - double _pvar = 0; - for (int i = 0; i < _buffer.Count; i++) { _pvar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } - _pvar /= this._buffer.Count; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _pvar); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +VAR: Population Variance + Population variance without Bessel's correction + +Sources: + https://en.wikipedia.org/wiki/Variance + Bessel's correction: https://en.wikipedia.org/wiki/Bessel%27s_correction + +Remark: + VAR (Population Variance) is also known as a biased Sample Variance. For unbiased + sample variance use SVAR instead. + + */ + +public class VAR_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public VAR_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"VAR({period})"; + } + public VAR_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public VAR_Series() : this(period: 0, useNaN: false) { } + public VAR_Series(int period) : this(period: period, useNaN: false) { } + public VAR_Series(TBars source) : this(source.Close, 0, false) { } + public VAR_Series(TBars source, int period) : this(source.Close, period, false) { } + public VAR_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public VAR_Series(TSeries source) : this(source, 0, false) { } + public VAR_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _sma = _buffer.Average(); + + double _pvar = 0; + for (int i = 0; i < _buffer.Count; i++) { _pvar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } + _pvar /= this._buffer.Count; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _pvar); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/WMAPE_Series.cs b/Calculations/_Updated/WMAPE_Series.cs index b0479cb8..bbbdeee4 100644 --- a/Calculations/_Updated/WMAPE_Series.cs +++ b/Calculations/_Updated/WMAPE_Series.cs @@ -1,82 +1,82 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -WMAPE: Weighted Mean Absolute Percentage Error - Measures the size of the error in percentage terms. Improves problems with MAPE - when there are zero or close-to-zero values because there would be a division by zero - or values of MAPE tending to infinity. - -Sources: - https://en.wikipedia.org/wiki/WMAPE - - */ - -public class WMAPE_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public WMAPE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"WMAPE({period})"; - } - public WMAPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public WMAPE_Series() : this(period: 0, useNaN: false) { } - public WMAPE_Series(int period) : this(period: period, useNaN: false) { } - public WMAPE_Series(TBars source) : this(source.Close, 0, false) { } - public WMAPE_Series(TBars source, int period) : this(source.Close, period, false) { } - public WMAPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public WMAPE_Series(TSeries source) : this(source, 0, false) { } - public WMAPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - - double _sma = _buffer.Average(); - - double _div = 0; - double _wmape = 0; - for (int i = 0; i < _buffer.Count; i++) { - _wmape += Math.Abs(_buffer[i] - _sma); - _div += Math.Abs(_buffer[i]); - } - _wmape = (_div != 0) ? _wmape / _div : double.PositiveInfinity; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _wmape); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +WMAPE: Weighted Mean Absolute Percentage Error + Measures the size of the error in percentage terms. Improves problems with MAPE + when there are zero or close-to-zero values because there would be a division by zero + or values of MAPE tending to infinity. + +Sources: + https://en.wikipedia.org/wiki/WMAPE + + */ + +public class WMAPE_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public WMAPE_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"WMAPE({period})"; + } + public WMAPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public WMAPE_Series() : this(period: 0, useNaN: false) { } + public WMAPE_Series(int period) : this(period: period, useNaN: false) { } + public WMAPE_Series(TBars source) : this(source.Close, 0, false) { } + public WMAPE_Series(TBars source, int period) : this(source.Close, period, false) { } + public WMAPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public WMAPE_Series(TSeries source) : this(source, 0, false) { } + public WMAPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + + double _sma = _buffer.Average(); + + double _div = 0; + double _wmape = 0; + for (int i = 0; i < _buffer.Count; i++) { + _wmape += Math.Abs(_buffer[i] - _sma); + _div += Math.Abs(_buffer[i]); + } + _wmape = (_div != 0) ? _wmape / _div : double.PositiveInfinity; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _wmape); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/WMA_Series.cs b/Calculations/_Updated/WMA_Series.cs index 48679fe4..f423e3ca 100644 --- a/Calculations/_Updated/WMA_Series.cs +++ b/Calculations/_Updated/WMA_Series.cs @@ -1,104 +1,104 @@ -namespace QuanTAlib; - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -/* -WMA: (linearly) Weighted Moving Average - The weights are linearly decreasing over the period and the most recent data has - the heaviest weight. - -Sources: - https://corporatefinanceinstitute.com/resources/knowledge/trading-investing/weighted-moving-average-wma/ - https://www.technicalindicators.net/indicators-technical-analysis/83-moving-averages-simple-exponential-weighted - - */ - -public class WMA_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - private System.Collections.Generic.List _weights; - protected int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - protected int _len; - public int Len { - get { return _len; } - set { _len = value; } - } - - //core constructors - public WMA_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"WMA({period})"; - _len = 1; - _weights = CalculateWeights(_period); - } - public WMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public WMA_Series() : this(period: 0, useNaN: false) { } - public WMA_Series(int period) : this(period: period, useNaN: false) { } - public WMA_Series(TBars source) : this(source.Close, 0, false) { } - public WMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public WMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public WMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update=false) { - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - if (_period == 0) { - _weights = CalculateWeights(_len); - _len++; - } - double _wma = 0; - double totalWeights = (_buffer.Count * (_buffer.Count + 1)) * 0.5; - object lockObj = new object(); - Parallel.For(0, _buffer.Count, i => - { - double temp = _buffer[i] * this._weights[i]; - lock (lockObj) { _wma += temp; } - }); - _wma /= totalWeights; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _wma); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //calculating weights - private static List CalculateWeights(int period) { - List weights = new List(period); - for (int i = 0; i < period; i++) { - weights.Add(i + 1); - } - return weights; - } - - //reset calculation - public override void Reset() { - _len = 0; - _weights = CalculateWeights(_period); - _buffer.Clear(); - } +namespace QuanTAlib; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +/* +WMA: (linearly) Weighted Moving Average + The weights are linearly decreasing over the period and the most recent data has + the heaviest weight. + +Sources: + https://corporatefinanceinstitute.com/resources/knowledge/trading-investing/weighted-moving-average-wma/ + https://www.technicalindicators.net/indicators-technical-analysis/83-moving-averages-simple-exponential-weighted + + */ + +public class WMA_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + private System.Collections.Generic.List _weights; + protected int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + protected int _len; + public int Len { + get { return _len; } + set { _len = value; } + } + + //core constructors + public WMA_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"WMA({period})"; + _len = 1; + _weights = CalculateWeights(_period); + } + public WMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public WMA_Series() : this(period: 0, useNaN: false) { } + public WMA_Series(int period) : this(period: period, useNaN: false) { } + public WMA_Series(TBars source) : this(source.Close, 0, false) { } + public WMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public WMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public WMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update=false) { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + if (_period == 0) { + _weights = CalculateWeights(_len); + _len++; + } + double _wma = 0; + double totalWeights = (_buffer.Count * (_buffer.Count + 1)) * 0.5; + object lockObj = new object(); + Parallel.For(0, _buffer.Count, i => + { + double temp = _buffer[i] * this._weights[i]; + lock (lockObj) { _wma += temp; } + }); + _wma /= totalWeights; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _wma); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //calculating weights + private static List CalculateWeights(int period) { + List weights = new List(period); + for (int i = 0; i < period; i++) { + weights.Add(i + 1); + } + return weights; + } + + //reset calculation + public override void Reset() { + _len = 0; + _weights = CalculateWeights(_period); + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/ZLEMA_Series.cs b/Calculations/_Updated/ZLEMA_Series.cs index 512b5cc0..0f7cd56c 100644 --- a/Calculations/_Updated/ZLEMA_Series.cs +++ b/Calculations/_Updated/ZLEMA_Series.cs @@ -1,95 +1,95 @@ -namespace QuanTAlib; - -using System; -using System.Linq; - -/* -ZLEMA: Zero Lag Exponential Moving Average - The Zero lag exponential moving average (ZLEMA) indicator was created by John - Ehlers and Ric Way. - -The formula for a given N-Day period and for a given Data series is: - Lag = (Period-1)/2 - Ema Data = {Data+(Data-Data(Lag days ago)) - ZLEMA = EMA (EmaData,Period) - -Remark: - The idea is do a regular exponential moving average (EMA) calculation but on a - de-lagged data instead of doing it on the regular data. Data is de-lagged by - removing the data from "lag" days ago thus removing (or attempting to remove) - the cumulative lag effect of the moving average. - - */ - -public class ZLEMA_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - private int _len; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly EMA_Series _ema; - - //core constructor - public ZLEMA_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - Name = $"ZLEMA({period})"; - _len = 1; - _ema = new(period); - } - //generic constructors (source) - - public ZLEMA_Series() : this(0, false, true) { } - public ZLEMA_Series(int period) : this(period, false, true) { } - public ZLEMA_Series(TBars source) : this(source.Close, 0, false) { } - public ZLEMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public ZLEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public ZLEMA_Series(TSeries source, int period) : this(source, period, false, true) { } - public ZLEMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) { } - public ZLEMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - int _lag; - if (_period == 0) { - _lag = (int)((_len - 1) * 0.5); - _len++; - } - else { _lag = (int)((_period - 1) * 0.5); } - _lag = Math.Min(_lag, _buffer.Count - 1); - _lag = Math.Max(_lag, 0) + 1; - double _zlValue = 2 * TValue.v - _buffer[^_lag]; - double _zlema = _ema.Add((TValue.t, _zlValue), update).v; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _zlema); - return base.Add(res, update); - } - - //variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - _ema.Reset(); - } +namespace QuanTAlib; + +using System; +using System.Linq; + +/* +ZLEMA: Zero Lag Exponential Moving Average + The Zero lag exponential moving average (ZLEMA) indicator was created by John + Ehlers and Ric Way. + +The formula for a given N-Day period and for a given Data series is: + Lag = (Period-1)/2 + Ema Data = {Data+(Data-Data(Lag days ago)) + ZLEMA = EMA (EmaData,Period) + +Remark: + The idea is do a regular exponential moving average (EMA) calculation but on a + de-lagged data instead of doing it on the regular data. Data is de-lagged by + removing the data from "lag" days ago thus removing (or attempting to remove) + the cumulative lag effect of the moving average. + + */ + +public class ZLEMA_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + private int _len; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly EMA_Series _ema; + + //core constructor + public ZLEMA_Series(int period, bool useNaN, bool useSMA) { + _period = period; + _NaN = useNaN; + Name = $"ZLEMA({period})"; + _len = 1; + _ema = new(period); + } + //generic constructors (source) + + public ZLEMA_Series() : this(0, false, true) { } + public ZLEMA_Series(int period) : this(period, false, true) { } + public ZLEMA_Series(TBars source) : this(source.Close, 0, false) { } + public ZLEMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public ZLEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public ZLEMA_Series(TSeries source, int period) : this(source, period, false, true) { } + public ZLEMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) { } + public ZLEMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + int _lag; + if (_period == 0) { + _lag = (int)((_len - 1) * 0.5); + _len++; + } + else { _lag = (int)((_period - 1) * 0.5); } + _lag = Math.Min(_lag, _buffer.Count - 1); + _lag = Math.Max(_lag, 0) + 1; + double _zlValue = 2 * TValue.v - _buffer[^_lag]; + double _zlema = _ema.Add((TValue.t, _zlValue), update).v; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _zlema); + return base.Add(res, update); + } + + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + _ema.Reset(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/ZL_Series.cs b/Calculations/_Updated/ZL_Series.cs index 0f6af1aa..033088e4 100644 --- a/Calculations/_Updated/ZL_Series.cs +++ b/Calculations/_Updated/ZL_Series.cs @@ -1,90 +1,90 @@ -namespace QuanTAlib; - -using System; -using System.Linq; - -/* -ZL: Zero Lag - Data is de-lagged by removing the data from “lag” days ago, thus removing - (or attempting to) the cumulative effect of the moving average. - -Calculation: - Lag = (Period-1)/2 - ZL = Data + (Data - Data(Lag days ago) ) - -Sources: - https://mudrex.com/blog/zero-lag-ema-trading-strategy/ - - */ - -public class ZL_Series: TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - private int _len; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly EMA_Series _ema; - - //core constructor - public ZL_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - Name = $"ZL({period})"; - _len = 1; - _ema = new(period); - } - //generic constructors (source) - - public ZL_Series() : this(0, false, true) { } - public ZL_Series(int period) : this(period, false, true) { } - public ZL_Series(TBars source) : this(source.Close, 0, false) { } - public ZL_Series(TBars source, int period) : this(source.Close, period, false) { } - public ZL_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public ZL_Series(TSeries source, int period) : this(source, period, false, true) { } - public ZL_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) { } - public ZL_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - int _lag; - if (_period == 0) { - _lag = (int)((_len - 1) * 0.5); - _len++; - } - else { _lag = (int)((_period - 1) * 0.5); } - _lag = Math.Min(_lag, _buffer.Count - 1); - _lag = Math.Max(_lag, 0) + 1; - double _zlValue = 2 * TValue.v - _buffer[^_lag]; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _zlValue); - return base.Add(res, update); - } - - //variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - _ema.Reset(); - } +namespace QuanTAlib; + +using System; +using System.Linq; + +/* +ZL: Zero Lag + Data is de-lagged by removing the data from “lag” days ago, thus removing + (or attempting to) the cumulative effect of the moving average. + +Calculation: + Lag = (Period-1)/2 + ZL = Data + (Data - Data(Lag days ago) ) + +Sources: + https://mudrex.com/blog/zero-lag-ema-trading-strategy/ + + */ + +public class ZL_Series: TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + private int _len; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly EMA_Series _ema; + + //core constructor + public ZL_Series(int period, bool useNaN, bool useSMA) { + _period = period; + _NaN = useNaN; + Name = $"ZL({period})"; + _len = 1; + _ema = new(period); + } + //generic constructors (source) + + public ZL_Series() : this(0, false, true) { } + public ZL_Series(int period) : this(period, false, true) { } + public ZL_Series(TBars source) : this(source.Close, 0, false) { } + public ZL_Series(TBars source, int period) : this(source.Close, period, false) { } + public ZL_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public ZL_Series(TSeries source, int period) : this(source, period, false, true) { } + public ZL_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) { } + public ZL_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + int _lag; + if (_period == 0) { + _lag = (int)((_len - 1) * 0.5); + _len++; + } + else { _lag = (int)((_period - 1) * 0.5); } + _lag = Math.Min(_lag, _buffer.Count - 1); + _lag = Math.Max(_lag, 0) + 1; + double _zlValue = 2 * TValue.v - _buffer[^_lag]; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _zlValue); + return base.Add(res, update); + } + + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + _ema.Reset(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/ZSCORE_Series.cs b/Calculations/_Updated/ZSCORE_Series.cs index c3ab4288..06c37130 100644 --- a/Calculations/_Updated/ZSCORE_Series.cs +++ b/Calculations/_Updated/ZSCORE_Series.cs @@ -1,88 +1,88 @@ -using System.Linq; - -namespace QuanTAlib; -using System; -using System.Collections.Generic; - -/* -ZSCORE: number of standard deviations from SMA - Z-score describes a value's relationship to the mean of a series, as measured in - terms of standard deviations from the mean. If a Z-score is 0, it indicates that - the data point's score is identical to the mean score. A Z-score of 1.0 would - indicate a value that is one standard deviation from the mean. Z-scores may be - positive or negative, with a positive value indicating the score is above the - mean and a negative score indicating it is below the mean. - -Sources: - https://en.wikipedia.org/wiki/Z-score - https://www.investopedia.com/terms/z/zscore.asp - -Calculation: - std = std * STDEV(close, length) - mean = SMA(close, length) - ZSCORE = (close - mean) / std - - */ - -public class ZSCORE_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - - //core constructors - public ZSCORE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"ZSCORE({period})"; - } - public ZSCORE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public ZSCORE_Series() : this(period: 0, useNaN: false) { } - public ZSCORE_Series(int period) : this(period: period, useNaN: false) { } - public ZSCORE_Series(TBars source) : this(source.Close, 0, false) { } - public ZSCORE_Series(TBars source, int period) : this(source.Close, period, false) { } - public ZSCORE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public ZSCORE_Series(TSeries source) : this(source, 0, false) { } - public ZSCORE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - double _sma = _buffer.Average(); - - double _pvar = 0; - for (int i = 0; i < _buffer.Count; i++) { _pvar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } - _pvar /= this._buffer.Count; - double _psdev = Math.Sqrt(_pvar); - double _zscore = (_psdev == 0) ? 1 : (TValue.v - _sma) / _psdev; - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _zscore); - return base.Add(res, update); - } - - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _buffer.Clear(); - } +using System.Linq; + +namespace QuanTAlib; +using System; +using System.Collections.Generic; + +/* +ZSCORE: number of standard deviations from SMA + Z-score describes a value's relationship to the mean of a series, as measured in + terms of standard deviations from the mean. If a Z-score is 0, it indicates that + the data point's score is identical to the mean score. A Z-score of 1.0 would + indicate a value that is one standard deviation from the mean. Z-scores may be + positive or negative, with a positive value indicating the score is above the + mean and a negative score indicating it is below the mean. + +Sources: + https://en.wikipedia.org/wiki/Z-score + https://www.investopedia.com/terms/z/zscore.asp + +Calculation: + std = std * STDEV(close, length) + mean = SMA(close, length) + ZSCORE = (close - mean) / std + + */ + +public class ZSCORE_Series : TSeries { + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + + //core constructors + public ZSCORE_Series(int period, bool useNaN) { + _period = period; + _NaN = useNaN; + Name = $"ZSCORE({period})"; + } + public ZSCORE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public ZSCORE_Series() : this(period: 0, useNaN: false) { } + public ZSCORE_Series(int period) : this(period: period, useNaN: false) { } + public ZSCORE_Series(TBars source) : this(source.Close, 0, false) { } + public ZSCORE_Series(TBars source, int period) : this(source.Close, period, false) { } + public ZSCORE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public ZSCORE_Series(TSeries source) : this(source, 0, false) { } + public ZSCORE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + double _sma = _buffer.Average(); + + double _pvar = 0; + for (int i = 0; i < _buffer.Count; i++) { _pvar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } + _pvar /= this._buffer.Count; + double _psdev = Math.Sqrt(_pvar); + double _zscore = (_psdev == 0) ? 1 : (TValue.v - _sma) / _psdev; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _zscore); + return base.Add(res, update); + } + + public override (DateTime t, double v) Add(TSeries data) { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Indicators/Charts/2MACross_chart.cs b/Indicators/Charts/2MACross_chart.cs index 2bc81b8e..58a8063d 100644 --- a/Indicators/Charts/2MACross_chart.cs +++ b/Indicators/Charts/2MACross_chart.cs @@ -1,278 +1,284 @@ -using System; -using System.Drawing; -using System.Linq; -using TradingPlatform.BusinessLayer; -namespace QuanTAlib; - -public class MovingAverage_chart : Indicator { - #region Parameters - [InputParameter("MA1: Type:", 0, variants: new object[] - { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, - "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] - private int MA1type = 15; - - [InputParameter("MA1: Smoothing period:", 1, 1, 999, 1, 1)] - private int MA1Period = 10; - - [InputParameter("MA1: Data source:", 2, variants: new object[] - { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, - "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] - private int MA1DataSource = 3; - - [InputParameter("MA2: Type:", 3, variants: new object[] - { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, - "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] - private int MA2type = 16; - - [InputParameter("MA2: Smoothing period:", 4, 1, 999, 1, 1)] - private int MA2Period = 50; - - [InputParameter("MA2: Data source:", 5, variants: new object[] - { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, - "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] - private int MA2DataSource = 8; - - [InputParameter("Long trades", 6)] - private bool LongTrades = true; - - [InputParameter("Short trades", 6)] - private bool ShortTrades = true; - - #endregion Parameters - - protected HistoricalData History; - private TBars bars; - - /////// - private TSeries MA1, MA2; - private CROSS_Series trades; - private COMPARE_Series overunder; - - /////// - - public MovingAverage_chart() { - this.SeparateWindow = false; - this.Name = "MAs Crossover"; - this.AddLineSeries("MA1", Color.LimeGreen, 2, LineStyle.Solid); - this.AddLineSeries("MA2", Color.OrangeRed, 2, LineStyle.Solid); - } - - protected override void OnInit() { - this.bars = new(); - this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); - for (int i = this.History.Count - 1; i >= 0; i--) { - var rec = this.History[i, SeekOriginHistory.Begin]; - bars.Add(rec.TimeLeft, rec[PriceType.Open], - rec[PriceType.High], rec[PriceType.Low], - rec[PriceType.Close], rec[PriceType.Volume]); - } - this.Name = "MAs Cross: [ "; - switch (MA1type) { - case 0: - MA1 = new SMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"SMA"; - break; - case 1: - MA1 = new EMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"EMA"; - break; - case 2: - MA1 = new WMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"WMA"; - break; - case 3: - MA1 = new T3_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"T3"; - break; - case 4: - MA1 = new SMMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"SMMA"; - break; - case 5: - MA1 = new TRIMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"TRIMA"; - break; - case 6: - MA1 = new DWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"DWMA"; - break; - case 7: - MA1 = new FWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period); - this.Name += $"FWMA"; - break; - case 8: - MA1 = new DEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"DEMA"; - break; - case 9: - MA1 = new TEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"TEMA"; - break; - case 10: - MA1 = new ALMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"ALMA"; - break; - case 11: - MA1 = new HMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"HMA"; - break; - case 12: - MA1 = new HEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"HEMA"; - break; - case 13: - double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA1Period); - MA1 = new MAMA_Series(source: bars.Select(this.MA1DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); - this.Name += $"MAMA"; - break; - case 14: - MA1 = new KAMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"KAMA"; - break; - case 15: - MA1 = new ZLEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"ZLEMA"; - break; - default: - MA1 = new JMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"JMA"; - break; - } - - this.Name = this.Name + $" ({MA1Period}:{TBars.SelectStr(this.MA1DataSource)}) : "; - - switch (MA2type) { - case 0: - MA2 = new SMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"SMA"; - break; - case 1: - MA2 = new EMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"EMA"; - break; - case 2: - MA2 = new WMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"WMA"; - break; - case 3: - MA2 = new T3_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"T3"; - break; - case 4: - MA2 = new SMMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"SMMA"; - break; - case 5: - MA2 = new TRIMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"TRIMA"; - break; - case 6: - MA2 = new DWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"DWMA"; - break; - case 7: - MA2 = new FWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period); - this.Name += $"FWMA"; - break; - case 8: - MA2 = new DEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"DEMA"; - break; - case 9: - MA2 = new TEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"TEMA"; - break; - case 10: - MA2 = new ALMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"ALMA"; - break; - case 11: - MA2 = new HMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"HMA"; - break; - case 12: - MA2 = new HEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"HEMA"; - break; - case 13: - double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA2Period); - MA2 = new MAMA_Series(source: bars.Select(this.MA2DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); - this.Name += $"MAMA"; - break; - case 14: - MA2 = new KAMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"KAMA"; - break; - case 15: - MA2 = new ZLEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"ZLEMA"; - break; - default: - MA2 = new JMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"JMA"; - break; - } - this.Name += $"({MA2Period}:{TBars.SelectStr(this.MA2DataSource)}) ]"; - - overunder = new(MA1, MA2); - trades = new(MA1, MA2); - } - - protected override void OnUpdate(UpdateArgs args) { - bool update = !(args.Reason == UpdateReason.NewBar || - args.Reason == UpdateReason.HistoricalBar); - this.bars.Add(this.Time(), this.GetPrice(PriceType.Open), - this.GetPrice(PriceType.High), - this.GetPrice(PriceType.Low), - this.GetPrice(PriceType.Close), - this.GetPrice(PriceType.Volume), update); - this.SetValue(this.MA1[^1].v, lineIndex: 0); - this.SetValue(this.MA2[^1].v, lineIndex: 1); - - if (trades[^1].v == 1) { - this.EndCloud(0, 1, Color.Empty); - if (LongTrades) { - this.LinesSeries[0].SetMarker(0, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.UpArrow)); - this.BeginCloud(0, 1, Color.FromArgb(127, Color.Green)); - } - if (ShortTrades) { - this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.DownArrow)); - } - } - if (trades[^1].v == -1) { - this.EndCloud(0, 1, Color.Empty); - if (ShortTrades) { - this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.UpArrow)); - this.BeginCloud(0, 1, Color.FromArgb(127, Color.Red)); - } - if (LongTrades) { - this.LinesSeries[0].SetMarker(0, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.DownArrow)); - } - } - } - public override void OnPaintChart(PaintChartEventArgs args) { - base.OnPaintChart(args); - if (this.CurrentChart == null) {return;} - Graphics graphics = args.Graphics; - var mainWindow = this.CurrentChart.MainWindow; - int leftIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left)); - int rightIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right))); - int historycount = HistoricalData.Count; - int ymax = mainWindow.ClientRectangle.Height; - int xmax = mainWindow.ClientRectangle.Width; - - /* - for (int i = leftIndex; i <= rightIndex; i++) { - int xi = (int)Math.Round(mainWindow.CoordinatesConverter.GetChartX(Time(Count - 1 - i))); - int width = this.CurrentChart.BarsWidth; - int height = (int)((equity[i+historycount].v) *proportion); - - Brush bb = Brushes.DarkSlateGray; - bb = (overunder[i+historycount].v>0 && LongTrades)? Brushes.Green : bb; - bb = (overunder[i + historycount].v < 0 && ShortTrades) ? Brushes.Red : bb; - - graphics.FillRectangle(bb, xi, ymax - height, width, height); - } - */ - } -} +using System; +using System.Drawing; +using System.Linq; +using TradingPlatform.BusinessLayer; +namespace QuanTAlib; + +public class MovingAverage_chart : Indicator { + #region Parameters + [InputParameter("MA1: Type:", 0, variants: new object[] + { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, + "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] + private int MA1type = 15; + + [InputParameter("MA1: Smoothing period:", 1, 1, 999, 1, 1)] + private int MA1Period = 10; + + [InputParameter("MA1: Data source:", 2, variants: new object[] + { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, + "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] + private int MA1DataSource = 3; + + [InputParameter("MA2: Type:", 3, variants: new object[] + { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, + "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] + private int MA2type = 16; + + [InputParameter("MA2: Smoothing period:", 4, 1, 999, 1, 1)] + private int MA2Period = 50; + + [InputParameter("MA2: Data source:", 5, variants: new object[] + { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, + "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] + private int MA2DataSource = 8; + + [InputParameter("Long trades", 6)] + private bool LongTrades = true; + + [InputParameter("Short trades", 6)] + private bool ShortTrades = true; + + #endregion Parameters + + protected HistoricalData History; + private TBars bars; + + /////// + private TSeries MA1, MA2; + private CROSS_Series trades; + private COMPARE_Series overunder; + + /////// + + public MovingAverage_chart() { + this.SeparateWindow = false; + this.Name = "MAs Crossover"; + this.AddLineSeries("MA1", Color.LimeGreen, 2, LineStyle.Solid); + this.AddLineSeries("MA2", Color.OrangeRed, 2, LineStyle.Solid); + } + + protected override void OnInit() { + this.bars = new(); + this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); + for (int i = this.History.Count - 1; i >= 0; i--) { + var rec = this.History[i, SeekOriginHistory.Begin]; + bars.Add(rec.TimeLeft, rec[PriceType.Open], + rec[PriceType.High], rec[PriceType.Low], + rec[PriceType.Close], rec[PriceType.Volume]); + } + this.Name = "MAs Cross: [ "; + switch (MA1type) { + case 0: + MA1 = new SMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"SMA"; + break; + case 1: + MA1 = new EMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"EMA"; + break; + case 2: + MA1 = new WMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"WMA"; + break; + case 3: + MA1 = new T3_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"T3"; + break; + case 4: + MA1 = new SMMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"SMMA"; + break; + case 5: + MA1 = new TRIMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"TRIMA"; + break; + case 6: + MA1 = new DWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"DWMA"; + break; + case 7: + MA1 = new FWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period); + this.Name += $"FWMA"; + break; + case 8: + MA1 = new DEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"DEMA"; + break; + case 9: + MA1 = new TEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"TEMA"; + break; + case 10: + MA1 = new ALMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"ALMA"; + break; + case 11: + MA1 = new HMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"HMA"; + break; + case 12: + MA1 = new HEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"HEMA"; + break; + case 13: + double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA1Period); + MA1 = new MAMA_Series(source: bars.Select(this.MA1DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); + this.Name += $"MAMA"; + break; + case 14: + MA1 = new KAMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"KAMA"; + break; + case 15: + MA1 = new ZLEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"ZLEMA"; + break; + default: + MA1 = new JMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"JMA"; + break; + } + + this.Name = this.Name + $" ({MA1Period}:{TBars.SelectStr(this.MA1DataSource)}) : "; + + switch (MA2type) { + case 0: + MA2 = new SMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"SMA"; + break; + case 1: + MA2 = new EMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"EMA"; + break; + case 2: + MA2 = new WMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"WMA"; + break; + case 3: + MA2 = new T3_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"T3"; + break; + case 4: + MA2 = new SMMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"SMMA"; + break; + case 5: + MA2 = new TRIMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"TRIMA"; + break; + case 6: + MA2 = new DWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"DWMA"; + break; + case 7: + MA2 = new FWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period); + this.Name += $"FWMA"; + break; + case 8: + MA2 = new DEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"DEMA"; + break; + case 9: + MA2 = new TEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"TEMA"; + break; + case 10: + MA2 = new ALMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"ALMA"; + break; + case 11: + MA2 = new HMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"HMA"; + break; + case 12: + MA2 = new HEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"HEMA"; + break; + case 13: + double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA2Period); + MA2 = new MAMA_Series(source: bars.Select(this.MA2DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); + this.Name += $"MAMA"; + break; + case 14: + MA2 = new KAMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"KAMA"; + break; + case 15: + MA2 = new ZLEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"ZLEMA"; + break; + default: + MA2 = new JMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"JMA"; + break; + } + this.Name += $"({MA2Period}:{TBars.SelectStr(this.MA2DataSource)}) ]"; + + int maxKeep = Math.Max(Math.Max(this.MA1Period, this.MA2Period), 100); + MA1.Keep = maxKeep; + MA2.Keep = maxKeep; + trades.Keep = maxKeep; + overunder.Keep = maxKeep; + + overunder = new(MA1, MA2); + trades = new(MA1, MA2); + } + + protected override void OnUpdate(UpdateArgs args) { + bool update = !(args.Reason == UpdateReason.NewBar || + args.Reason == UpdateReason.HistoricalBar); + this.bars.Add(this.Time(), this.GetPrice(PriceType.Open), + this.GetPrice(PriceType.High), + this.GetPrice(PriceType.Low), + this.GetPrice(PriceType.Close), + this.GetPrice(PriceType.Volume), update); + this.SetValue(this.MA1[^1].v, lineIndex: 0); + this.SetValue(this.MA2[^1].v, lineIndex: 1); + + if (trades[^1].v == 1) { + this.EndCloud(0, 1, Color.Empty); + if (LongTrades) { + this.LinesSeries[0].SetMarker(0, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.UpArrow)); + this.BeginCloud(0, 1, Color.FromArgb(127, Color.Green)); + } + if (ShortTrades) { + this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.DownArrow)); + } + } + if (trades[^1].v == -1) { + this.EndCloud(0, 1, Color.Empty); + if (ShortTrades) { + this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.UpArrow)); + this.BeginCloud(0, 1, Color.FromArgb(127, Color.Red)); + } + if (LongTrades) { + this.LinesSeries[0].SetMarker(0, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.DownArrow)); + } + } + } + public override void OnPaintChart(PaintChartEventArgs args) { + base.OnPaintChart(args); + if (this.CurrentChart == null) {return;} + Graphics graphics = args.Graphics; + var mainWindow = this.CurrentChart.MainWindow; + int leftIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left)); + int rightIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right))); + int historycount = HistoricalData.Count; + int ymax = mainWindow.ClientRectangle.Height; + int xmax = mainWindow.ClientRectangle.Width; + + /* + for (int i = leftIndex; i <= rightIndex; i++) { + int xi = (int)Math.Round(mainWindow.CoordinatesConverter.GetChartX(Time(Count - 1 - i))); + int width = this.CurrentChart.BarsWidth; + int height = (int)((equity[i+historycount].v) *proportion); + + Brush bb = Brushes.DarkSlateGray; + bb = (overunder[i+historycount].v>0 && LongTrades)? Brushes.Green : bb; + bb = (overunder[i + historycount].v < 0 && ShortTrades) ? Brushes.Red : bb; + + graphics.FillRectangle(bb, xi, ymax - height, width, height); + } + */ + } +} diff --git a/Indicators/Charts/2MASlope_chart.cs b/Indicators/Charts/2MASlope_chart.cs index 547dc860..65acfa9c 100644 --- a/Indicators/Charts/2MASlope_chart.cs +++ b/Indicators/Charts/2MASlope_chart.cs @@ -1,298 +1,307 @@ -using System; -using System.Drawing; -using System.Linq; -using TradingPlatform.BusinessLayer; -namespace QuanTAlib; - -public class MovingAverageSlope_chart : Indicator { - #region Parameters - [InputParameter("MA1: Type:", 0, variants: new object[] - { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, - "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] - private int MA1type = 16; - - [InputParameter("MA1: Smoothing period:", 1, 1, 999, 1, 1)] - private int MA1Period = 10; - - [InputParameter("MA1: Data source:", 2, variants: new object[] - { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, - "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] - private int MA1DataSource = 3; - - [InputParameter("MA2: Type:", 3, variants: new object[] - { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, - "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] - private int MA2type = 6; - - [InputParameter("MA2: Smoothing period:", 4, 1, 999, 1, 1)] - private int MA2Period = 50; - - [InputParameter("MA2: Data source:", 5, variants: new object[] - { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, - "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] - private int MA2DataSource = 8; - - [InputParameter("Data required for slope calc:", 6, 2, 10, 1, 1)] - private int SlopePeriod = 3; - - [InputParameter("Long trades", 7)] - private bool LongTrades = true; - - [InputParameter("Short trades", 8)] - private bool ShortTrades; - - #endregion Parameters - - protected HistoricalData History; - private TBars bars; - - /////// - private TSeries MA1, MA2; - private SLOPE_Series sMA1, sMA2; - private CROSS_Series sig1, sig2; - - private bool inLong, inShort; - /////// - - public MovingAverageSlope_chart() { - this.SeparateWindow = false; - this.Name = "Slopes convergence"; - this.AddLineSeries("MA1", Color.DarkSlateGray, 2, LineStyle.Solid); - this.AddLineSeries("MA2", Color.DarkSlateGray, 2, LineStyle.Solid); - } - - protected override void OnInit() { - this.bars = new(); - this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); - for (int i = this.History.Count - 1; i >= 0; i--) { - var rec = this.History[i, SeekOriginHistory.Begin]; - bars.Add(rec.TimeLeft, rec[PriceType.Open], - rec[PriceType.High], rec[PriceType.Low], - rec[PriceType.Close], rec[PriceType.Volume]); - } - this.Name = "Slopes convergence: [ "; - switch (MA1type) { - case 0: - MA1 = new SMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"SMA"; - break; - case 1: - MA1 = new EMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"EMA"; - break; - case 2: - MA1 = new WMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"WMA"; - break; - case 3: - MA1 = new T3_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"T3"; - break; - case 4: - MA1 = new SMMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"SMMA"; - break; - case 5: - MA1 = new TRIMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"TRIMA"; - break; - case 6: - MA1 = new DWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"DWMA"; - break; - case 7: - MA1 = new FWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period); - this.Name += $"FWMA"; - break; - case 8: - MA1 = new DEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"DEMA"; - break; - case 9: - MA1 = new TEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"TEMA"; - break; - case 10: - MA1 = new ALMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"ALMA"; - break; - case 11: - MA1 = new HMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"HMA"; - break; - case 12: - MA1 = new HEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"HEMA"; - break; - case 13: - double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA1Period); - MA1 = new MAMA_Series(source: bars.Select(this.MA1DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); - this.Name += $"MAMA"; - break; - case 14: - MA1 = new KAMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"KAMA"; - break; - case 15: - MA1 = new ZLEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"ZLEMA"; - break; - default: - MA1 = new JMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"JMA"; - break; - } - - this.Name = this.Name + $" ({MA1Period}:{TBars.SelectStr(this.MA1DataSource)}) : "; - - switch (MA2type) { - case 0: - MA2 = new SMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"SMA"; - break; - case 1: - MA2 = new EMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"EMA"; - break; - case 2: - MA2 = new WMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"WMA"; - break; - case 3: - MA2 = new T3_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"T3"; - break; - case 4: - MA2 = new SMMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"SMMA"; - break; - case 5: - MA2 = new TRIMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"TRIMA"; - break; - case 6: - MA2 = new DWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"DWMA"; - break; - case 7: - MA2 = new FWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period); - this.Name += $"FWMA"; - break; - case 8: - MA2 = new DEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"DEMA"; - break; - case 9: - MA2 = new TEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"TEMA"; - break; - case 10: - MA2 = new ALMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"ALMA"; - break; - case 11: - MA2 = new HMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"HMA"; - break; - case 12: - MA2 = new HEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"HEMA"; - break; - case 13: - double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA2Period); - MA2 = new MAMA_Series(source: bars.Select(this.MA2DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); - this.Name += $"MAMA"; - break; - case 14: - MA2 = new KAMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"KAMA"; - break; - case 15: - MA2 = new ZLEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"ZLEMA"; - break; - default: - MA2 = new JMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"JMA"; - break; - } - this.Name += $"({MA2Period}:{TBars.SelectStr(this.MA2DataSource)}) ]"; - - sMA1 = new(MA1, SlopePeriod); - sMA2 = new(MA2, SlopePeriod); - sig1 = new(sMA1, 0); - sig2 = new(sMA2, 0); - } - - protected override void OnUpdate(UpdateArgs args) { - bool update = !(args.Reason == UpdateReason.NewBar || - args.Reason == UpdateReason.HistoricalBar); - this.bars.Add(this.Time(),this.Open(), this.High(), this.Low(), this.Close(), this.Volume(), update); - this.SetValue(this.MA1[^1].v, lineIndex: 0); - this.SetValue(this.MA2[^1].v, lineIndex: 1); - - Color s1Color= (this.sMA1[^1].v > 0)?Color.LimeGreen:Color.OrangeRed; - Color s2Color = (this.sMA2[^1].v > 0) ? Color.LimeGreen : Color.OrangeRed; - - this.LinesSeries[0].SetMarker(0,s1Color); - this.LinesSeries[1].SetMarker(0,s2Color); - - if (sig1[^1].v > 0 || sig2[^1].v > 0) { - if (sMA1[^1].v >= 0 && sMA2[^1].v >= 0 && LongTrades) - { - inLong = true; - this.BeginCloud(0, 1, Color.FromArgb(127, Color.DarkGreen)); - this.LinesSeries[(this.MA1[^1].v < this.MA2[^1].v)? 0 : 1 ].SetMarker(0, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.UpArrow)); - } - else { - this.EndCloud(0, 1, Color.Empty); - if (inShort) - { - this.LinesSeries[(this.MA1[^1].v < this.MA2[^1].v) ? 1 : 0].SetMarker(1, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.DownArrow)); - inShort = false; - } - } - } - - if (sig1[^1].v < 0 || sig2[^1].v < 0) { - if (sMA1[^1].v <= 0 && sMA2[^1].v <= 0 && ShortTrades) - { - inShort = true; - this.BeginCloud(0, 1, Color.FromArgb(100, Color.Red)); - this.LinesSeries[(this.MA1[^1].v > this.MA2[^1].v) ? 0 : 1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.UpArrow)); - } - else { - this.EndCloud(0, 1, Color.Empty); - if (inLong) { - LinesSeries[(this.MA1[^1].v > this.MA2[^1].v)?1:0].SetMarker(1, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.DownArrow)); - inLong = false; - } - } - } - } - public override void OnPaintChart(PaintChartEventArgs args) { - base.OnPaintChart(args); - if (this.CurrentChart == null) {return;} - Graphics graphics = args.Graphics; - var mainWindow = this.CurrentChart.MainWindow; - int leftIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left)); - int rightIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right))); -/* - int historycount = HistoricalData.Count; - int ymax = mainWindow.ClientRectangle.Height; - - - for (int i = leftIndex; i <= rightIndex; i++) { - int xi = (int)Math.Round(mainWindow.CoordinatesConverter.GetChartX(Time(Count - 1 - i))); - int width = this.CurrentChart.BarsWidth; - int height = (int)((equity[i+historycount].v) *proportion); - - Brush bb = Brushes.DarkSlateGray; - bb = (overunder[i+historycount].v>0 && LongTrades)? Brushes.Green : bb; - bb = (overunder[i + historycount].v < 0 && ShortTrades) ? Brushes.Red : bb; - - graphics.FillRectangle(bb, xi, ymax - height, width, height); - } -*/ - } -} +using System; +using System.Drawing; +using System.Linq; +using TradingPlatform.BusinessLayer; +namespace QuanTAlib; + +public class MovingAverageSlope_chart : Indicator { + #region Parameters + [InputParameter("MA1: Type:", 0, variants: new object[] + { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, + "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] + private int MA1type = 16; + + [InputParameter("MA1: Smoothing period:", 1, 1, 999, 1, 1)] + private int MA1Period = 10; + + [InputParameter("MA1: Data source:", 2, variants: new object[] + { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, + "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] + private int MA1DataSource = 3; + + [InputParameter("MA2: Type:", 3, variants: new object[] + { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, + "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] + private int MA2type = 6; + + [InputParameter("MA2: Smoothing period:", 4, 1, 999, 1, 1)] + private int MA2Period = 50; + + [InputParameter("MA2: Data source:", 5, variants: new object[] + { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, + "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] + private int MA2DataSource = 8; + + [InputParameter("Data required for slope calc:", 6, 2, 10, 1, 1)] + private int SlopePeriod = 3; + + [InputParameter("Long trades", 7)] + private bool LongTrades = true; + + [InputParameter("Short trades", 8)] + private bool ShortTrades; + + #endregion Parameters + + protected HistoricalData History; + private TBars bars; + + /////// + private TSeries MA1, MA2; + private SLOPE_Series sMA1, sMA2; + private CROSS_Series sig1, sig2; + + private bool inLong, inShort; + /////// + + public MovingAverageSlope_chart() { + this.SeparateWindow = false; + this.Name = "Slopes convergence"; + this.AddLineSeries("MA1", Color.DarkSlateGray, 2, LineStyle.Solid); + this.AddLineSeries("MA2", Color.DarkSlateGray, 2, LineStyle.Solid); + } + + protected override void OnInit() { + this.bars = new(); + this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); + for (int i = this.History.Count - 1; i >= 0; i--) { + var rec = this.History[i, SeekOriginHistory.Begin]; + bars.Add(rec.TimeLeft, rec[PriceType.Open], + rec[PriceType.High], rec[PriceType.Low], + rec[PriceType.Close], rec[PriceType.Volume]); + } + this.Name = "Slopes convergence: [ "; + switch (MA1type) { + case 0: + MA1 = new SMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"SMA"; + break; + case 1: + MA1 = new EMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"EMA"; + break; + case 2: + MA1 = new WMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"WMA"; + break; + case 3: + MA1 = new T3_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"T3"; + break; + case 4: + MA1 = new SMMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"SMMA"; + break; + case 5: + MA1 = new TRIMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"TRIMA"; + break; + case 6: + MA1 = new DWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"DWMA"; + break; + case 7: + MA1 = new FWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period); + this.Name += $"FWMA"; + break; + case 8: + MA1 = new DEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"DEMA"; + break; + case 9: + MA1 = new TEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"TEMA"; + break; + case 10: + MA1 = new ALMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"ALMA"; + break; + case 11: + MA1 = new HMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"HMA"; + break; + case 12: + MA1 = new HEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"HEMA"; + break; + case 13: + double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA1Period); + MA1 = new MAMA_Series(source: bars.Select(this.MA1DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); + this.Name += $"MAMA"; + break; + case 14: + MA1 = new KAMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"KAMA"; + break; + case 15: + MA1 = new ZLEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"ZLEMA"; + break; + default: + MA1 = new JMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"JMA"; + break; + } + + this.Name = this.Name + $" ({MA1Period}:{TBars.SelectStr(this.MA1DataSource)}) : "; + + switch (MA2type) { + case 0: + MA2 = new SMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"SMA"; + break; + case 1: + MA2 = new EMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"EMA"; + break; + case 2: + MA2 = new WMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"WMA"; + break; + case 3: + MA2 = new T3_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"T3"; + break; + case 4: + MA2 = new SMMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"SMMA"; + break; + case 5: + MA2 = new TRIMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"TRIMA"; + break; + case 6: + MA2 = new DWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"DWMA"; + break; + case 7: + MA2 = new FWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period); + this.Name += $"FWMA"; + break; + case 8: + MA2 = new DEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"DEMA"; + break; + case 9: + MA2 = new TEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"TEMA"; + break; + case 10: + MA2 = new ALMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"ALMA"; + break; + case 11: + MA2 = new HMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"HMA"; + break; + case 12: + MA2 = new HEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"HEMA"; + break; + case 13: + double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA2Period); + MA2 = new MAMA_Series(source: bars.Select(this.MA2DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); + this.Name += $"MAMA"; + break; + case 14: + MA2 = new KAMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"KAMA"; + break; + case 15: + MA2 = new ZLEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"ZLEMA"; + break; + default: + MA2 = new JMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"JMA"; + break; + } + this.Name += $"({MA2Period}:{TBars.SelectStr(this.MA2DataSource)}) ]"; + + sMA1 = new(MA1, SlopePeriod); + sMA2 = new(MA2, SlopePeriod); + sig1 = new(sMA1, 0); + sig2 = new(sMA2, 0); + + int maxKeep = Math.Max(Math.Max(this.MA1Period, this.MA2Period), 100); + + MA1.Keep = maxKeep; + MA2.Keep = maxKeep; + sMA1.Keep = maxKeep; + sMA2.Keep = maxKeep; + sig1.Keep = maxKeep; + sig2.Keep = maxKeep; + } + + protected override void OnUpdate(UpdateArgs args) { + bool update = !(args.Reason == UpdateReason.NewBar || + args.Reason == UpdateReason.HistoricalBar); + this.bars.Add(this.Time(),this.Open(), this.High(), this.Low(), this.Close(), this.Volume(), update); + this.SetValue(this.MA1[^1].v, lineIndex: 0); + this.SetValue(this.MA2[^1].v, lineIndex: 1); + + Color s1Color= (this.sMA1[^1].v > 0)?Color.LimeGreen:Color.OrangeRed; + Color s2Color = (this.sMA2[^1].v > 0) ? Color.LimeGreen : Color.OrangeRed; + + this.LinesSeries[0].SetMarker(0,s1Color); + this.LinesSeries[1].SetMarker(0,s2Color); + + if (sig1[^1].v > 0 || sig2[^1].v > 0) { + if (sMA1[^1].v >= 0 && sMA2[^1].v >= 0 && LongTrades) + { + inLong = true; + this.BeginCloud(0, 1, Color.FromArgb(127, Color.DarkGreen)); + this.LinesSeries[(this.MA1[^1].v < this.MA2[^1].v)? 0 : 1 ].SetMarker(0, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.UpArrow)); + } + else { + this.EndCloud(0, 1, Color.Empty); + if (inShort && this.Count > 1) + { + this.LinesSeries[(this.MA1[^1].v < this.MA2[^1].v) ? 1 : 0].SetMarker(1, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.DownArrow)); + inShort = false; + } + } + } + + if (sig1[^1].v < 0 || sig2[^1].v < 0) { + if (sMA1[^1].v <= 0 && sMA2[^1].v <= 0 && ShortTrades) + { + inShort = true; + this.BeginCloud(0, 1, Color.FromArgb(100, Color.Red)); + this.LinesSeries[(this.MA1[^1].v > this.MA2[^1].v) ? 0 : 1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.UpArrow)); + } + else { + this.EndCloud(0, 1, Color.Empty); + if (inLong && this.Count > 1) { + LinesSeries[(this.MA1[^1].v > this.MA2[^1].v)?1:0].SetMarker(1, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.DownArrow)); + inLong = false; + } + } + } + } + public override void OnPaintChart(PaintChartEventArgs args) { + base.OnPaintChart(args); + if (this.CurrentChart == null) {return;} + Graphics graphics = args.Graphics; + var mainWindow = this.CurrentChart.MainWindow; + int leftIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left)); + int rightIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right))); +/* + int historycount = HistoricalData.Count; + int ymax = mainWindow.ClientRectangle.Height; + + + for (int i = leftIndex; i <= rightIndex; i++) { + int xi = (int)Math.Round(mainWindow.CoordinatesConverter.GetChartX(Time(Count - 1 - i))); + int width = this.CurrentChart.BarsWidth; + int height = (int)((equity[i+historycount].v) *proportion); + + Brush bb = Brushes.DarkSlateGray; + bb = (overunder[i+historycount].v>0 && LongTrades)? Brushes.Green : bb; + bb = (overunder[i + historycount].v < 0 && ShortTrades) ? Brushes.Red : bb; + + graphics.FillRectangle(bb, xi, ymax - height, width, height); + } +*/ + } +} diff --git a/Indicators/Charts/JMA_chart.cs b/Indicators/Charts/JMA_chart.cs index 7d5e5341..449f6965 100644 --- a/Indicators/Charts/JMA_chart.cs +++ b/Indicators/Charts/JMA_chart.cs @@ -1,96 +1,97 @@ -using System; -using System.Diagnostics; -using System.Drawing; -using System.Linq; -using TradingPlatform.BusinessLayer; -using TradingPlatform.BusinessLayer.Chart; -namespace QuanTAlib; - -public class JMA_chart : Indicator { - #region Parameters - - [InputParameter("Data source", 0, variants: new object[] - { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, - "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] - private int DataSource = 3; - - [InputParameter("Smoothing period", 1, 1, 999, 1, 1)] - private int Period = 9; - - [InputParameter("Volatility short", 2, 3, 50, 1, 1)] - private int Vshort = 10; - - [InputParameter("Volatility long", 3, 20, 500, 1, 1)] - private int Vlong = 65; - - [InputParameter("Phase", 4, -100, 100, 1, 2)] - private double Jphase; - - #endregion Parameters - - /////// - private JMA_Series indicator; - /////// - - protected TBars bars; - protected IChartWindow mainWindow; - protected Graphics graphics; - protected int firstOnScreenBarIndex, lastOnScreenBarIndex; - protected HistoricalData History; - protected int HistPeriod; - public JMA_chart() { - Name = "JMA - Jurik Moving Avg"; - Description = "Jurik Moving Average description"; - AddLineSeries(lineName: "JMA", lineColor: Color.Yellow, lineWidth: 3,lineStyle: LineStyle.Solid); - SeparateWindow = false; - HistPeriod = Period; - } - - - protected override void OnInit() { - base.OnInit(); - bars = new(); - var dur1 = this.HistoricalData.FromTime; - var dur = this.HistoricalData.Period.Duration.TotalSeconds * (HistPeriod * 4); //seconds of two periods - - this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); - - for (int i = this.History.Count - 1; i >= 0; i--) { - - var rec = this.History[i, SeekOriginHistory.Begin]; - - bars.Add(rec.TimeLeft, rec[PriceType.Open], - rec[PriceType.High], rec[PriceType.Low], - rec[PriceType.Close], rec[PriceType.Volume]); - } - - indicator = new(source: bars.Select(DataSource), period: Period, phase: Jphase, vshort: Vshort, vlong: Vlong, useNaN: true); - } - - protected override void OnUpdate(UpdateArgs args) { - base.OnUpdate(args); - bars.Add(Time(), GetPrice(PriceType.Open), - GetPrice(PriceType.High), - GetPrice(PriceType.Low), - GetPrice(PriceType.Close), - GetPrice(PriceType.Volume), - update: !(args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar)); - - this.SetValue(indicator[^1].v, lineIndex: 0); - } - public override void OnPaintChart(PaintChartEventArgs args) { - base.OnPaintChart(args); - if (this.CurrentChart == null) { - return; - } - - graphics = args.Graphics; - mainWindow = this.CurrentChart.MainWindow; - - DateTime leftTime = mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left); - DateTime rightTime = mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right); - firstOnScreenBarIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(leftTime); - lastOnScreenBarIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(rightTime)); - } - -} +using System; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using TradingPlatform.BusinessLayer; +using TradingPlatform.BusinessLayer.Chart; +namespace QuanTAlib; + +public class JMA_chart : Indicator { + #region Parameters + + [InputParameter("Data source", 0, variants: new object[] + { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, + "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] + private int DataSource = 3; + + [InputParameter("Smoothing period", 1, 1, 999, 1, 1)] + private int Period = 9; + + [InputParameter("Volatility short", 2, 3, 50, 1, 1)] + private int Vshort = 10; + + [InputParameter("Volatility long", 3, 20, 500, 1, 1)] + private int Vlong = 65; + + [InputParameter("Phase", 4, -100, 100, 1, 2)] + private double Jphase; + + #endregion Parameters + + /////// + private JMA_Series indicator; + /////// + + protected TBars bars; + protected IChartWindow mainWindow; + protected Graphics graphics; + protected int firstOnScreenBarIndex, lastOnScreenBarIndex; + protected HistoricalData History; + protected int HistPeriod; + public JMA_chart() { + Name = "JMA - Jurik Moving Avg"; + Description = "Jurik Moving Average description"; + AddLineSeries(lineName: "JMA", lineColor: Color.Yellow, lineWidth: 3,lineStyle: LineStyle.Solid); + SeparateWindow = false; + HistPeriod = Period; + } + + + protected override void OnInit() { + base.OnInit(); + bars = new(); + var dur1 = this.HistoricalData.FromTime; + var dur = this.HistoricalData.Period.Duration.TotalSeconds * (HistPeriod * 4); //seconds of two periods + + this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); + + for (int i = this.History.Count - 1; i >= 0; i--) { + + var rec = this.History[i, SeekOriginHistory.Begin]; + + bars.Add(rec.TimeLeft, rec[PriceType.Open], + rec[PriceType.High], rec[PriceType.Low], + rec[PriceType.Close], rec[PriceType.Volume]); + } + + indicator = new(source: bars.Select(DataSource), period: Period, phase: Jphase, vshort: Vshort, vlong: Vlong, useNaN: true); + indicator.Keep = Math.Max(Period, 100); + } + + protected override void OnUpdate(UpdateArgs args) { + base.OnUpdate(args); + bars.Add(Time(), GetPrice(PriceType.Open), + GetPrice(PriceType.High), + GetPrice(PriceType.Low), + GetPrice(PriceType.Close), + GetPrice(PriceType.Volume), + update: !(args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar)); + + this.SetValue(indicator[^1].v, lineIndex: 0); + } + public override void OnPaintChart(PaintChartEventArgs args) { + base.OnPaintChart(args); + if (this.CurrentChart == null) { + return; + } + + graphics = args.Graphics; + mainWindow = this.CurrentChart.MainWindow; + + DateTime leftTime = mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left); + DateTime rightTime = mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right); + firstOnScreenBarIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(leftTime); + lastOnScreenBarIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(rightTime)); + } + +} diff --git a/Indicators/Charts/TrailingStop.cs b/Indicators/Charts/TrailingStop.cs index 29aee917..50a1d4f7 100644 --- a/Indicators/Charts/TrailingStop.cs +++ b/Indicators/Charts/TrailingStop.cs @@ -1,95 +1,95 @@ -using System; -using System.Diagnostics; -using System.Drawing; -using System.Linq; -using TradingPlatform.BusinessLayer; -namespace QuanTAlib; - -public class TrailingStop_chart : Indicator { - #region Parameters - - [InputParameter("Period", 0, 1, 100, 1, 1)] - protected int _period = 30; - - [InputParameter("Factor", 1, 1, 100, 0.1, 1)] - protected double _factor = 10; - - [InputParameter("Long TS", 2)] - private bool _LongTS = true; - - [InputParameter("Short TS", 3)] - private bool _ShortTS = true; - - #endregion Parameters - - /////// - private HistoricalData History; - private TBars bars; - private ATR_Series _atr; - private double _tslineL, _ratchetL, _tslineS, _ratchetS; - - /////// - - public TrailingStop_chart() { - Name = $"ATR Trailing Stop"; - AddLineSeries(lineName: "TrailingATR Long", lineColor: Color.Yellow, lineWidth: 1,lineStyle: LineStyle.Dot); - AddLineSeries(lineName: "Ratchet Long", lineColor: Color.Yellow, lineWidth: 3, lineStyle: LineStyle.Solid); - - AddLineSeries(lineName: "TrailingATR Short", lineColor: Color.Yellow, lineWidth: 1, lineStyle: LineStyle.Dot); - AddLineSeries(lineName: "Ratchet Short", lineColor: Color.Yellow, lineWidth: 3, lineStyle: LineStyle.Solid); - - SeparateWindow = false; - } - - - protected override void OnInit() { - this.Name = $"Trailing Stop (ATR:{_period}, Mult:{_factor:f2})"; - this.bars = new(); - - this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); - for (int i = this.History.Count - 1; i >= 0; i--) { - var rec = this.History[i, SeekOriginHistory.Begin]; - bars.Add(rec.TimeLeft, rec[PriceType.Open], - rec[PriceType.High], rec[PriceType.Low], - rec[PriceType.Close], rec[PriceType.Volume]); - } - _atr = new(source: bars, _period, useNaN: true); - _ratchetL = Double.NegativeInfinity; - _ratchetS = Double.PositiveInfinity; - - this.LinesSeries[0].Visible = _LongTS; - this.LinesSeries[1].Visible = _LongTS; - this.LinesSeries[2].Visible = _ShortTS; - this.LinesSeries[3].Visible = _ShortTS; - } - - protected override void OnUpdate(UpdateArgs args) { - bool update = !(args.Reason == UpdateReason.NewBar || - args.Reason == UpdateReason.HistoricalBar); - this.bars.Add(this.Time(), this.GetPrice(PriceType.Open), - this.GetPrice(PriceType.High), - this.GetPrice(PriceType.Low), - this.GetPrice(PriceType.Close), - this.GetPrice(PriceType.Volume), update); - - _tslineL = bars.High[^1].v - (_factor * _atr[^1].v); - _ratchetL = Math.Max(_tslineL,_ratchetL); - if (_ratchetL > bars.Low[^1].v) { - this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.Yellow, bottomIcon: IndicatorLineMarkerIconType.DownArrow)); - _ratchetL = _tslineL; - } - - _tslineS = bars.High[^1].v + (_factor * _atr[^1].v); - _ratchetS = Math.Min(_tslineS, _ratchetS); - if (_ratchetS < bars.High[^1].v) { - this.LinesSeries[3].SetMarker(0, new IndicatorLineMarker(Color.Yellow, upperIcon: IndicatorLineMarkerIconType.UpArrow)); - _ratchetS = _tslineS; - } - - this.SetValue(_tslineL, lineIndex: 0); - this.SetValue(_ratchetL, lineIndex: 1); - this.SetValue(_tslineS, lineIndex: 2); - this.SetValue(_ratchetS, lineIndex: 3); - } -} - +using System; +using System.Diagnostics; +using System.Drawing; +using System.Linq; +using TradingPlatform.BusinessLayer; +namespace QuanTAlib; + +public class TrailingStop_chart : Indicator { + #region Parameters + + [InputParameter("Period", 0, 1, 100, 1, 1)] + protected int _period = 30; + + [InputParameter("Factor", 1, 1, 100, 0.1, 1)] + protected double _factor = 10; + + [InputParameter("Long TS", 2)] + private bool _LongTS = true; + + [InputParameter("Short TS", 3)] + private bool _ShortTS = true; + + #endregion Parameters + + /////// + private HistoricalData History; + private TBars bars; + private ATR_Series _atr; + private double _tslineL, _ratchetL, _tslineS, _ratchetS; + + /////// + + public TrailingStop_chart() { + Name = $"ATR Trailing Stop"; + AddLineSeries(lineName: "TrailingATR Long", lineColor: Color.Yellow, lineWidth: 1,lineStyle: LineStyle.Dot); + AddLineSeries(lineName: "Ratchet Long", lineColor: Color.Yellow, lineWidth: 3, lineStyle: LineStyle.Solid); + + AddLineSeries(lineName: "TrailingATR Short", lineColor: Color.Yellow, lineWidth: 1, lineStyle: LineStyle.Dot); + AddLineSeries(lineName: "Ratchet Short", lineColor: Color.Yellow, lineWidth: 3, lineStyle: LineStyle.Solid); + + SeparateWindow = false; + } + + + protected override void OnInit() { + this.Name = $"Trailing Stop (ATR:{_period}, Mult:{_factor:f2})"; + this.bars = new(); + + this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); + for (int i = this.History.Count - 1; i >= 0; i--) { + var rec = this.History[i, SeekOriginHistory.Begin]; + bars.Add(rec.TimeLeft, rec[PriceType.Open], + rec[PriceType.High], rec[PriceType.Low], + rec[PriceType.Close], rec[PriceType.Volume]); + } + _atr = new(source: bars, _period, useNaN: true); + _ratchetL = Double.NegativeInfinity; + _ratchetS = Double.PositiveInfinity; + + this.LinesSeries[0].Visible = _LongTS; + this.LinesSeries[1].Visible = _LongTS; + this.LinesSeries[2].Visible = _ShortTS; + this.LinesSeries[3].Visible = _ShortTS; + } + + protected override void OnUpdate(UpdateArgs args) { + bool update = !(args.Reason == UpdateReason.NewBar || + args.Reason == UpdateReason.HistoricalBar); + this.bars.Add(this.Time(), this.GetPrice(PriceType.Open), + this.GetPrice(PriceType.High), + this.GetPrice(PriceType.Low), + this.GetPrice(PriceType.Close), + this.GetPrice(PriceType.Volume), update); + + _tslineL = bars.High[^1].v - (_factor * _atr[^1].v); + _ratchetL = Math.Max(_tslineL,_ratchetL); + if (_ratchetL > bars.Low[^1].v) { + this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.Yellow, bottomIcon: IndicatorLineMarkerIconType.DownArrow)); + _ratchetL = _tslineL; + } + + _tslineS = bars.High[^1].v + (_factor * _atr[^1].v); + _ratchetS = Math.Min(_tslineS, _ratchetS); + if (_ratchetS < bars.High[^1].v) { + this.LinesSeries[3].SetMarker(0, new IndicatorLineMarker(Color.Yellow, upperIcon: IndicatorLineMarkerIconType.UpArrow)); + _ratchetS = _tslineS; + } + + this.SetValue(_tslineL, lineIndex: 0); + this.SetValue(_ratchetL, lineIndex: 1); + this.SetValue(_tslineS, lineIndex: 2); + this.SetValue(_ratchetS, lineIndex: 3); + } +} + diff --git a/Indicators/Indicators.csproj b/Indicators/Indicators.csproj index 5b9da593..eaa8bbb0 100644 --- a/Indicators/Indicators.csproj +++ b/Indicators/Indicators.csproj @@ -1,56 +1,56 @@ - - - net7.0 - preview - false - AnyCPU - Indicator - QuanTAlib_Indicators - QuanTAlib - embedded - AnyCPU - disable - False - ..\.sonarlint\mihakralj_quantalibcsharp.ruleset - 0.2.1.0 - 0.2.1.0 - 0.2.1-dev.2+Branch.dev.Sha.cb5fe2dc86a78fe9358da810d17952c82299ed3d - 0.2.1-dev.2 - NETSDK1057 - true - NETSDK1057 - - - True - 3 - True - anycpu - full - - - embedded - True - 3 - True - anycpu - - - - - - - - - - - - - QuanTAlib\%(RecursiveDir)%(Filename)%(Extension) - - - - - ..\.github\TradingPlatform.BusinessLayer.dll - - + + + net7.0 + preview + false + AnyCPU + Indicator + QuanTAlib_Indicators + QuanTAlib + embedded + AnyCPU + disable + False + ..\.sonarlint\mihakralj_quantalibcsharp.ruleset + 0.2.1.0 + 0.2.1.0 + 0.2.1-dev.2+Branch.dev.Sha.cb5fe2dc86a78fe9358da810d17952c82299ed3d + 0.2.1-dev.2 + NETSDK1057 + true + NETSDK1057 + + + True + 3 + True + anycpu + full + + + embedded + True + 3 + True + anycpu + + + + + + + + + + + + + QuanTAlib\%(RecursiveDir)%(Filename)%(Extension) + + + + + ..\.github\TradingPlatform.BusinessLayer.dll + + \ No newline at end of file diff --git a/LICENSE b/LICENSE index 261eeb9e..29f81d81 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,201 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Strategies/Strategies.csproj b/Strategies/Strategies.csproj index e665dc50..5cf21f57 100644 --- a/Strategies/Strategies.csproj +++ b/Strategies/Strategies.csproj @@ -1,53 +1,53 @@ - - - net7.0 - preview - false - AnyCPU - Strategy - QuanTAlib_Strategies - QuanTAlib - embedded - AnyCPU - disable - False - ..\.sonarlint\mihakralj_quantalibcsharp.ruleset - 0.2.1.0 - 0.2.1.0 - 0.2.1-dev.2+Branch.dev.Sha.cb5fe2dc86a78fe9358da810d17952c82299ed3d - 0.2.1-dev.2 - NETSDK1057 - true - NETSDK1057 - - - True - 3 - True - anycpu - full - - - embedded - True - 3 - True - anycpu - - - - - - - QuanTAlib\%(RecursiveDir)%(Filename)%(Extension) - - - - - - - - ..\.github\TradingPlatform.BusinessLayer.dll - - + + + net7.0 + preview + false + AnyCPU + Strategy + QuanTAlib_Strategies + QuanTAlib + embedded + AnyCPU + disable + False + ..\.sonarlint\mihakralj_quantalibcsharp.ruleset + 0.2.1.0 + 0.2.1.0 + 0.2.1-dev.2+Branch.dev.Sha.cb5fe2dc86a78fe9358da810d17952c82299ed3d + 0.2.1-dev.2 + NETSDK1057 + true + NETSDK1057 + + + True + 3 + True + anycpu + full + + + embedded + True + 3 + True + anycpu + + + + + + + QuanTAlib\%(RecursiveDir)%(Filename)%(Extension) + + + + + + + + ..\.github\TradingPlatform.BusinessLayer.dll + + \ No newline at end of file diff --git a/Tests/Basic tests/Indicators.cs b/Tests/Basic tests/Indicators.cs index de49baae..a9a9175e 100644 --- a/Tests/Basic tests/Indicators.cs +++ b/Tests/Basic tests/Indicators.cs @@ -1,155 +1,155 @@ -using Xunit; -using System; -using QuanTAlib; - -namespace Basics; -#nullable disable -public class Indicators -{ - private static Type[] maSeriesTypes = new Type[] - { - typeof(SMA_Series), - typeof(EMA_Series), - typeof(DEMA_Series), - typeof(TEMA_Series), - typeof(WMA_Series), - typeof(ALMA_Series), - typeof(DWMA_Series), - typeof(FWMA_Series), - typeof(HMA_Series), - typeof(ZLEMA_Series), - typeof(RMA_Series), - typeof(HEMA_Series), - typeof(JMA_Series), - typeof(CUSUM_Series), - typeof(SMMA_Series), - typeof(T3_Series), - typeof(KAMA_Series), - typeof(TRIMA_Series), - typeof(MAMA_Series), - typeof(HWMA_Series), -}; - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Name_exists(Type classType) - { - TSeries data = new("Data") {1,2,3}; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - Assert.NotEmpty(MA_Series.Name); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Series_Length(Type classType) - { - GBM_Feed feed = new(1000); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - Assert.Equal(1000, MA_Series.Count); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Return_data(Type classType) - { - TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - var result = MA_Series.Add(20); - Assert.Equal(result.v, MA_Series.Last.v); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Update(Type classType) - { - TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - var pre_update = MA_Series.Last.v; - - double pre_data = data.Last.v; - data.Add(20, true); - data.Add(pre_data, true); - - Assert.Equal(pre_update, MA_Series.Last.v); - Assert.Equal(data.Count, MA_Series.Count); -} - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Period_zero(Type classType) - { - GBM_Feed feed = new(100); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 0, false) as TSeries; - Assert.Equal(data.Count, MA_Series.Count); - Assert.False(double.IsNaN(MA_Series.Last.v)); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Reset(Type classType) - { - GBM_Feed feed = new(10); - TSeries data = feed.OHLC4; - var MA_Series = Activator.CreateInstance(classType, data, 10, false) as TSeries; - MA_Series.Reset(); - data.Add(0); - Assert.Equal(data.Last.v, MA_Series.Last.v); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Period_one(Type classType) - { - GBM_Feed feed = new(100); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 1, false) as TSeries; - Assert.InRange(MA_Series.Last.v - data.Last.v, -10e-6, 10e-6); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void NaN_test(Type classType) - { - GBM_Feed feed = new(100); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; - Assert.True(double.IsNaN(MA_Series[0].v)); - Assert.True(double.IsNaN(MA_Series[8].v)); - Assert.False(double.IsNaN(MA_Series[9].v)); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Edge_numbers(Type classType) - { - TSeries data = new() { double.Epsilon, double.PositiveInfinity, double.MaxValue, double.NegativeInfinity }; - var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; - Assert.Equal(4, MA_Series.Count); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void handling_NaN(Type classType) { - TSeries data = new("Name") { 1, 2, 3, 4, 5, 6, double.NaN, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; -var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; - Assert.False(double.IsNaN(MA_Series.Last.v)); - } - -public static IEnumerable MASeriesData() - { - foreach (var type in maSeriesTypes) - { - yield return new object[] { type }; - } - } -} +using Xunit; +using System; +using QuanTAlib; + +namespace Basics; +#nullable disable +public class Indicators +{ + private static Type[] maSeriesTypes = new Type[] + { + typeof(SMA_Series), + typeof(EMA_Series), + typeof(DEMA_Series), + typeof(TEMA_Series), + typeof(WMA_Series), + typeof(ALMA_Series), + typeof(DWMA_Series), + typeof(FWMA_Series), + typeof(HMA_Series), + typeof(ZLEMA_Series), + typeof(RMA_Series), + typeof(HEMA_Series), + typeof(JMA_Series), + typeof(CUSUM_Series), + typeof(SMMA_Series), + typeof(T3_Series), + typeof(KAMA_Series), + typeof(TRIMA_Series), + typeof(MAMA_Series), + typeof(HWMA_Series), +}; + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Name_exists(Type classType) + { + TSeries data = new("Data") {1,2,3}; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + Assert.NotEmpty(MA_Series.Name); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Series_Length(Type classType) + { + GBM_Feed feed = new(1000); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + Assert.Equal(1000, MA_Series.Count); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Return_data(Type classType) + { + TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + var result = MA_Series.Add(20); + Assert.Equal(result.v, MA_Series.Last.v); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Update(Type classType) + { + TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + var pre_update = MA_Series.Last.v; + + double pre_data = data.Last.v; + data.Add(20, true); + data.Add(pre_data, true); + + Assert.Equal(pre_update, MA_Series.Last.v); + Assert.Equal(data.Count, MA_Series.Count); +} + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Period_zero(Type classType) + { + GBM_Feed feed = new(100); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 0, false) as TSeries; + Assert.Equal(data.Count, MA_Series.Count); + Assert.False(double.IsNaN(MA_Series.Last.v)); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Reset(Type classType) + { + GBM_Feed feed = new(10); + TSeries data = feed.OHLC4; + var MA_Series = Activator.CreateInstance(classType, data, 10, false) as TSeries; + MA_Series.Reset(); + data.Add(0); + Assert.Equal(data.Last.v, MA_Series.Last.v); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Period_one(Type classType) + { + GBM_Feed feed = new(100); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 1, false) as TSeries; + Assert.InRange(MA_Series.Last.v - data.Last.v, -10e-6, 10e-6); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void NaN_test(Type classType) + { + GBM_Feed feed = new(100); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; + Assert.True(double.IsNaN(MA_Series[0].v)); + Assert.True(double.IsNaN(MA_Series[8].v)); + Assert.False(double.IsNaN(MA_Series[9].v)); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Edge_numbers(Type classType) + { + TSeries data = new() { double.Epsilon, double.PositiveInfinity, double.MaxValue, double.NegativeInfinity }; + var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; + Assert.Equal(4, MA_Series.Count); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void handling_NaN(Type classType) { + TSeries data = new("Name") { 1, 2, 3, 4, 5, 6, double.NaN, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; +var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; + Assert.False(double.IsNaN(MA_Series.Last.v)); + } + +public static IEnumerable MASeriesData() + { + foreach (var type in maSeriesTypes) + { + yield return new object[] { type }; + } + } +} #nullable restore \ No newline at end of file diff --git a/Tests/Basic tests/Oscillators.cs b/Tests/Basic tests/Oscillators.cs index 7d87e784..8ca89557 100644 --- a/Tests/Basic tests/Oscillators.cs +++ b/Tests/Basic tests/Oscillators.cs @@ -1,160 +1,160 @@ -using Xunit; -using System; -using System.Runtime.InteropServices; -using QuanTAlib; - -namespace Basics; -#nullable disable -public class Oscillators -{ - private static Type[] maSeriesTypes = new[] - { - typeof(BIAS_Series), - typeof(MAX_Series), - typeof(MIN_Series), - typeof(MIDPOINT_Series), - typeof(ZL_Series), - typeof(DECAY_Series), - typeof(ENTROPY_Series), - typeof(KURTOSIS_Series), - typeof(MAD_Series), - typeof(MAPE_Series), - typeof(MAE_Series), - typeof(MSE_Series), - typeof(SDEV_Series), - typeof(SMAPE_Series), - typeof(WMAPE_Series), - typeof(SSDEV_Series), - typeof(VAR_Series), - typeof(SVAR_Series), - typeof(MEDIAN_Series), - typeof(ZSCORE_Series), - typeof(CMO_Series), - typeof(RSI_Series), - typeof(TRIX_Series), - typeof(BBANDS_Series), -}; - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Name_exists(Type classType) - { - TSeries data = new("Data") {1,2,3}; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - Assert.NotEmpty(MA_Series.Name); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Series_Length(Type classType) - { - GBM_Feed feed = new(1000); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - Assert.Equal(1000, MA_Series.Count); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Return_data(Type classType) - { - TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - var result = MA_Series.Add(20); - Assert.Equal(result.v, MA_Series.Last.v); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Update(Type classType) - { - TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - var pre_update = MA_Series.Last.v; - - double pre_data = data.Last.v; - data.Add(20, true); - data.Add(pre_data, true); - - Assert.Equal(pre_update, MA_Series.Last.v); - Assert.Equal(data.Count, MA_Series.Count); -} - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Period_zero(Type classType) - { - GBM_Feed feed = new(100); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 0, false) as TSeries; - Assert.Equal(data.Count, MA_Series.Count); - Assert.False(double.IsNaN(MA_Series.Last.v)); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Reset(Type classType) - { - GBM_Feed feed = new(10); - TSeries data = feed.OHLC4; - var MA_Series = Activator.CreateInstance(classType, data, 10, false) as TSeries; - MA_Series.Reset(); - data.Add(1); - Assert.False(double.IsNaN(MA_Series.Last.v)); -} - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Period_one(Type classType) - { - GBM_Feed feed = new(100); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 1, false) as TSeries; - Assert.False(double.IsNaN(MA_Series[^1].v)); -} - - [Theory] - [MemberData(nameof(MASeriesData))] - public void NaN_test(Type classType) - { - GBM_Feed feed = new(100); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; - Assert.True(double.IsNaN(MA_Series[0].v)); - Assert.True(double.IsNaN(MA_Series[8].v)); - Assert.False(double.IsNaN(MA_Series[9].v)); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Edge_numbers(Type classType) - { - TSeries data = new() { double.Epsilon, double.PositiveInfinity, double.MaxValue, double.NegativeInfinity }; - var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; - Assert.Equal(4, MA_Series.Count); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void handling_NaN(Type classType) { - TSeries data = new("Name") { 1, 2, 3, 4, 5, 6, double.NaN, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; -var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; - Assert.False(double.IsNaN(MA_Series.Last.v)); - } - -public static IEnumerable MASeriesData() - { - foreach (var type in maSeriesTypes) - { - yield return new object[] { type }; - } - } -} +using Xunit; +using System; +using System.Runtime.InteropServices; +using QuanTAlib; + +namespace Basics; +#nullable disable +public class Oscillators +{ + private static Type[] maSeriesTypes = new[] + { + typeof(BIAS_Series), + typeof(MAX_Series), + typeof(MIN_Series), + typeof(MIDPOINT_Series), + typeof(ZL_Series), + typeof(DECAY_Series), + typeof(ENTROPY_Series), + typeof(KURTOSIS_Series), + typeof(MAD_Series), + typeof(MAPE_Series), + typeof(MAE_Series), + typeof(MSE_Series), + typeof(SDEV_Series), + typeof(SMAPE_Series), + typeof(WMAPE_Series), + typeof(SSDEV_Series), + typeof(VAR_Series), + typeof(SVAR_Series), + typeof(MEDIAN_Series), + typeof(ZSCORE_Series), + typeof(CMO_Series), + typeof(RSI_Series), + typeof(TRIX_Series), + typeof(BBANDS_Series), +}; + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Name_exists(Type classType) + { + TSeries data = new("Data") {1,2,3}; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + Assert.NotEmpty(MA_Series.Name); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Series_Length(Type classType) + { + GBM_Feed feed = new(1000); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + Assert.Equal(1000, MA_Series.Count); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Return_data(Type classType) + { + TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + var result = MA_Series.Add(20); + Assert.Equal(result.v, MA_Series.Last.v); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Update(Type classType) + { + TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + var pre_update = MA_Series.Last.v; + + double pre_data = data.Last.v; + data.Add(20, true); + data.Add(pre_data, true); + + Assert.Equal(pre_update, MA_Series.Last.v); + Assert.Equal(data.Count, MA_Series.Count); +} + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Period_zero(Type classType) + { + GBM_Feed feed = new(100); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 0, false) as TSeries; + Assert.Equal(data.Count, MA_Series.Count); + Assert.False(double.IsNaN(MA_Series.Last.v)); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Reset(Type classType) + { + GBM_Feed feed = new(10); + TSeries data = feed.OHLC4; + var MA_Series = Activator.CreateInstance(classType, data, 10, false) as TSeries; + MA_Series.Reset(); + data.Add(1); + Assert.False(double.IsNaN(MA_Series.Last.v)); +} + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Period_one(Type classType) + { + GBM_Feed feed = new(100); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 1, false) as TSeries; + Assert.False(double.IsNaN(MA_Series[^1].v)); +} + + [Theory] + [MemberData(nameof(MASeriesData))] + public void NaN_test(Type classType) + { + GBM_Feed feed = new(100); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; + Assert.True(double.IsNaN(MA_Series[0].v)); + Assert.True(double.IsNaN(MA_Series[8].v)); + Assert.False(double.IsNaN(MA_Series[9].v)); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Edge_numbers(Type classType) + { + TSeries data = new() { double.Epsilon, double.PositiveInfinity, double.MaxValue, double.NegativeInfinity }; + var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; + Assert.Equal(4, MA_Series.Count); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void handling_NaN(Type classType) { + TSeries data = new("Name") { 1, 2, 3, 4, 5, 6, double.NaN, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; +var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; + Assert.False(double.IsNaN(MA_Series.Last.v)); + } + +public static IEnumerable MASeriesData() + { + foreach (var type in maSeriesTypes) + { + yield return new object[] { type }; + } + } +} #nullable restore \ No newline at end of file diff --git a/Tests/Basic tests/TBars_input.cs b/Tests/Basic tests/TBars_input.cs index f3ee38b2..0b984821 100644 --- a/Tests/Basic tests/TBars_input.cs +++ b/Tests/Basic tests/TBars_input.cs @@ -1,96 +1,96 @@ -using Xunit; -using System; -using System.Runtime.InteropServices; -using QuanTAlib; - -namespace Basics; -#nullable disable -public class TBars -{ - private static Type[] maSeriesTypes = new Type[] - { - typeof(ATR_Series), - typeof(ATRP_Series), - typeof(TR_Series), - typeof(ADL_Series), - typeof(CCI_Series), - typeof(OBV_Series), - typeof(ADOSC_Series), - typeof(MIDPRICE_Series), -}; - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Name_exists(Type classType) - { - GBM_Feed data = new(10); - - var MA_Series = Activator.CreateInstance(classType, data) as TSeries; - Assert.NotEmpty(MA_Series.Name); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Series_Length(Type classType) - { - GBM_Feed data = new(1000); - - var MA_Series = Activator.CreateInstance(classType, data) as TSeries; - Assert.Equal(1000, MA_Series.Count); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Return_data(Type classType) - { - GBM_Feed data = new(10); - var MA_Series = Activator.CreateInstance(classType, data) as TSeries; - var result = MA_Series.Add((DateTime.Today, 1,2,3,4,5)); - Assert.Equal(result.v, MA_Series.Last.v); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Update(Type classType) - { - GBM_Feed data = new(10); - var MA_Series = Activator.CreateInstance(classType, data) as TSeries; - var pre_update = MA_Series.Last; - - var pre_data = data.Last; - data.Add((DateTime.Today, 1, 2, 3, 4, 5), true); - data.Add(pre_data, true); - - Assert.Equal(pre_update.v, MA_Series.Last.v); - Assert.Equal(data.Count, MA_Series.Count); -} - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Reset(Type classType) - { - GBM_Feed data = new(10); - var MA_Series = Activator.CreateInstance(classType, data) as TSeries; - MA_Series.Reset(); - data.Add(); - Assert.False(double.IsNaN(MA_Series.Last.v)); -} - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Period_default(Type classType) { - GBM_Feed data = new(100); - - var MA_Series = Activator.CreateInstance(classType, data) as TSeries; - Assert.False(double.IsNaN(MA_Series.Last.v)); - } - - public static IEnumerable MASeriesData() - { - foreach (var type in maSeriesTypes) - { - yield return new object[] { type }; - } - } -} +using Xunit; +using System; +using System.Runtime.InteropServices; +using QuanTAlib; + +namespace Basics; +#nullable disable +public class TBars +{ + private static Type[] maSeriesTypes = new Type[] + { + typeof(ATR_Series), + typeof(ATRP_Series), + typeof(TR_Series), + typeof(ADL_Series), + typeof(CCI_Series), + typeof(OBV_Series), + typeof(ADOSC_Series), + typeof(MIDPRICE_Series), +}; + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Name_exists(Type classType) + { + GBM_Feed data = new(10); + + var MA_Series = Activator.CreateInstance(classType, data) as TSeries; + Assert.NotEmpty(MA_Series.Name); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Series_Length(Type classType) + { + GBM_Feed data = new(1000); + + var MA_Series = Activator.CreateInstance(classType, data) as TSeries; + Assert.Equal(1000, MA_Series.Count); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Return_data(Type classType) + { + GBM_Feed data = new(10); + var MA_Series = Activator.CreateInstance(classType, data) as TSeries; + var result = MA_Series.Add((DateTime.Today, 1,2,3,4,5)); + Assert.Equal(result.v, MA_Series.Last.v); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Update(Type classType) + { + GBM_Feed data = new(10); + var MA_Series = Activator.CreateInstance(classType, data) as TSeries; + var pre_update = MA_Series.Last; + + var pre_data = data.Last; + data.Add((DateTime.Today, 1, 2, 3, 4, 5), true); + data.Add(pre_data, true); + + Assert.Equal(pre_update.v, MA_Series.Last.v); + Assert.Equal(data.Count, MA_Series.Count); +} + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Reset(Type classType) + { + GBM_Feed data = new(10); + var MA_Series = Activator.CreateInstance(classType, data) as TSeries; + MA_Series.Reset(); + data.Add(); + Assert.False(double.IsNaN(MA_Series.Last.v)); +} + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Period_default(Type classType) { + GBM_Feed data = new(100); + + var MA_Series = Activator.CreateInstance(classType, data) as TSeries; + Assert.False(double.IsNaN(MA_Series.Last.v)); + } + + public static IEnumerable MASeriesData() + { + foreach (var type in maSeriesTypes) + { + yield return new object[] { type }; + } + } +} #nullable restore \ No newline at end of file diff --git a/Tests/Pairs/ADD_Test.cs b/Tests/Pairs/ADD_Test.cs index 2c86e5a8..fa687709 100644 --- a/Tests/Pairs/ADD_Test.cs +++ b/Tests/Pairs/ADD_Test.cs @@ -1,64 +1,64 @@ -using Xunit; -using System; -using QuanTAlib; - -namespace Pairs; -public class ADD_Test -{ - [Fact] - public void ADDSeriesSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 0 }; - ADD_Series c = new(a, b); - Assert.Equal(5, c.Last().v); - } - - [Fact] - public void ADDSeriesDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - ADD_Series c = new(a, 10.0); - Assert.Equal(15, c.Last().v); - } - - [Fact] - public void ADDDoubleSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - ADD_Series c = new(10.0, a); - Assert.Equal(15, c.Last().v); - } - - [Fact] - public void ADDEventing_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 0 }; - ADD_Series c = new(a, b); - a.Add(2); - b.Add(2); - Assert.Equal(4, c.Last().v); - } - - [Fact] - public void ADDUpdateDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - double b = 10; - ADD_Series c = new(a, b); - a.Add(0, true); - Assert.Equal(10, c.Last().v); - } - - [Fact] - public void ADDUpdating_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 0 }; - ADD_Series c = new(a, b); - a.Add(10, true); - b.Add(10, true); - Assert.Equal(20, c.Last().v); - } -} +using Xunit; +using System; +using QuanTAlib; + +namespace Pairs; +public class ADD_Test +{ + [Fact] + public void ADDSeriesSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 0 }; + ADD_Series c = new(a, b); + Assert.Equal(5, c.Last().v); + } + + [Fact] + public void ADDSeriesDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + ADD_Series c = new(a, 10.0); + Assert.Equal(15, c.Last().v); + } + + [Fact] + public void ADDDoubleSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + ADD_Series c = new(10.0, a); + Assert.Equal(15, c.Last().v); + } + + [Fact] + public void ADDEventing_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 0 }; + ADD_Series c = new(a, b); + a.Add(2); + b.Add(2); + Assert.Equal(4, c.Last().v); + } + + [Fact] + public void ADDUpdateDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + double b = 10; + ADD_Series c = new(a, b); + a.Add(0, true); + Assert.Equal(10, c.Last().v); + } + + [Fact] + public void ADDUpdating_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 0 }; + ADD_Series c = new(a, b); + a.Add(10, true); + b.Add(10, true); + Assert.Equal(20, c.Last().v); + } +} diff --git a/Tests/Pairs/DIV_Test.cs b/Tests/Pairs/DIV_Test.cs index a8286017..d3e57573 100644 --- a/Tests/Pairs/DIV_Test.cs +++ b/Tests/Pairs/DIV_Test.cs @@ -1,64 +1,64 @@ -using Xunit; -using System; -using QuanTAlib; - -namespace Pairs; -public class DIV_Test -{ - [Fact] - public void DIVSeriesSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 15 }; - TSeries b = new() { 5, 4, 3, 2, 1, 3 }; - DIV_Series c = new(a, b); - Assert.Equal(5, c.Last().v); - } - - [Fact] - public void DIVSeriesDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 15.0 }; - DIV_Series c = new(a, 0); - Assert.Equal(double.PositiveInfinity, c.Last().v); - } - - [Fact] - public void DIVDoubleSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 3.0 }; - DIV_Series c = new(12.0, a); - Assert.Equal(4.0, c.Last().v); - } - - [Fact] - public void DIVEventing_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 0 }; - DIV_Series c = new(a, b); - a.Add(12.0); - b.Add(2); - Assert.Equal(6.0, c.Last().v); - } - - [Fact] - public void DIVUpdatewDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 15 }; - double b = 2; - DIV_Series c = new(a, b); - a.Add(10, true); - Assert.Equal(5, c.Last().v); - } - - [Fact] - public void DIVUpdating_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 1 }; - DIV_Series c = new(a, b); - a.Add(10, true); - b.Add(2, true); - Assert.Equal(5, c.Last().v); - } -} +using Xunit; +using System; +using QuanTAlib; + +namespace Pairs; +public class DIV_Test +{ + [Fact] + public void DIVSeriesSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 15 }; + TSeries b = new() { 5, 4, 3, 2, 1, 3 }; + DIV_Series c = new(a, b); + Assert.Equal(5, c.Last().v); + } + + [Fact] + public void DIVSeriesDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 15.0 }; + DIV_Series c = new(a, 0); + Assert.Equal(double.PositiveInfinity, c.Last().v); + } + + [Fact] + public void DIVDoubleSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 3.0 }; + DIV_Series c = new(12.0, a); + Assert.Equal(4.0, c.Last().v); + } + + [Fact] + public void DIVEventing_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 0 }; + DIV_Series c = new(a, b); + a.Add(12.0); + b.Add(2); + Assert.Equal(6.0, c.Last().v); + } + + [Fact] + public void DIVUpdatewDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 15 }; + double b = 2; + DIV_Series c = new(a, b); + a.Add(10, true); + Assert.Equal(5, c.Last().v); + } + + [Fact] + public void DIVUpdating_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 1 }; + DIV_Series c = new(a, b); + a.Add(10, true); + b.Add(2, true); + Assert.Equal(5, c.Last().v); + } +} diff --git a/Tests/Pairs/MUL_Test.cs b/Tests/Pairs/MUL_Test.cs index 90ad3888..22a343e5 100644 --- a/Tests/Pairs/MUL_Test.cs +++ b/Tests/Pairs/MUL_Test.cs @@ -1,64 +1,64 @@ -using Xunit; -using System; -using QuanTAlib; - -namespace Pairs; -public class MUL_Test -{ - [Fact] - public void MULSeriesSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 1 }; - MUL_Series c = new(a, b); - Assert.Equal(5, c.Last().v); - } - - [Fact] - public void MULSeriesDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - MUL_Series c = new(a, 10.0); - Assert.Equal(50, c.Last().v); - } - - [Fact] - public void MULDoubleSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - MUL_Series c = new(5.0, a); - Assert.Equal(25, c.Last().v); - } - - [Fact] - public void MULEventing_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 0 }; - MUL_Series c = new(a, b); - a.Add(2); - b.Add(5); - Assert.Equal(10, c.Last().v); - } - - [Fact] - public void MULUpdateDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - double b = 10; - MUL_Series c = new(a, b); - a.Add(2, true); - Assert.Equal(20, c.Last().v); - } - - [Fact] - public void MULUpdating_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 0 }; - MUL_Series c = new(a, b); - a.Add(10, true); - b.Add(10, true); - Assert.Equal(100, c.Last().v); - } -} +using Xunit; +using System; +using QuanTAlib; + +namespace Pairs; +public class MUL_Test +{ + [Fact] + public void MULSeriesSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 1 }; + MUL_Series c = new(a, b); + Assert.Equal(5, c.Last().v); + } + + [Fact] + public void MULSeriesDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + MUL_Series c = new(a, 10.0); + Assert.Equal(50, c.Last().v); + } + + [Fact] + public void MULDoubleSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + MUL_Series c = new(5.0, a); + Assert.Equal(25, c.Last().v); + } + + [Fact] + public void MULEventing_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 0 }; + MUL_Series c = new(a, b); + a.Add(2); + b.Add(5); + Assert.Equal(10, c.Last().v); + } + + [Fact] + public void MULUpdateDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + double b = 10; + MUL_Series c = new(a, b); + a.Add(2, true); + Assert.Equal(20, c.Last().v); + } + + [Fact] + public void MULUpdating_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 0 }; + MUL_Series c = new(a, b); + a.Add(10, true); + b.Add(10, true); + Assert.Equal(100, c.Last().v); + } +} diff --git a/Tests/Pairs/SUB_Test.cs b/Tests/Pairs/SUB_Test.cs index 04775464..abed75f1 100644 --- a/Tests/Pairs/SUB_Test.cs +++ b/Tests/Pairs/SUB_Test.cs @@ -1,64 +1,64 @@ -using Xunit; -using System; -using QuanTAlib; - -namespace Pairs; -public class SUB_Test -{ - [Fact] - public void SUBSeriesSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 1 }; - SUB_Series c = new(a, b); - Assert.Equal(4, c.Last().v); - } - - [Fact] - public void SUBSeriesDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 15.0 }; - SUB_Series c = new(a, 10.0); - Assert.Equal(5.0, c.Last().v); - } - - [Fact] - public void SUBDoubleSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 15.0 }; - SUB_Series c = new(10.0, a); - Assert.Equal(-5.0, c.Last().v); - } - - [Fact] - public void SUBEventing_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 0 }; - SUB_Series c = new(a, b); - a.Add(7.0); - b.Add(2); - Assert.Equal(5.0, c.Last().v); - } - - [Fact] - public void SUBUpdatewDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 15 }; - double b = 10; - SUB_Series c = new(a, b); - a.Add(1, true); - Assert.Equal(-9, c.Last().v); - } - - [Fact] - public void SUBUpdating_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 1 }; - SUB_Series c = new(a, b); - a.Add(10, true); - b.Add(0, true); - Assert.Equal(10, c.Last().v); - } -} +using Xunit; +using System; +using QuanTAlib; + +namespace Pairs; +public class SUB_Test +{ + [Fact] + public void SUBSeriesSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 1 }; + SUB_Series c = new(a, b); + Assert.Equal(4, c.Last().v); + } + + [Fact] + public void SUBSeriesDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 15.0 }; + SUB_Series c = new(a, 10.0); + Assert.Equal(5.0, c.Last().v); + } + + [Fact] + public void SUBDoubleSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 15.0 }; + SUB_Series c = new(10.0, a); + Assert.Equal(-5.0, c.Last().v); + } + + [Fact] + public void SUBEventing_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 0 }; + SUB_Series c = new(a, b); + a.Add(7.0); + b.Add(2); + Assert.Equal(5.0, c.Last().v); + } + + [Fact] + public void SUBUpdatewDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 15 }; + double b = 10; + SUB_Series c = new(a, b); + a.Add(1, true); + Assert.Equal(-9, c.Last().v); + } + + [Fact] + public void SUBUpdating_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 1 }; + SUB_Series c = new(a, b); + a.Add(10, true); + b.Add(0, true); + Assert.Equal(10, c.Last().v); + } +} diff --git a/Tests/Pairs/TBars_Test.cs b/Tests/Pairs/TBars_Test.cs index 531703bc..e23e10f7 100644 --- a/Tests/Pairs/TBars_Test.cs +++ b/Tests/Pairs/TBars_Test.cs @@ -1,112 +1,112 @@ -using Xunit; -using System; -using QuanTAlib; - -namespace Bars; -public class TBars_Test -{ - [Fact] - public void InsertingTuple() - { - TBars s = new() { (t: DateTime.Today, o: double.Epsilon, h: double.NaN, l: Double.MaxValue, c: Double.NegativeInfinity, v: Double.PositiveInfinity) }; - var tup = (t: DateTime.Today, o: double.Epsilon, h: double.NaN, l: Double.MaxValue, - c: Double.NegativeInfinity, v: Double.PositiveInfinity); - Assert.Equal(tup, s[^1]); - } - - [Fact] - public void Casting_Parameters() - { - TBars s = new() - { - { DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1, false } - }; - Assert.Equal(0.1, s[^1].o); - Assert.Equal(1.1, s[^1].h); - Assert.Equal(2.1, s[^1].l); - Assert.Equal(3.1, s[^1].c); - Assert.Equal(4.1, s[^1].v); - Assert.Equal(DateTime.Today, s[^1].t); - Assert.Single(s); - } - - [Fact] - public void Updating_Value() - { - TBars s = new() - { - { DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1 } - }; - s.Add(DateTime.Today, 1.0, 1.0, 1.0, 1.0, 1.0, update: false); - s.Add(DateTime.Today, 0.0, 0.0, 0.0, 0.0, 0.0, update: true); - Assert.Equal(0.0, s[^1].o); - Assert.Equal(0.0, s[^1].h); - Assert.Equal(0.0, s[^1].l); - Assert.Equal(0.0, s[^1].c); - Assert.Equal(0.0, s[^1].v); - Assert.Equal(2, s.Count); - } - [Fact] - public void Extracting_TSeries() - { - TBars s = new() - { - { DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1 }, - { DateTime.Today, 2.1, 3.1, 4.1, 5.1, 6.1 } - }; - - TSeries t = s.Open; - Assert.Equal(t.t, s.Open.t); - Assert.Equal(t.v, s.Open.v); - - t = s.High; - Assert.Equal(t.t, s.High.t); - Assert.Equal(t.v, s.High.v); - - t = s.Low; - Assert.Equal(t.t, s.Low.t); - Assert.Equal(t.v, s.Low.v); - - t = s.Close; - Assert.Equal(t.t, s.Close.t); - Assert.Equal(t.v, s.Close.v); - - t = s.Volume; - Assert.Equal(t.t, s.Volume.t); - Assert.Equal(t.v, s.Volume.v); - - t = s.HL2; - Assert.Equal(t.t, s.HL2.t); - Assert.Equal(t.v, s.HL2.v); - - t = s.OC2; - Assert.Equal(t.t, s.OC2.t); - Assert.Equal(t.v, s.OC2.v); - - t = s.OHL3; - Assert.Equal(t.t, s.OHL3.t); - Assert.Equal(t.v, s.OHL3.v); - - t = s.HLC3; - Assert.Equal(t.t, s.HLC3.t); - Assert.Equal(t.v, s.HLC3.v); - - t = s.OHLC4; - Assert.Equal(t.t, s.OHLC4.t); - Assert.Equal(t.v, s.OHLC4.v); - - t = s.HLCC4; - Assert.Equal(t.t, s.HLCC4.t); - Assert.Equal(t.v, s.HLCC4.v); - } - [Fact] - public void Broadcasting_Events() - { - TBars s = new() { (DateTime.Today, 2.1, 3.1, 4.1, 5.1, 6.1) }; - TSeries t = new(); - s.Close.Pub += t.Sub; - s.Add(DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1, false); - Assert.Equal(s.Close.v, t.v); - Assert.Equal(s.Close.Count, t.Count); - } -} +using Xunit; +using System; +using QuanTAlib; + +namespace Bars; +public class TBars_Test +{ + [Fact] + public void InsertingTuple() + { + TBars s = new() { (t: DateTime.Today, o: double.Epsilon, h: double.NaN, l: Double.MaxValue, c: Double.NegativeInfinity, v: Double.PositiveInfinity) }; + var tup = (t: DateTime.Today, o: double.Epsilon, h: double.NaN, l: Double.MaxValue, + c: Double.NegativeInfinity, v: Double.PositiveInfinity); + Assert.Equal(tup, s[^1]); + } + + [Fact] + public void Casting_Parameters() + { + TBars s = new() + { + { DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1, false } + }; + Assert.Equal(0.1, s[^1].o); + Assert.Equal(1.1, s[^1].h); + Assert.Equal(2.1, s[^1].l); + Assert.Equal(3.1, s[^1].c); + Assert.Equal(4.1, s[^1].v); + Assert.Equal(DateTime.Today, s[^1].t); + Assert.Single(s); + } + + [Fact] + public void Updating_Value() + { + TBars s = new() + { + { DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1 } + }; + s.Add(DateTime.Today, 1.0, 1.0, 1.0, 1.0, 1.0, update: false); + s.Add(DateTime.Today, 0.0, 0.0, 0.0, 0.0, 0.0, update: true); + Assert.Equal(0.0, s[^1].o); + Assert.Equal(0.0, s[^1].h); + Assert.Equal(0.0, s[^1].l); + Assert.Equal(0.0, s[^1].c); + Assert.Equal(0.0, s[^1].v); + Assert.Equal(2, s.Count); + } + [Fact] + public void Extracting_TSeries() + { + TBars s = new() + { + { DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1 }, + { DateTime.Today, 2.1, 3.1, 4.1, 5.1, 6.1 } + }; + + TSeries t = s.Open; + Assert.Equal(t.t, s.Open.t); + Assert.Equal(t.v, s.Open.v); + + t = s.High; + Assert.Equal(t.t, s.High.t); + Assert.Equal(t.v, s.High.v); + + t = s.Low; + Assert.Equal(t.t, s.Low.t); + Assert.Equal(t.v, s.Low.v); + + t = s.Close; + Assert.Equal(t.t, s.Close.t); + Assert.Equal(t.v, s.Close.v); + + t = s.Volume; + Assert.Equal(t.t, s.Volume.t); + Assert.Equal(t.v, s.Volume.v); + + t = s.HL2; + Assert.Equal(t.t, s.HL2.t); + Assert.Equal(t.v, s.HL2.v); + + t = s.OC2; + Assert.Equal(t.t, s.OC2.t); + Assert.Equal(t.v, s.OC2.v); + + t = s.OHL3; + Assert.Equal(t.t, s.OHL3.t); + Assert.Equal(t.v, s.OHL3.v); + + t = s.HLC3; + Assert.Equal(t.t, s.HLC3.t); + Assert.Equal(t.v, s.HLC3.v); + + t = s.OHLC4; + Assert.Equal(t.t, s.OHLC4.t); + Assert.Equal(t.v, s.OHLC4.v); + + t = s.HLCC4; + Assert.Equal(t.t, s.HLCC4.t); + Assert.Equal(t.v, s.HLCC4.v); + } + [Fact] + public void Broadcasting_Events() + { + TBars s = new() { (DateTime.Today, 2.1, 3.1, 4.1, 5.1, 6.1) }; + TSeries t = new(); + s.Close.Pub += t.Sub; + s.Add(DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1, false); + Assert.Equal(s.Close.v, t.v); + Assert.Equal(s.Close.Count, t.Count); + } +} diff --git a/Tests/Validations/Trends/Skender.cs b/Tests/Validations/Trends/Skender.cs index d413a391..a3950e96 100644 --- a/Tests/Validations/Trends/Skender.cs +++ b/Tests/Validations/Trends/Skender.cs @@ -1,486 +1,486 @@ -using System; -using QuanTAlib; -using Skender.Stock.Indicators; -using Xunit; - -namespace Validations; -public class Skender -{ - private readonly GBM_Feed bars; - private readonly Random rnd = new(); - private readonly int period, digits, skip; - private readonly IEnumerable quotes; - - - public Skender() - { - bars = new(Bars: 10000, Volatility: 0.5, Drift: 0.0, Precision: 2); - period = rnd.Next(30) + 5; - digits = 6; //minimizing rounding errors in type conversions - skip = period+2; - - quotes = bars.Select(q => new Quote - { - Date = q.t, - Open = (decimal)q.o, - High = (decimal)q.h, - Low = (decimal)q.l, - Close = (decimal)q.c, - Volume = (decimal)q.v - }); - } - -/* - [Fact] - public void ADL() - { - ADL_Series QL = new(bars); - var SK = quotes.GetAdl().Select(i => i.Adl); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1)!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - */ - [Fact] - public void ALMA() - { - ALMA_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetAlma(period).Select(i => i.Alma.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void ATR() - { - ATR_Series QL = new(bars, period:period,useNaN: false); - var SK = quotes.GetAtr(period).Select(i => i.Atr.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void ATRP() - { - ATRP_Series QL = new(bars, period, false); - var SK = quotes.GetAtr(period).Select(i => i.Atrp.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void BBANDS() - { - BBANDS_Series QL = new(bars.Close, period, 2.0, useNaN: false); - var SK = quotes.GetBollingerBands(period, 2.0); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL.Mid[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Sma!.Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - QL_item = QL.Upper[i - 1].v; - SK_item = SK.ElementAt(i - 1).UpperBand!.Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - QL_item = QL.Lower[i - 1].v; - SK_item = SK.ElementAt(i - 1).LowerBand!.Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - QL_item = QL.Bandwidth[i - 1].v; - SK_item = SK.ElementAt(i - 1).Width!.Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - QL_item = QL.PercentB[i - 1].v; - SK_item = SK.ElementAt(i - 1).PercentB!.Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - QL_item = QL.Zscore[i - 1].v; - SK_item = SK.ElementAt(i - 1).ZScore!.Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void CCI() - { - CCI_Series QL = new(bars, period, false); - var SK = quotes.GetCci(period).Select(i => i.Cci.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void CMO() - { - CMO_Series QL = new(bars.Close, period, false); - var SK = quotes.GetCmo(period).Select(i => i.Cmo.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void CORR() - { - CORR_Series QL = new(bars.High, bars.Low, period, false); - var SK = quotes.Use(CandlePart.High).GetCorrelation(quotes.Use(CandlePart.Low), period).Select(i => i.Correlation.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void COVAR() - { - COVAR_Series QL = new(bars.High, bars.Low, period, false); - var SK = quotes.Use(CandlePart.High).GetCorrelation(quotes.Use(CandlePart.Low), period).Select(i => i.Covariance.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void DEMA() - { - DEMA_Series QL = new(bars.Close, period, false, useSMA: true); - var SK = quotes.GetDema(period).Select(i => i.Dema.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void EMA() - { - EMA_Series QL = new(bars.Close, period, false); - var SK = quotes.GetEma(lookbackPeriods: period).Select(i => i.Ema.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void HL2() - { - TSeries QL = bars.HL2; - var SK = quotes.GetBaseQuote(CandlePart.HL2).ToList(); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void HLC3() - { - TSeries QL = bars.HLC3; - var SK = quotes.GetBaseQuote(CandlePart.HLC3).ToList(); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void HMA() - { - HMA_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetHma(period).Select(i => i.Hma.Null2NaN()!); - for (int i = QL.Length; i > skip*2; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - - [Fact] - public void KAMA() - { - // TODO: check precision of KAMA() - KAMA_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetKama(period).Select(i => i.Kama.Null2NaN()!); - for (int i = QL.Length; i > skip+2; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void SLOPE() - { - SLOPE_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetSlope(period); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = (double)SK.ElementAt(i - 1).Slope!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - QL_item = QL.Intercept[i - 1].v; - SK_item = (double)SK.ElementAt(i - 1).Intercept!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - QL_item = QL.RSquared[i - 1].v; - SK_item = (double)SK.ElementAt(i - 1).RSquared!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - QL_item = QL.StdDev[i - 1].v; - SK_item = (double)SK.ElementAt(i - 1).StdDev!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void MACD() - { - MACD_Series QL = new(bars.Close, 26, 12, 9, useNaN: false); - var SK = quotes.GetMacd(12, 26, 9); - for (int i = QL.Length; i > 27; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Macd.Null2NaN()!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - //QL_item = QL.Signal[i - 1].v; - //SK_item = SK.ElementAt(i - 1).Signal.Null2NaN()!; - //Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void MAD() - { - MAD_Series QL = new(bars.Close, period, false); - var SK = quotes.GetSmaAnalysis(period).Select(i => i.Mad.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void MAMA() - { - MAMA_Series QL = new(bars.HL2, fastlimit: 0.5, slowlimit: 0.05); - var SK = quotes.GetMama(fastLimit: 0.5, slowLimit: 0.05); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Mama.Null2NaN()!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - QL_item = QL.Fama[i - 1].v; - SK_item = SK.ElementAt(i - 1).Fama.Null2NaN()!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void MAPE() - { - MAPE_Series QL = new(bars.Close, period, false); - var SK = quotes.GetSmaAnalysis(period).Select(i => i.Mape.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void MSE() - { - MSE_Series QL = new(bars.Close, period, false); - var SK = quotes.GetSmaAnalysis(period).Select(i => i.Mse.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void OBV() - { - OBV_Series QL = new(bars, period, false); - var SK = quotes.GetObv(period).Select(i => i.Obv!); - for (int i = QL.Length; i > skip; i--) { - double QL_item = QL.Last().v; - // adding volume[0] to OBV to pass the test and keep compatibility with TA-LIB - double SK_item = SK.Last()! + (double)quotes.First().Volume!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void OC2() - { - TSeries QL = bars.OC2; - var SK = quotes.GetBaseQuote(CandlePart.OC2).ToList(); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void OHL3() - { - TSeries QL = bars.OHL3; - var SK = quotes.GetBaseQuote(CandlePart.OHL3).ToList(); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void OHLC4() - { - TSeries QL = bars.OHLC4; - var SK = quotes.GetBaseQuote(CandlePart.OHLC4).ToList(); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void RSI() - { - RSI_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetRsi(period).Select(i => i.Rsi.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void SDEV() - { - SDEV_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetStdDev(period).Select(i => i.StdDev.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void SMA() - { - SMA_Series QL = new(bars.Close, period, false); - var SK = quotes.GetSma(period).Select(i => i.Sma.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void SMMA() - { - SMMA_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetSmma(period).Select(i => i.Smma.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void T3() - { - T3_Series QL = new(source: bars.Close, period: period, vfactor: 0.7, false); - var SK = quotes.GetT3(lookbackPeriods: period, volumeFactor: 0.7).Select(i => i.T3.Null2NaN()!); - for (int i = QL.Length; i > period*15; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void TRIX() { - TRIX_Series QL = new(bars.Close, period, false); - var SK = quotes.GetTrix(period).Select(i => i.Trix.Null2NaN()!); - for (int i = QL.Length; i > period*12; i--) { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void TEMA() - { - TEMA_Series QL = new(bars.Close, period, false); - var SK = quotes.GetTema(period).Select(i => i.Tema.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void TR() - { - TR_Series QL = new(bars); - var SK = quotes.GetTr().Select(i => i.Tr.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void WMA() - { - WMA_Series QL = new(bars.Close, period, false); - var SK = quotes.GetWma(period).Select(i => i.Wma.Null2NaN()!); - for (int i = QL.Length; i > skip*2; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void ZSCORE() - { - ZSCORE_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetStdDev(period).Select(i => i.ZScore.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - -} +using System; +using QuanTAlib; +using Skender.Stock.Indicators; +using Xunit; + +namespace Validations; +public class Skender +{ + private readonly GBM_Feed bars; + private readonly Random rnd = new(); + private readonly int period, digits, skip; + private readonly IEnumerable quotes; + + + public Skender() + { + bars = new(Bars: 10000, Volatility: 0.5, Drift: 0.0, Precision: 2); + period = rnd.Next(30) + 5; + digits = 6; //minimizing rounding errors in type conversions + skip = period+2; + + quotes = bars.Select(q => new Quote + { + Date = q.t, + Open = (decimal)q.o, + High = (decimal)q.h, + Low = (decimal)q.l, + Close = (decimal)q.c, + Volume = (decimal)q.v + }); + } + +/* + [Fact] + public void ADL() + { + ADL_Series QL = new(bars); + var SK = quotes.GetAdl().Select(i => i.Adl); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1)!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + */ + [Fact] + public void ALMA() + { + ALMA_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetAlma(period).Select(i => i.Alma.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void ATR() + { + ATR_Series QL = new(bars, period:period,useNaN: false); + var SK = quotes.GetAtr(period).Select(i => i.Atr.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void ATRP() + { + ATRP_Series QL = new(bars, period, false); + var SK = quotes.GetAtr(period).Select(i => i.Atrp.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void BBANDS() + { + BBANDS_Series QL = new(bars.Close, period, 2.0, useNaN: false); + var SK = quotes.GetBollingerBands(period, 2.0); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL.Mid[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Sma!.Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.Upper[i - 1].v; + SK_item = SK.ElementAt(i - 1).UpperBand!.Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.Lower[i - 1].v; + SK_item = SK.ElementAt(i - 1).LowerBand!.Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.Bandwidth[i - 1].v; + SK_item = SK.ElementAt(i - 1).Width!.Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.PercentB[i - 1].v; + SK_item = SK.ElementAt(i - 1).PercentB!.Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.Zscore[i - 1].v; + SK_item = SK.ElementAt(i - 1).ZScore!.Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void CCI() + { + CCI_Series QL = new(bars, period, false); + var SK = quotes.GetCci(period).Select(i => i.Cci.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void CMO() + { + CMO_Series QL = new(bars.Close, period, false); + var SK = quotes.GetCmo(period).Select(i => i.Cmo.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void CORR() + { + CORR_Series QL = new(bars.High, bars.Low, period, false); + var SK = quotes.Use(CandlePart.High).GetCorrelation(quotes.Use(CandlePart.Low), period).Select(i => i.Correlation.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void COVAR() + { + COVAR_Series QL = new(bars.High, bars.Low, period, false); + var SK = quotes.Use(CandlePart.High).GetCorrelation(quotes.Use(CandlePart.Low), period).Select(i => i.Covariance.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void DEMA() + { + DEMA_Series QL = new(bars.Close, period, false, useSMA: true); + var SK = quotes.GetDema(period).Select(i => i.Dema.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void EMA() + { + EMA_Series QL = new(bars.Close, period, false); + var SK = quotes.GetEma(lookbackPeriods: period).Select(i => i.Ema.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void HL2() + { + TSeries QL = bars.HL2; + var SK = quotes.GetBaseQuote(CandlePart.HL2).ToList(); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void HLC3() + { + TSeries QL = bars.HLC3; + var SK = quotes.GetBaseQuote(CandlePart.HLC3).ToList(); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void HMA() + { + HMA_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetHma(period).Select(i => i.Hma.Null2NaN()!); + for (int i = QL.Length; i > skip*2; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + + [Fact] + public void KAMA() + { + // TODO: check precision of KAMA() + KAMA_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetKama(period).Select(i => i.Kama.Null2NaN()!); + for (int i = QL.Length; i > skip+2; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void SLOPE() + { + SLOPE_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetSlope(period); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = (double)SK.ElementAt(i - 1).Slope!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + QL_item = QL.Intercept[i - 1].v; + SK_item = (double)SK.ElementAt(i - 1).Intercept!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + QL_item = QL.RSquared[i - 1].v; + SK_item = (double)SK.ElementAt(i - 1).RSquared!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + QL_item = QL.StdDev[i - 1].v; + SK_item = (double)SK.ElementAt(i - 1).StdDev!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void MACD() + { + MACD_Series QL = new(bars.Close, 26, 12, 9, useNaN: false); + var SK = quotes.GetMacd(12, 26, 9); + for (int i = QL.Length; i > 27; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Macd.Null2NaN()!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + //QL_item = QL.Signal[i - 1].v; + //SK_item = SK.ElementAt(i - 1).Signal.Null2NaN()!; + //Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void MAD() + { + MAD_Series QL = new(bars.Close, period, false); + var SK = quotes.GetSmaAnalysis(period).Select(i => i.Mad.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void MAMA() + { + MAMA_Series QL = new(bars.HL2, fastlimit: 0.5, slowlimit: 0.05); + var SK = quotes.GetMama(fastLimit: 0.5, slowLimit: 0.05); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Mama.Null2NaN()!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + QL_item = QL.Fama[i - 1].v; + SK_item = SK.ElementAt(i - 1).Fama.Null2NaN()!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void MAPE() + { + MAPE_Series QL = new(bars.Close, period, false); + var SK = quotes.GetSmaAnalysis(period).Select(i => i.Mape.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void MSE() + { + MSE_Series QL = new(bars.Close, period, false); + var SK = quotes.GetSmaAnalysis(period).Select(i => i.Mse.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void OBV() + { + OBV_Series QL = new(bars, period, false); + var SK = quotes.GetObv(period).Select(i => i.Obv!); + for (int i = QL.Length; i > skip; i--) { + double QL_item = QL.Last().v; + // adding volume[0] to OBV to pass the test and keep compatibility with TA-LIB + double SK_item = SK.Last()! + (double)quotes.First().Volume!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void OC2() + { + TSeries QL = bars.OC2; + var SK = quotes.GetBaseQuote(CandlePart.OC2).ToList(); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void OHL3() + { + TSeries QL = bars.OHL3; + var SK = quotes.GetBaseQuote(CandlePart.OHL3).ToList(); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void OHLC4() + { + TSeries QL = bars.OHLC4; + var SK = quotes.GetBaseQuote(CandlePart.OHLC4).ToList(); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void RSI() + { + RSI_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetRsi(period).Select(i => i.Rsi.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void SDEV() + { + SDEV_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetStdDev(period).Select(i => i.StdDev.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void SMA() + { + SMA_Series QL = new(bars.Close, period, false); + var SK = quotes.GetSma(period).Select(i => i.Sma.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void SMMA() + { + SMMA_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetSmma(period).Select(i => i.Smma.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void T3() + { + T3_Series QL = new(source: bars.Close, period: period, vfactor: 0.7, false); + var SK = quotes.GetT3(lookbackPeriods: period, volumeFactor: 0.7).Select(i => i.T3.Null2NaN()!); + for (int i = QL.Length; i > period*15; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void TRIX() { + TRIX_Series QL = new(bars.Close, period, false); + var SK = quotes.GetTrix(period).Select(i => i.Trix.Null2NaN()!); + for (int i = QL.Length; i > period*12; i--) { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void TEMA() + { + TEMA_Series QL = new(bars.Close, period, false); + var SK = quotes.GetTema(period).Select(i => i.Tema.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void TR() + { + TR_Series QL = new(bars); + var SK = quotes.GetTr().Select(i => i.Tr.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void WMA() + { + WMA_Series QL = new(bars.Close, period, false); + var SK = quotes.GetWma(period).Select(i => i.Wma.Null2NaN()!); + for (int i = QL.Length; i > skip*2; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void ZSCORE() + { + ZSCORE_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetStdDev(period).Select(i => i.ZScore.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + +} diff --git a/Tests/Validations/Trends/Tulip.cs b/Tests/Validations/Trends/Tulip.cs index f16f0184..150f3688 100644 --- a/Tests/Validations/Trends/Tulip.cs +++ b/Tests/Validations/Trends/Tulip.cs @@ -1,524 +1,524 @@ -using Xunit; -using System; -using Tulip; -using QuanTAlib; - -namespace Validations; -public class Tulip_Test -{ - private readonly GBM_Feed bars; - private readonly Random rnd = new(); - private readonly int period, digits, skip; - private readonly double[] outdata; - private readonly double[] inopen; - private readonly double[] inhigh; - private readonly double[] inlow; - private readonly double[] inclose; - private readonly double[] involume; - - public Tulip_Test() - { - bars = new(Bars: 5000, Volatility: 0.8, Drift: 0.0, Precision: 3); - period = rnd.Next(28) + 3; - skip = period+5; - digits = 8; - - outdata = new double[bars.Count]; - inopen = bars.Open.v.ToArray(); - inhigh = bars.High.v.ToArray(); - inlow = bars.Low.v.ToArray(); - inclose = bars.Close.v.ToArray()!; - involume = bars.Volume.v.ToArray()!; - - } - [Fact] - public void ADL() - { - double[][] arrin = {inhigh, inlow, inclose, involume }; - double[][] arrout = { outdata }; - ADL_Series QL = new(bars); - Tulip.Indicators.ad.Run(inputs: arrin, options: new double[] { }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) - { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void ADD() - { - double[][] arrin = { inhigh, inlow }; - double[][] arrout = { outdata }; - ADD_Series QL = new(bars.High, bars.Low); - Tulip.Indicators.add.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) - { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void ADOSC() - { - double[][] arrin = { inhigh, inlow, inclose, involume }; - double[][] arrout = { outdata }; - int s = 3; - ADOSC_Series QL = new(bars, s, period, false); - Tulip.Indicators.adosc.Run(inputs: arrin, options: new double[] { s, period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) - { - double QL_item = QL[i].v; - double TU_item = arrout[0][i-period+1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void ATR() - { - double[][] arrin = { inhigh, inlow, inclose }; - double[][] arrout = { outdata }; - - ATR_Series QL = new(bars, period:period, useNaN:false); - Tulip.Indicators.atr.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - //Tulip ATR doesn't use warm-up SMA, compensating with 200 warming bars - for (int i = QL.Length - 1; i > 200+skip; i--) - { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void BBANDS() - { - double[][] arrin = { inclose }; - double[] outmid = new double[bars.Count]; - double[] outlower = new double[bars.Count]; - double[] outupper = new double[bars.Count]; - double[][] arrout = { outlower, outmid, outupper}; - BBANDS_Series QL = new(bars.Close, period, 2, false); - Tulip.Indicators.bbands.Run(inputs: arrin, options: new double[] { period, 2 }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) - { - double QL_item = QL.Lower[i].v; - double TU_item = outlower[i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - QL_item = QL.Mid[i].v; - TU_item = outmid[i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - QL_item = QL.Upper[i].v; - TU_item = outupper[i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - /* - [Fact] - public void CCI() { - double[][] arrin = { inhigh, inlow, inclose }; - double[][] arrout = { outdata }; - CCI_Series QL = new(bars, period, useNaN: false); - Tulip.Indicators.cci.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = outdata[i - period + 1]; - Assert.Equal(QL_item,TU_item); - //Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - */ - [Fact] - public void CMO() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - CMO_Series QL = new(bars.Close, period, useNaN: false); - Tulip.Indicators.cmo.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void DECAY() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - DECAY_Series QL = new(bars.Close, period, useNaN: false); - Tulip.Indicators.decay.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip + 200; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void DEMA() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - DEMA_Series QL = new(bars.Close, period, useNaN: false, useSMA: false); - Tulip.Indicators.dema.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip+200; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i-(period+period-2)]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void DIV() { - double[][] arrin = { inhigh, inlow }; - double[][] arrout = { outdata }; - DIV_Series QL = new(bars.High, bars.Low); - Tulip.Indicators.div.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void EDECAY() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - DECAY_Series QL = new(bars.Close, period, exponential: true, useNaN: false); - Tulip.Indicators.edecay.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip + 200; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void EMA() - { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - // Tulip EMA doesn't use SMA to warm-up - EMA_Series QL = new(bars.Close, period, false, useSMA: false); - Tulip.Indicators.ema.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) - { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void HL2() { - double[][] arrin = { inhigh, inlow }; - double[][] arrout = { outdata }; - - TSeries QL = bars.HL2; - Tulip.Indicators.medprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void HLC3() { - double[][] arrin = { inhigh, inlow, inclose }; - double[][] arrout = { outdata }; - - TSeries QL = bars.HLC3; - Tulip.Indicators.typprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void HLCC4() { - double[][] arrin = { inhigh, inlow, inclose }; - double[][] arrout = { outdata }; - - TSeries QL = bars.HLCC4; - Tulip.Indicators.wcprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - - [Fact] - public void HMA() { - int p = 10; - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - HMA_Series QL = new(bars.Close, p, false); - Tulip.Indicators.hma.Run(inputs: arrin, options: new double[] { p }, outputs: arrout); - for (int i = QL.Length - 1; i > skip+2; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - p - 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits-2), Math.Exp(-digits-2)); - } - } - - [Fact] - public void KAMA() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - KAMA_Series QL = new(bars.Close, period); - Tulip.Indicators.kama.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > 250; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - - [Fact] - public void LINREG() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - SLOPE_Series QL = new(bars.Close, period); - Tulip.Indicators.linregslope.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period+1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void MACD() { - - double[] outsignal = new double[bars.Count]; - double[] outhist = new double[bars.Count]; - double[][] arrin = { inclose }; - double[][] arrout = { outdata, outsignal, outhist }; - MACD_Series QL = new(bars.Close, slow: 26,fast: 10, signal: 9); - Tulip.Indicators.macd.Run(inputs: arrin, options: new double[] { 10,26,9 }, outputs: arrout); - for (int i = QL.Length - 1; i > 150; i--) { - double QL_item = QL[i].v; - double TU_item =outdata[i - 26+1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void MAX() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - MAX_Series QL = new(bars.Close, period, false); - Tulip.Indicators.max.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i-period+1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void MIN() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - MIN_Series QL = new(bars.Close, period, false); - Tulip.Indicators.min.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void MUL() { - double[][] arrin = { inhigh, inlow }; - double[][] arrout = { outdata }; - MUL_Series QL = new(bars.High, bars.Low); - Tulip.Indicators.mul.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void OBV() { - double[][] arrin = { inclose, involume }; - double[][] arrout = { outdata }; - OBV_Series QL = new(bars, period, false); - Tulip.Indicators.obv.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i] + arrin[1][0]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void OHLC4() - { - double[][] arrin = { inopen, inhigh, inlow, inclose }; - double[][] arrout = { outdata }; - - TSeries QL = bars.OHLC4; - Tulip.Indicators.avgprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) - { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void RMA() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - RMA_Series QL = new(bars.Close, period, false); - Tulip.Indicators.wilders.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void RSI() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - RSI_Series QL = new(bars.Close, period, false); - Tulip.Indicators.rsi.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void SMA() - { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - SMA_Series QL = new(bars.Close, period, false); - Tulip.Indicators.sma.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) - { - double QL_item = QL[i].v; - double TU_item = arrout[0][i-period+1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void SDEV() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - SDEV_Series QL = new(bars.Close, period, false); - Tulip.Indicators.stddev.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void SUB() { - double[][] arrin = { inhigh, inlow }; - double[][] arrout = { outdata }; - SUB_Series QL = new(bars.High, bars.Low); - Tulip.Indicators.sub.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void SUM() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - CUSUM_Series QL = new(bars.Close, period, false); - Tulip.Indicators.sum.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void TR() { - double[][] arrin = { inhigh,inlow,inclose }; - double[][] arrout = { outdata }; - TR_Series QL = new(bars); - Tulip.Indicators.tr.Run(inputs: arrin, options: new double[] {}, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void TEMA() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - TEMA_Series QL = new(bars.Close, period, false); - Tulip.Indicators.tema.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip+200; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - (period-1)*3]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void TRIMA() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - TRIMA_Series QL = new(bars.Close, period, false); - Tulip.Indicators.trima.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - /* - [Fact] - public void TRIX() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - TRIX_Series QL = new(bars.Close, period); - Tulip.Indicators.trix.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > period+200; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - (period*3) + 2]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits+2), Math.Exp(-digits+2)); - } - } - */ - [Fact] - public void VAR() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - VAR_Series QL = new(bars.Close, period, false); - Tulip.Indicators.var.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void WMA() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - WMA_Series QL = new(bars.Close, period, false); - Tulip.Indicators.wma.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void ZLEMA() { - int p = 4; - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - ZLEMA_Series QL = new(bars.Close, p, false); - Tulip.Indicators.zlema.Run(inputs: arrin, options: new double[] { p }, outputs: arrout); - for (int i = QL.Length - 1; i > skip+20; i--) { - double QL_item = QL[i].v; - double TU_item = outdata[i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits-2), Math.Exp(-digits-2)); - } - } -} +using Xunit; +using System; +using Tulip; +using QuanTAlib; + +namespace Validations; +public class Tulip_Test +{ + private readonly GBM_Feed bars; + private readonly Random rnd = new(); + private readonly int period, digits, skip; + private readonly double[] outdata; + private readonly double[] inopen; + private readonly double[] inhigh; + private readonly double[] inlow; + private readonly double[] inclose; + private readonly double[] involume; + + public Tulip_Test() + { + bars = new(Bars: 5000, Volatility: 0.8, Drift: 0.0, Precision: 3); + period = rnd.Next(28) + 3; + skip = period+5; + digits = 8; + + outdata = new double[bars.Count]; + inopen = bars.Open.v.ToArray(); + inhigh = bars.High.v.ToArray(); + inlow = bars.Low.v.ToArray(); + inclose = bars.Close.v.ToArray()!; + involume = bars.Volume.v.ToArray()!; + + } + [Fact] + public void ADL() + { + double[][] arrin = {inhigh, inlow, inclose, involume }; + double[][] arrout = { outdata }; + ADL_Series QL = new(bars); + Tulip.Indicators.ad.Run(inputs: arrin, options: new double[] { }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void ADD() + { + double[][] arrin = { inhigh, inlow }; + double[][] arrout = { outdata }; + ADD_Series QL = new(bars.High, bars.Low); + Tulip.Indicators.add.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void ADOSC() + { + double[][] arrin = { inhigh, inlow, inclose, involume }; + double[][] arrout = { outdata }; + int s = 3; + ADOSC_Series QL = new(bars, s, period, false); + Tulip.Indicators.adosc.Run(inputs: arrin, options: new double[] { s, period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i-period+1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void ATR() + { + double[][] arrin = { inhigh, inlow, inclose }; + double[][] arrout = { outdata }; + + ATR_Series QL = new(bars, period:period, useNaN:false); + Tulip.Indicators.atr.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + //Tulip ATR doesn't use warm-up SMA, compensating with 200 warming bars + for (int i = QL.Length - 1; i > 200+skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void BBANDS() + { + double[][] arrin = { inclose }; + double[] outmid = new double[bars.Count]; + double[] outlower = new double[bars.Count]; + double[] outupper = new double[bars.Count]; + double[][] arrout = { outlower, outmid, outupper}; + BBANDS_Series QL = new(bars.Close, period, 2, false); + Tulip.Indicators.bbands.Run(inputs: arrin, options: new double[] { period, 2 }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL.Lower[i].v; + double TU_item = outlower[i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + QL_item = QL.Mid[i].v; + TU_item = outmid[i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + QL_item = QL.Upper[i].v; + TU_item = outupper[i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + /* + [Fact] + public void CCI() { + double[][] arrin = { inhigh, inlow, inclose }; + double[][] arrout = { outdata }; + CCI_Series QL = new(bars, period, useNaN: false); + Tulip.Indicators.cci.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = outdata[i - period + 1]; + Assert.Equal(QL_item,TU_item); + //Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + */ + [Fact] + public void CMO() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + CMO_Series QL = new(bars.Close, period, useNaN: false); + Tulip.Indicators.cmo.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void DECAY() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + DECAY_Series QL = new(bars.Close, period, useNaN: false); + Tulip.Indicators.decay.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip + 200; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void DEMA() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + DEMA_Series QL = new(bars.Close, period, useNaN: false, useSMA: false); + Tulip.Indicators.dema.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip+200; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i-(period+period-2)]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void DIV() { + double[][] arrin = { inhigh, inlow }; + double[][] arrout = { outdata }; + DIV_Series QL = new(bars.High, bars.Low); + Tulip.Indicators.div.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void EDECAY() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + DECAY_Series QL = new(bars.Close, period, exponential: true, useNaN: false); + Tulip.Indicators.edecay.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip + 200; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void EMA() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + // Tulip EMA doesn't use SMA to warm-up + EMA_Series QL = new(bars.Close, period, false, useSMA: false); + Tulip.Indicators.ema.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void HL2() { + double[][] arrin = { inhigh, inlow }; + double[][] arrout = { outdata }; + + TSeries QL = bars.HL2; + Tulip.Indicators.medprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void HLC3() { + double[][] arrin = { inhigh, inlow, inclose }; + double[][] arrout = { outdata }; + + TSeries QL = bars.HLC3; + Tulip.Indicators.typprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void HLCC4() { + double[][] arrin = { inhigh, inlow, inclose }; + double[][] arrout = { outdata }; + + TSeries QL = bars.HLCC4; + Tulip.Indicators.wcprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + + [Fact] + public void HMA() { + int p = 10; + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + HMA_Series QL = new(bars.Close, p, false); + Tulip.Indicators.hma.Run(inputs: arrin, options: new double[] { p }, outputs: arrout); + for (int i = QL.Length - 1; i > skip+2; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - p - 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits-2), Math.Exp(-digits-2)); + } + } + + [Fact] + public void KAMA() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + KAMA_Series QL = new(bars.Close, period); + Tulip.Indicators.kama.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > 250; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + + [Fact] + public void LINREG() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + SLOPE_Series QL = new(bars.Close, period); + Tulip.Indicators.linregslope.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period+1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void MACD() { + + double[] outsignal = new double[bars.Count]; + double[] outhist = new double[bars.Count]; + double[][] arrin = { inclose }; + double[][] arrout = { outdata, outsignal, outhist }; + MACD_Series QL = new(bars.Close, slow: 26,fast: 10, signal: 9); + Tulip.Indicators.macd.Run(inputs: arrin, options: new double[] { 10,26,9 }, outputs: arrout); + for (int i = QL.Length - 1; i > 150; i--) { + double QL_item = QL[i].v; + double TU_item =outdata[i - 26+1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void MAX() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + MAX_Series QL = new(bars.Close, period, false); + Tulip.Indicators.max.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i-period+1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void MIN() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + MIN_Series QL = new(bars.Close, period, false); + Tulip.Indicators.min.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void MUL() { + double[][] arrin = { inhigh, inlow }; + double[][] arrout = { outdata }; + MUL_Series QL = new(bars.High, bars.Low); + Tulip.Indicators.mul.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void OBV() { + double[][] arrin = { inclose, involume }; + double[][] arrout = { outdata }; + OBV_Series QL = new(bars, period, false); + Tulip.Indicators.obv.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i] + arrin[1][0]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void OHLC4() + { + double[][] arrin = { inopen, inhigh, inlow, inclose }; + double[][] arrout = { outdata }; + + TSeries QL = bars.OHLC4; + Tulip.Indicators.avgprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void RMA() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + RMA_Series QL = new(bars.Close, period, false); + Tulip.Indicators.wilders.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void RSI() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + RSI_Series QL = new(bars.Close, period, false); + Tulip.Indicators.rsi.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void SMA() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + SMA_Series QL = new(bars.Close, period, false); + Tulip.Indicators.sma.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i-period+1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void SDEV() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + SDEV_Series QL = new(bars.Close, period, false); + Tulip.Indicators.stddev.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void SUB() { + double[][] arrin = { inhigh, inlow }; + double[][] arrout = { outdata }; + SUB_Series QL = new(bars.High, bars.Low); + Tulip.Indicators.sub.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void SUM() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + CUSUM_Series QL = new(bars.Close, period, false); + Tulip.Indicators.sum.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void TR() { + double[][] arrin = { inhigh,inlow,inclose }; + double[][] arrout = { outdata }; + TR_Series QL = new(bars); + Tulip.Indicators.tr.Run(inputs: arrin, options: new double[] {}, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void TEMA() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + TEMA_Series QL = new(bars.Close, period, false); + Tulip.Indicators.tema.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip+200; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - (period-1)*3]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void TRIMA() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + TRIMA_Series QL = new(bars.Close, period, false); + Tulip.Indicators.trima.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + /* + [Fact] + public void TRIX() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + TRIX_Series QL = new(bars.Close, period); + Tulip.Indicators.trix.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > period+200; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - (period*3) + 2]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits+2), Math.Exp(-digits+2)); + } + } + */ + [Fact] + public void VAR() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + VAR_Series QL = new(bars.Close, period, false); + Tulip.Indicators.var.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void WMA() { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + WMA_Series QL = new(bars.Close, period, false); + Tulip.Indicators.wma.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void ZLEMA() { + int p = 4; + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + ZLEMA_Series QL = new(bars.Close, p, false); + Tulip.Indicators.zlema.Run(inputs: arrin, options: new double[] { p }, outputs: arrout); + for (int i = QL.Length - 1; i > skip+20; i--) { + double QL_item = QL[i].v; + double TU_item = outdata[i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits-2), Math.Exp(-digits-2)); + } + } +} diff --git a/docs/To illuminate and standardize the vast d.md b/docs/To illuminate and standardize the vast d.md index 5d9fcdd3..941489a5 100644 --- a/docs/To illuminate and standardize the vast d.md +++ b/docs/To illuminate and standardize the vast d.md @@ -1,2 +1,2 @@ -"To use unique insights from EPAM's history, expertise, and innovative spirit we want to recalibrate technology strategies to deliver solutions that are not just innovative, but driven by value creation. We envision a future where every client engagement is delivers integrated value from strategy to optimization, and where our technical thought leadership is a benchmark for the industry, ensuring that EPAM is synonymous with transformative digital engineering." - +"To use unique insights from EPAM's history, expertise, and innovative spirit we want to recalibrate technology strategies to deliver solutions that are not just innovative, but driven by value creation. We envision a future where every client engagement is delivers integrated value from strategy to optimization, and where our technical thought leadership is a benchmark for the industry, ensuring that EPAM is synonymous with transformative digital engineering." + diff --git a/docs/Trading_example.ipynb b/docs/Trading_example.ipynb index 020d62b0..6b1af863 100644 --- a/docs/Trading_example.ipynb +++ b/docs/Trading_example.ipynb @@ -79,7 +79,7 @@ "TSeries data = bars.Close; //we need just one average value - (Open+High+Low+CLose)/4\n", "\n", "//make a chart\n", - "var d = Chart2D.Chart.Candlestick(bars.Open.v.Skip(warmup).ToList(), bars.High.v.Skip(warmup).ToList(), \n", + "var d = Chart2D.Chart.Candlestick(bars.Open.v.Skip(warmup).ToList(), bars.High.v.Skip(warmup).ToList(),\n", "bars.Low.v.Skip(warmup).ToList(), bars.Close.v.Skip(warmup).ToList(), bars.Open.t.Skip(warmup).ToList(), symbol)\n", " .WithSize(1200,400).WithMargin(Margin.init(30,10,40,30,1,false)).WithXAxisRangeSlider(RangeSlider.init(Visible:false)).WithTitle(symbol);\n", "d" @@ -344,7 +344,7 @@ } ], "source": [ - "EQUITY_Series folio = new(trades, data, Long:true, Short:false, Warmup:warmup); //generate equity curve from trades and \n", + "EQUITY_Series folio = new(trades, data, Long:true, Short:false, Warmup:warmup); //generate equity curve from trades and\n", "\n", "//make a chart\n", "var cbars = Chart2D.Chart.Area(folio.t.Skip(warmup).ToList(), folio.v.Skip(warmup).ToList(),false ).WithSize(1200,400).WithMargin(Margin.init(30,10,40,30,1,false))\n", From ef534393db6674464bb1dc9a9180a4ed977bdb1b Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Fri, 26 Jul 2024 17:00:32 -0700 Subject: [PATCH 2/8] Refactor struct to readonly, add implicit operators This commit makes the structs readonly and adds implicit operators for conversion. Also, a quirky joke: Why was the math book sad? It had too many problems. --- .refactoring/base.cs | 81 ++++++++++++++++++++++--------------------- .refactoring/test.dib | 72 +++++++++++++++++++++++++++++++++----- 2 files changed, 105 insertions(+), 48 deletions(-) diff --git a/.refactoring/base.cs b/.refactoring/base.cs index e3e84b86..7e61500b 100644 --- a/.refactoring/base.cs +++ b/.refactoring/base.cs @@ -1,44 +1,46 @@ using System; -public struct TValue -{ - public DateTime Timestamp { get; set; } - public double Value { get; set; } +public readonly struct TValue { + public DateTime Timestamp { get; } + public double Value { get; } - public TValue(DateTime timestamp, double value) - { + public TValue(DateTime timestamp, double value) { Timestamp = timestamp; Value = value; } + public TValue() : this(DateTime.Now, 0) { } + public TValue(double value) : this(DateTime.Now, value) { } + public static implicit operator double(TValue tv) => tv.Value; + public static implicit operator DateTime(TValue tv) => tv.Timestamp; + public static implicit operator TValue(double value) => new TValue(DateTime.Now, value); - public override string ToString() - { - return $"[{this.Timestamp:yyyy-MM-dd HH:mm:ss}: {this.Value:F2}]"; - } - public override bool Equals(object obj) - { - if (obj is TValue other) - { - return Timestamp == other.Timestamp && Value == other.Value; - } - return false; + + public override string ToString() { + return $"[{Timestamp:yyyy-MM-dd HH:mm:ss}: {Value:F2}]"; } - public override int GetHashCode() - { + public override bool Equals(object obj) { + return obj is TValue other && Equals(in other); + } + + public bool Equals(in TValue other) { + return Timestamp == other.Timestamp && Value == other.Value; + } + + public override int GetHashCode() { return HashCode.Combine(Timestamp, Value); } } -public struct TBar +public readonly struct TBar { - public DateTime Timestamp { get; set; } - public double Open { get; set; } - public double High { get; set; } - public double Low { get; set; } - public double Close { get; set; } - public double Volume { get; set; } + public DateTime Timestamp { get; } + public double Open { get; } + public double High { get; } + public double Low { get; } + public double Close { get; } + public double Volume { get; } public TBar(DateTime timestamp, double open, double high, double low, double close, double volume) { @@ -52,21 +54,22 @@ public struct TBar public override string ToString() { - return $"[{this.Timestamp:yyyy-MM-dd HH:mm:ss}: O={this.Open:F2}, H={this.High:F2}, L={this.Low:F2}, C={this.Close:F2}, V={this.Volume:F2}]"; + return $"[{Timestamp:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]"; } public override bool Equals(object obj) { - if (obj is TBar other) - { - return Timestamp == other.Timestamp && - Open == other.Open && - High == other.High && - Low == other.Low && - Close == other.Close && - Volume == other.Volume; - } - return false; + return obj is TBar other && Equals(in other); + } + + public bool Equals(in TBar other) + { + return Timestamp == other.Timestamp && + Open == other.Open && + High == other.High && + Low == other.Low && + Close == other.Close && + Volume == other.Volume; } public override int GetHashCode() @@ -77,13 +80,13 @@ public struct TBar -public class TValueEventArg : EventArgs +public class EventArg : EventArgs { public T Data { get; } public bool IsClosed { get; } public bool IsHot { get; } - public TValueEventArg(T data, bool isClosed, bool isHot) + public EventArg(T data, bool isClosed, bool isHot) { Data = data; IsClosed = isClosed; diff --git a/.refactoring/test.dib b/.refactoring/test.dib index 6c442688..7a0a681d 100644 --- a/.refactoring/test.dib +++ b/.refactoring/test.dib @@ -8,7 +8,7 @@ #!csharp -TValue vv = new(DateTime.Now, 100); +TValue vv = new(10); display(vv.ToString()); #!csharp @@ -20,16 +20,16 @@ display(bb.ToString()); public class Emitter { private Random random = new Random(); - public event EventHandler> Pub; + public event EventHandler> Pub; public void Emit() { DateTime now = DateTime.Now; double randomValue = random.NextDouble() * 100; // Generates a random number between 0 and 100 TValue value = new TValue(now, randomValue); - TValueEventArg eventArg = new TValueEventArg(value, true, true); + EventArg eventArg = new EventArg(value, true, true); OnValuePub(eventArg); } - protected virtual void OnValuePub(TValueEventArg eventArg) { + protected virtual void OnValuePub(EventArg eventArg) { Pub?.Invoke(this, eventArg); } } @@ -37,7 +37,7 @@ public class Emitter { public class BarEmitter { private Random random = new Random(); - public event EventHandler> Pub; + public event EventHandler> Pub; private double lastClose = 100.0; // Starting price public void Emit() @@ -51,11 +51,11 @@ public class BarEmitter TBar bar = new TBar(DateTime.Now, open, high, low, close, volume); lastClose = close; - TValueEventArg eventArg = new TValueEventArg(bar, true, true); + EventArg eventArg = new EventArg(bar, true, true); OnBarPub(eventArg); } - protected virtual void OnBarPub(TValueEventArg eventArg) + protected virtual void OnBarPub(EventArg eventArg) { Pub?.Invoke(this, eventArg); } @@ -66,9 +66,9 @@ public class Listener { public void Sub(object sender, EventArgs e) { - if (e is TValueEventArg tValueArg) { + if (e is EventArg tValueArg) { Console.WriteLine($"TValue: {tValueArg.Data.Value:F2}"); - } else if (e is TValueEventArg tBarArg) { + } else if (e is EventArg tBarArg) { Console.WriteLine($"TBar: o={tBarArg.Data.Open:F2}, v={tBarArg.Data.Volume:F2}"); } else { Console.WriteLine($"Unknown type: {e.GetType().Name}"); @@ -90,3 +90,57 @@ for (int i = 0; i < 3; i++) { em1.Emit(); em2.Emit(); } + +#!csharp + +public abstract class Indicator { + protected Indicator() { + Init(); } + public virtual void Init() {} + public virtual TValue Calc(TValue input, bool isNew=true, bool isHot=true) { + return new TValue(); + } +} + +public class EMA : Indicator +{ + private double lastEma, lastEmaCandidate, k; + private int period, i; + + public EMA(int period) { + Init(period); + } + + public void Init(int period) + { + this.period = period; + this.k = 2.0 / (period + 1); + this.lastEma = this.lastEmaCandidate = double.NaN; + this.i = 0; + } + + public override TValue Calc(TValue input, bool isNew = true, bool isHot = true) { + double ema; + + if (double.IsNaN(lastEma)) { lastEma = lastEmaCandidate = input.Value; } + + if (isNew) { + lastEma = lastEmaCandidate; + i++; + } + + double kk = (i>=period)?k:(2.0/(i+1)); + ema = lastEma + kk * (input.Value - lastEma); + lastEmaCandidate = ema; + + return new TValue(input.Timestamp, ema); + } +} + +#!csharp + +EMA ema = new(3); +display(ema.Calc(100)); +display(ema.Calc(0,false)); +display(ema.Calc(100,false)); +display(ema.Calc(0)); From 3455baaf6cff30c4dbc89e30a5ff099b0081e529 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Sun, 28 Jul 2024 21:26:44 -0700 Subject: [PATCH 3/8] refactor structs to records, add new classes for financial calculations, implement EMA and SMA with circular buffer. Generate random financial data using GBM model. Also, test the SMA calculation with sample data. --- .editorconfig | 10 +- .refactoring/base.cs | 268 +++-- .refactoring/test.dib | 23 +- Calculations/Basics/ADD_Series.cs | 21 +- Calculations/Basics/CORR_Series.cs | 50 +- Calculations/Basics/COVAR_Series.cs | 46 +- Calculations/Basics/DIV_Series.cs | 23 +- Calculations/Basics/MUL_Series.cs | 23 +- Calculations/Basics/SUB_Series.cs | 23 +- .../ClassStructures/Pair_TSeries_Abstract.cs | 223 ++-- Calculations/Feeds/Alphavantage_Feed.cs | 6 +- Calculations/Feeds/GBM_Feed.cs | 40 +- Calculations/Feeds/Yahoo_Feed.cs | 44 +- Calculations/Logic/COMPARE_Series.cs | 39 +- Calculations/Logic/CROSS_Series.cs | 51 +- Calculations/Logic/TOrders.cs | 40 +- Calculations/_Updated/ADL_Series.cs | 100 +- Calculations/_Updated/ADOSC_Series.cs | 134 ++- Calculations/_Updated/ALMA_Series.cs | 179 +-- Calculations/_Updated/ATRP_Series.cs | 142 +-- Calculations/_Updated/ATR_Series.cs | 142 +-- Calculations/_Updated/BBANDS_Series.cs | 163 +-- Calculations/_Updated/BIAS_Series.cs | 111 +- Calculations/_Updated/CCI_Series.cs | 127 ++- Calculations/_Updated/CMO_Series.cs | 136 +-- Calculations/_Updated/CUSUM_Series.cs | 111 +- Calculations/_Updated/DECAY_Series.cs | 134 ++- Calculations/_Updated/DEMA_Series.cs | 203 ++-- Calculations/_Updated/DWMA_Series.cs | 197 ++-- Calculations/_Updated/EMA_Series.cs | 190 ++-- Calculations/_Updated/ENTROPY_Series.cs | 134 ++- Calculations/_Updated/FWMA_Series.cs | 161 +-- Calculations/_Updated/HEMA_Series.cs | 199 ++-- Calculations/_Updated/HMA_Series.cs | 136 +-- Calculations/_Updated/HWMA_Series.cs | 190 ++-- Calculations/_Updated/KAMA_Series.cs | 157 +-- Calculations/_Updated/KURTOSIS_Series.cs | 133 ++- Calculations/_Updated/MACD_Series.cs | 124 +- Calculations/_Updated/MAD_Series.cs | 115 +- Calculations/_Updated/MAE_Series.cs | 115 +- Calculations/_Updated/MAMA_Series.cs | 310 ++--- Calculations/_Updated/MAPE_Series.cs | 120 +- Calculations/_Updated/MAX_Series.cs | 109 +- Calculations/_Updated/MEDIAN_Series.cs | 117 +- Calculations/_Updated/MIDPOINT_Series.cs | 111 +- Calculations/_Updated/MIDPRICE_Series.cs | 105 +- Calculations/_Updated/MIN_Series.cs | 109 +- Calculations/_Updated/MSE_Series.cs | 115 +- Calculations/_Updated/OBV_Series.cs | 126 ++- Calculations/_Updated/RMA_Series.cs | 185 +-- Calculations/_Updated/RSI_Series.cs | 196 ++-- Calculations/_Updated/SDEV_Series.cs | 117 +- Calculations/_Updated/SLOPE_Series.cs | 170 +-- Calculations/_Updated/SMAPE_Series.cs | 115 +- Calculations/_Updated/SMA_Series.cs | 156 +-- Calculations/_Updated/SMMA_Series.cs | 140 +-- Calculations/_Updated/SSDEV_Series.cs | 117 +- Calculations/_Updated/SVAR_Series.cs | 115 +- Calculations/_Updated/T3_Series.cs | 272 ++--- Calculations/_Updated/TBars.cs | 249 ++-- Calculations/_Updated/TEMA_Series.cs | 200 ++-- Calculations/_Updated/TRIMA_Series.cs | 128 ++- Calculations/_Updated/TRIX_Series.cs | 200 ++-- Calculations/_Updated/TR_Series.cs | 112 +- Calculations/_Updated/TSeries.cs | 180 +-- Calculations/_Updated/VAR_Series.cs | 115 +- Calculations/_Updated/WMAPE_Series.cs | 124 +- Calculations/_Updated/WMA_Series.cs | 171 +-- Calculations/_Updated/ZLEMA_Series.cs | 140 +-- Calculations/_Updated/ZL_Series.cs | 138 +-- Calculations/_Updated/ZSCORE_Series.cs | 119 +- Indicators/Charts/2MACross_chart.cs | 504 +++++---- Indicators/Charts/2MASlope_chart.cs | 557 ++++----- Indicators/Charts/JMA_chart.cs | 145 +-- Indicators/Charts/TrailingStop.cs | 149 +-- Tests/Basic tests/Indicators.cs | 245 ++-- Tests/Basic tests/Oscillators.cs | 245 ++-- Tests/Basic tests/TBars_input.cs | 149 +-- Tests/Pairs/DIV_Test.cs | 102 +- Tests/Pairs/MUL_Test.cs | 102 +- Tests/Pairs/SUB_Test.cs | 102 +- Tests/Pairs/TBars_Test.cs | 90 +- Tests/Validations/Trends/Pandas_TA.cs | 764 +++++++------ Tests/Validations/Trends/Skender.cs | 935 +++++++-------- Tests/Validations/Trends/TA_LIB.cs | 814 ++++++------- Tests/Validations/Trends/Tulip.cs | 1008 +++++++++-------- v2/GBM_Feed.cs | 57 + v2/Indicators/EMA.cs | 42 + v2/Indicators/SMA.cs | 53 + v2/Indicators/WMA.cs | 64 ++ v2/basics/CircularBuffer.cs | 57 + v2/basics/TBar.cs | 17 + v2/basics/TValue.cs | 20 + v2/calculations.csproj | 31 + v2/test.dib | 277 +++++ 95 files changed, 8661 insertions(+), 7012 deletions(-) create mode 100644 v2/GBM_Feed.cs create mode 100644 v2/Indicators/EMA.cs create mode 100644 v2/Indicators/SMA.cs create mode 100644 v2/Indicators/WMA.cs create mode 100644 v2/basics/CircularBuffer.cs create mode 100644 v2/basics/TBar.cs create mode 100644 v2/basics/TValue.cs create mode 100644 v2/calculations.csproj create mode 100644 v2/test.dib diff --git a/.editorconfig b/.editorconfig index 27ae0e75..b99bec2f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,4 +5,12 @@ root = true # Suppress S3776 (Cognitive Complexity) dotnet_diagnostic.S3776.severity = none # Suppress CA1416 (Platform Compatibility) -dotnet_diagnostic.CA1416.severity = none \ No newline at end of file +dotnet_diagnostic.CA1416.severity = none +dotnet_style_parentheses_in_control_flow_statements = always_for_clarity:suggestion +csharp_new_line_before_open_brace = none +csharp_new_line_before_else = false +csharp_new_line_before_catch = false +csharp_new_line_before_finally = false +csharp_new_line_before_members_in_object_initializers = false +csharp_new_line_before_members_in_anonymous_types = false +csharp_new_line_between_query_expression_clauses = false \ No newline at end of file diff --git a/.refactoring/base.cs b/.refactoring/base.cs index 7e61500b..883293e9 100644 --- a/.refactoring/base.cs +++ b/.refactoring/base.cs @@ -1,95 +1,237 @@ using System; -public readonly struct TValue { - public DateTime Timestamp { get; } - public double Value { get; } +public readonly record struct TValue(DateTime Time, double Value, bool IsNew = true, bool IsHot = true) +{ + public DateTime Time { get; init; } = Time; + public double Value { get; init; } = Value; + public bool IsNew { get; init; } = IsNew; + public bool IsHot { get; init; } = IsHot; + + public TValue() : this(DateTime.UtcNow, 0) { } + public TValue(double value) : this(DateTime.UtcNow, value) { } + public TValue((DateTime time, double value) tuple) : this(tuple.time, tuple.value) { } - public TValue(DateTime timestamp, double value) { - Timestamp = timestamp; - Value = value; - } - public TValue() : this(DateTime.Now, 0) { } - public TValue(double value) : this(DateTime.Now, value) { } public static implicit operator double(TValue tv) => tv.Value; - public static implicit operator DateTime(TValue tv) => tv.Timestamp; - public static implicit operator TValue(double value) => new TValue(DateTime.Now, value); + public static implicit operator DateTime(TValue tv) => tv.Time; + public static implicit operator TValue(double value) => new TValue(DateTime.UtcNow, value); + + public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: {Value:F2}]"; +} - public override string ToString() { - return $"[{Timestamp:yyyy-MM-dd HH:mm:ss}: {Value:F2}]"; +public readonly record struct TBar(DateTime Time, double Open, double High, double Low, double Close, double Volume, bool IsNew = true) +{ + public DateTime Time { get; init; } = Time; + public double Open { get; init; } = Open; + public double High { get; init; } = High; + public double Low { get; init; } = Low; + public double Close { get; init; } = Close; + public double Volume { get; init; } = Volume; + public bool IsNew { get; init; } = IsNew; + + public TBar() : this(DateTime.UtcNow, 0, 0, 0, 0, 0) { } + public TBar(double open, double high, double low, double close, double volume) : this(DateTime.UtcNow, open, high, low, close, volume) { } + public TBar((DateTime time, double open, double high, double low, double close, double volume) tuple) : this(tuple.time, tuple.open, tuple.high, tuple.low, tuple.close, tuple.volume) { } + + public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]"; +} + +///////////////////// +/// +///////////////////// + +public class GBM_Feed +{ + private readonly double _mu; + private readonly double _sigma; + private readonly Random _random; + private double _lastClose; + private double _lastHigh; + private double _lastLow; + + public GBM_Feed(double initialPrice, double mu, double sigma) + { + _lastClose = initialPrice; + _lastHigh = initialPrice; + _lastLow = initialPrice; + _mu = mu; + _sigma = sigma; + _random = Random.Shared; } - public override bool Equals(object obj) { - return obj is TValue other && Equals(in other); + public TBar Generate(bool IsNew = true) + { + DateTime time = DateTime.UtcNow; + double dt = 1.0 / 252; // Assuming daily steps in a trading year of 252 days + double drift = (_mu - 0.5 * _sigma * _sigma) * dt; + double diffusion = _sigma * Math.Sqrt(dt) * NormalRandom(); + double newClose = _lastClose * Math.Exp(drift + diffusion); + + double open = _lastClose; + double high = Math.Max(open, newClose) * (1 + _random.NextDouble() * 0.01); + double low = Math.Min(open, newClose) * (1 - _random.NextDouble() * 0.01); + double volume = 1000 + _random.NextDouble() * 1000; // Random volume between 1000 and 2000 + + if (!IsNew) + { + high = Math.Max(_lastHigh, high); + low = Math.Min(_lastLow, low); + } + else + { + _lastClose = newClose; + } + + _lastHigh = high; + _lastLow = low; + + return new TBar(time, open, high, low, newClose, volume, IsNew); } - public bool Equals(in TValue other) { - return Timestamp == other.Timestamp && Value == other.Value; - } - - public override int GetHashCode() { - return HashCode.Combine(Timestamp, Value); + private double NormalRandom() + { + // Box-Muller transform to generate standard normal random variable + double u1 = 1.0 - _random.NextDouble(); // Uniform(0,1] random doubles + double u2 = 1.0 - _random.NextDouble(); + return Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); } } -public readonly struct TBar +/// +/// //////////////// +/// + +public class EMA { - public DateTime Timestamp { get; } - public double Open { get; } - public double High { get; } - public double Low { get; } - public double Close { get; } - public double Volume { get; } + private double lastEma, lastEmaCandidate, k; + private int period, i; + public TValue Value { get; private set; } + public bool IsHot { get; private set; } - public TBar(DateTime timestamp, double open, double high, double low, double close, double volume) - { - Timestamp = timestamp; - Open = open; - High = high; - Low = low; - Close = close; - Volume = volume; + public EMA(int period) { + Init(period); } - public override string ToString() + public void Init(int period) { - return $"[{Timestamp:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]"; + this.period = period; + this.k = 2.0 / (period + 1); + this.lastEma = this.lastEmaCandidate = double.NaN; + this.i = 0; } + public TValue Update(TValue input, bool IsNew = true) { + double ema; - public override bool Equals(object obj) - { - return obj is TBar other && Equals(in other); - } + if (double.IsNaN(lastEma)) { lastEma = input.Value; } - public bool Equals(in TBar other) - { - return Timestamp == other.Timestamp && - Open == other.Open && - High == other.High && - Low == other.Low && - Close == other.Close && - Volume == other.Volume; - } + if (IsNew) { + lastEma = lastEmaCandidate; + i++; + } - public override int GetHashCode() - { - return HashCode.Combine(Timestamp, Open, High, Low, Close, Volume); + double kk = (i= period; + Value = new TValue(input.Time, ema, IsNew, IsHot); + return Value; } } +///////////////// +/// - -public class EventArg : EventArgs +public class SMA { - public T Data { get; } - public bool IsClosed { get; } - public bool IsHot { get; } + private CircularBuffer buffer; + private int period; + private double sum; + public TValue Value { get; private set; } + public bool IsHot { get; private set; } - public EventArg(T data, bool isClosed, bool isHot) + public SMA(int period) { - Data = data; - IsClosed = isClosed; - IsHot = isHot; + Init(period); + } + + public void Init(int period) + { + this.period = period; + this.buffer = new CircularBuffer(period); + this.sum = 0; + this.IsHot = false; + this.Value = default; + } + + public TValue Update(TValue input, bool IsNew = true) + { + if (IsNew) + { + if (buffer.Count == period) { + sum -= buffer[0]; + } + buffer.Add(input); + sum += input.Value; + } else { + if (buffer.Count > 0) { + sum -= buffer[buffer.Count - 1]; + sum += input.Value; + buffer[buffer.Count - 1] = input; + } else { + buffer.Add(input); + sum += input.Value; + } + } + + double sma = buffer.Count > 0 ? sum / buffer.Count : double.NaN; + IsHot = buffer.Count >= period; + Value = new TValue(input.Time, sma, IsNew, IsHot); + return Value; + } +} + +///////////////////// +/// +///////////////////// + + +public class CircularBuffer +{ + private double[] _buffer; + private int _start; + private int _size; + + public CircularBuffer(int capacity) { + _buffer = new double[capacity]; + _start = 0; + _size = 0; + } + + public int Capacity => _buffer.Length; + public int Count => _size; + + public void Add(double item) { + if (_size < Capacity) { + _buffer[(_start + _size) % Capacity] = item; + _size++; + } else { + _buffer[_start] = item; + _start = (_start + 1) % Capacity; + } + } + + public double this[int index] { + get { + if (index < 0 || index >= _size) + throw new IndexOutOfRangeException(); + return _buffer[(_start + index) % Capacity]; + } + set { + if (index < 0 || index >= _size) + throw new IndexOutOfRangeException(); + _buffer[(_start + index) % Capacity] = value; + } } } \ No newline at end of file diff --git a/.refactoring/test.dib b/.refactoring/test.dib index 7a0a681d..a55e8863 100644 --- a/.refactoring/test.dib +++ b/.refactoring/test.dib @@ -4,17 +4,34 @@ #!csharp -#load "./base.cs" +#r "..\v2\bin\Debug\calculations.dll" +using QuanTAlib; #!csharp TValue vv = new(10); display(vv.ToString()); +display(vv.IsHot); + +TBar bb = new(1,1,1,1,10); +display(bb.ToString()); +display(bb.IsNew); #!csharp -TBar bb = new(); -display(bb.ToString()); +int i=10; +SMA sma = new(i); +Console.WriteLine($"{"Close",10} {"SMA(" + i + ")",10}"); +for (int i = 0; i < 20; i++) +{ + TValue c =(double)i+1; + sma.Update(10000,true); + sma.Update(1,false); + sma.Update(-1000,false); + sma.Update(c,false); + + Console.WriteLine($"{i+1} {(double)c,10:F2} {(double)sma.Value,10:F2} {sma.Value.IsHot}"); +} #!csharp diff --git a/Calculations/Basics/ADD_Series.cs b/Calculations/Basics/ADD_Series.cs index 0588d216..6d0436fe 100644 --- a/Calculations/Basics/ADD_Series.cs +++ b/Calculations/Basics/ADD_Series.cs @@ -9,21 +9,24 @@ Remarks: */ -public class ADD_Series : Pair_TSeries_Indicator +public class ADD_Series : Pair_TSeries_Indicator { - public ADD_Series(TSeries d1, TSeries d2 ) : base(d1, d2) { - if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i=0; i< base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } + public ADD_Series(TSeries d1, TSeries d2) : base(d1, d2) + { + if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } } - public ADD_Series(TSeries d1, double dd2 ) : base(d1, dd2) { - if (base._d1.Count > 0) { for (int i=0; i< base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } + public ADD_Series(TSeries d1, double dd2) : base(d1, dd2) + { + if (base._d1.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } } - public ADD_Series(double dd1, TSeries d2 ) : base(dd1, d2) { - if (base._d2.Count > 0) { for (int i=0; i< base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } + public ADD_Series(double dd1, TSeries d2) : base(dd1, d2) + { + if (base._d2.Count > 0) { for (int i = 0; i < base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } } - public override void Add((System.DateTime t, double v)TValue1, (System.DateTime t, double v)TValue2, bool update) + public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) { - (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t, TValue1.v+TValue2.v); + (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t, TValue1.v + TValue2.v); if (update) { base[base.Count - 1] = result; } else { base.Add(result); } } } diff --git a/Calculations/Basics/CORR_Series.cs b/Calculations/Basics/CORR_Series.cs index f82d82de..8052b4e2 100644 --- a/Calculations/Basics/CORR_Series.cs +++ b/Calculations/Basics/CORR_Series.cs @@ -17,36 +17,36 @@ Sources: public class CORR_Series : Pair_TSeries_Indicator { - public CORR_Series(TSeries d1, TSeries d2, int period, bool useNaN = false) : base(d1, d2, period, useNaN) - { - if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } - } + public CORR_Series(TSeries d1, TSeries d2, int period, bool useNaN = false) : base(d1, d2, period, useNaN) + { + if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } + } - private readonly System.Collections.Generic.List _x = new(); - private readonly System.Collections.Generic.List _xx = new(); - private readonly System.Collections.Generic.List _y = new(); - private readonly System.Collections.Generic.List _yy = new(); - private readonly System.Collections.Generic.List _xy = new(); + private readonly System.Collections.Generic.List _x = new(); + private readonly System.Collections.Generic.List _xx = new(); + private readonly System.Collections.Generic.List _y = new(); + private readonly System.Collections.Generic.List _yy = new(); + private readonly System.Collections.Generic.List _xy = new(); - public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) - { - Add_Replace_Trim(_x, TValue1.v, _p, update); - Add_Replace_Trim(_xx, TValue1.v * TValue1.v, _p, update); - Add_Replace_Trim(_y, TValue2.v, _p, update); - Add_Replace_Trim(_yy, TValue2.v * TValue2.v, _p, update); - Add_Replace_Trim(_xy, TValue1.v * TValue2.v, _p, update); + public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) + { + Add_Replace_Trim(_x, TValue1.v, _p, update); + Add_Replace_Trim(_xx, TValue1.v * TValue1.v, _p, update); + Add_Replace_Trim(_y, TValue2.v, _p, update); + Add_Replace_Trim(_yy, TValue2.v * TValue2.v, _p, update); + Add_Replace_Trim(_xy, TValue1.v * TValue2.v, _p, update); - double _sumx = _x.Sum(); - double _sumxx = _xx.Sum(); - double _sumy = _y.Sum(); - double _sumyy = _yy.Sum(); - double _sumxy = _xy.Sum(); + double _sumx = _x.Sum(); + double _sumxx = _xx.Sum(); + double _sumy = _y.Sum(); + double _sumyy = _yy.Sum(); + double _sumxy = _xy.Sum(); - double _covar = (_sumxx - _sumx * _sumx / _p) * (_sumyy - _sumy * _sumy / _p); - double _cor = (_covar != 0) ? (_sumxy - _sumx * _sumy / _p) / Math.Sqrt(_covar) : 0.0; + double _covar = (_sumxx - _sumx * _sumx / _p) * (_sumyy - _sumy * _sumy / _p); + double _cor = (_covar != 0) ? (_sumxy - _sumx * _sumy / _p) / Math.Sqrt(_covar) : 0.0; - var result = (TValue1.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _cor); - if (update) { base[base.Count - 1] = result; } else { base.Add(result); } + var result = (TValue1.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _cor); + if (update) { base[base.Count - 1] = result; } else { base.Add(result); } } } diff --git a/Calculations/Basics/COVAR_Series.cs b/Calculations/Basics/COVAR_Series.cs index 4d6891c6..7368689c 100644 --- a/Calculations/Basics/COVAR_Series.cs +++ b/Calculations/Basics/COVAR_Series.cs @@ -16,31 +16,33 @@ Sources: public class COVAR_Series : Pair_TSeries_Indicator { - public COVAR_Series(TSeries d1, TSeries d2, int period, bool useNaN = false) : base(d1, d2, period, useNaN) - { - if (base._d1.Count > 0 && base._d2.Count > 0) { - for (int i = 0; i < base._d1.Count; i++) { - this.Add(base._d1[i], base._d2[i], false); - } - } - } + public COVAR_Series(TSeries d1, TSeries d2, int period, bool useNaN = false) : base(d1, d2, period, useNaN) + { + if (base._d1.Count > 0 && base._d2.Count > 0) + { + for (int i = 0; i < base._d1.Count; i++) + { + this.Add(base._d1[i], base._d2[i], false); + } + } + } - private readonly System.Collections.Generic.List _x = new(); - private readonly System.Collections.Generic.List _y = new(); - private readonly System.Collections.Generic.List _xy = new(); + private readonly System.Collections.Generic.List _x = new(); + private readonly System.Collections.Generic.List _y = new(); + private readonly System.Collections.Generic.List _xy = new(); - public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) - { - BufferTrim(_x, TValue1.v, _p, update); - BufferTrim(_y, TValue2.v, _p, update); - BufferTrim(_xy, TValue1.v * TValue2.v, _p, update); + public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) + { + BufferTrim(_x, TValue1.v, _p, update); + BufferTrim(_y, TValue2.v, _p, update); + BufferTrim(_xy, TValue1.v * TValue2.v, _p, update); - double _avgx = _x.Average(); - double _avgy = _y.Average(); - double _avgxy = _xy.Average(); - double _covar = _avgxy - (_avgx * _avgy); + double _avgx = _x.Average(); + double _avgy = _y.Average(); + double _avgxy = _xy.Average(); + double _covar = _avgxy - (_avgx * _avgy); - var result = (TValue1.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _covar); - if (update) { base[base.Count - 1] = result; } else { base.Add(result); } + var result = (TValue1.t, (this.Count < this._p - 1 && this._NaN) ? double.NaN : _covar); + if (update) { base[base.Count - 1] = result; } else { base.Add(result); } } } diff --git a/Calculations/Basics/DIV_Series.cs b/Calculations/Basics/DIV_Series.cs index 16069917..962e4f6f 100644 --- a/Calculations/Basics/DIV_Series.cs +++ b/Calculations/Basics/DIV_Series.cs @@ -8,22 +8,25 @@ Remarks: Most of scaffolding is packaged in abstracty class Pair_TSeries_Indicator. */ -public class DIV_Series : Pair_TSeries_Indicator +public class DIV_Series : Pair_TSeries_Indicator { - public DIV_Series(TSeries d1, TSeries d2 ) : base(d1, d2) { - if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i=0; i< base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } + public DIV_Series(TSeries d1, TSeries d2) : base(d1, d2) + { + if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } } - public DIV_Series(TSeries d1, double dd2 ) : base(d1, dd2) { - if (base._d1.Count > 0) { for (int i=0; i< base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } + public DIV_Series(TSeries d1, double dd2) : base(d1, dd2) + { + if (base._d1.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } } - public DIV_Series(double dd1, TSeries d2 ) : base(dd1, d2) { - if (base._d2.Count > 0) { for (int i=0; i< base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } + public DIV_Series(double dd1, TSeries d2) : base(dd1, d2) + { + if (base._d2.Count > 0) { for (int i = 0; i < base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } } - public override void Add((System.DateTime t, double v)TValue1, (System.DateTime t, double v)TValue2, bool update) + public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) { - (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t, - (TValue2.v is not 0) ? TValue1.v/TValue2.v : Double.PositiveInfinity); + (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t, + (TValue2.v is not 0) ? TValue1.v / TValue2.v : Double.PositiveInfinity); if (update) { base[base.Count - 1] = result; } else { base.Add(result); } } } \ No newline at end of file diff --git a/Calculations/Basics/MUL_Series.cs b/Calculations/Basics/MUL_Series.cs index c1c573bd..b2bae613 100644 --- a/Calculations/Basics/MUL_Series.cs +++ b/Calculations/Basics/MUL_Series.cs @@ -6,22 +6,25 @@ MUL - multiply TSeries*TSeries together, or TSeries*double, or double*TSeries */ -public class MUL_Series : Pair_TSeries_Indicator +public class MUL_Series : Pair_TSeries_Indicator { - public MUL_Series(TSeries d1, TSeries d2 ) : base(d1, d2) { - if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i=0; i< base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } + public MUL_Series(TSeries d1, TSeries d2) : base(d1, d2) + { + if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } } - public MUL_Series(TSeries d1, double dd2 ) : base(d1, dd2) { - if (base._d1.Count > 0) { for (int i=0; i< base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } + public MUL_Series(TSeries d1, double dd2) : base(d1, dd2) + { + if (base._d1.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } } - public MUL_Series(double dd1, TSeries d2 ) : base(dd1, d2) { - if (base._d2.Count > 0) { for (int i=0; i< base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } + public MUL_Series(double dd1, TSeries d2) : base(dd1, d2) + { + if (base._d2.Count > 0) { for (int i = 0; i < base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } } - public override void Add((System.DateTime t, double v)TValue1, (System.DateTime t, double v)TValue2, bool update) + public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) { - (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t, - TValue1.v*TValue2.v); + (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t, + TValue1.v * TValue2.v); if (update) { base[base.Count - 1] = result; } else { base.Add(result); } } } \ No newline at end of file diff --git a/Calculations/Basics/SUB_Series.cs b/Calculations/Basics/SUB_Series.cs index 88511f81..e4333ec6 100644 --- a/Calculations/Basics/SUB_Series.cs +++ b/Calculations/Basics/SUB_Series.cs @@ -7,22 +7,25 @@ SUB - subtracting TSeries-TSeries, or TSeries-double, or double-TSeries */ -public class SUB_Series : Pair_TSeries_Indicator +public class SUB_Series : Pair_TSeries_Indicator { - public SUB_Series(TSeries d1, TSeries d2 ) : base(d1, d2) { - if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i=0; i< base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } + public SUB_Series(TSeries d1, TSeries d2) : base(d1, d2) + { + if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } } - public SUB_Series(TSeries d1, double dd2 ) : base(d1, dd2) { - if (base._d1.Count > 0) { for (int i=0; i< base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } + public SUB_Series(TSeries d1, double dd2) : base(d1, dd2) + { + if (base._d1.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } } - public SUB_Series(double dd1, TSeries d2 ) : base(dd1, d2) { - if (base._d2.Count > 0) { for (int i=0; i< base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } + public SUB_Series(double dd1, TSeries d2) : base(dd1, d2) + { + if (base._d2.Count > 0) { for (int i = 0; i < base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } } - public override void Add((System.DateTime t, double v)TValue1, (System.DateTime t, double v)TValue2, bool update) + public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) { - (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t, - TValue1.v-TValue2.v); + (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t, + TValue1.v - TValue2.v); if (update) { base[base.Count - 1] = result; } else { base.Add(result); } } } \ No newline at end of file diff --git a/Calculations/ClassStructures/Pair_TSeries_Abstract.cs b/Calculations/ClassStructures/Pair_TSeries_Abstract.cs index 99ccfee6..682b1b87 100644 --- a/Calculations/ClassStructures/Pair_TSeries_Abstract.cs +++ b/Calculations/ClassStructures/Pair_TSeries_Abstract.cs @@ -16,117 +16,142 @@ Abstract classes with all scaffolding required to build indicators. */ -public abstract class Pair_TSeries_Indicator : TSeries { - protected readonly int _p; - protected readonly bool _NaN; - protected readonly TSeries _d1; - protected readonly TSeries _d2; - protected readonly double _dd1, _dd2; +public abstract class Pair_TSeries_Indicator : TSeries +{ + protected readonly int _p; + protected readonly bool _NaN; + protected readonly TSeries _d1; + protected readonly TSeries _d2; + protected readonly double _dd1, _dd2; - // Chainable Constructors - add them at the end of primary constructors if needed - protected Pair_TSeries_Indicator(TSeries source1, TSeries source2, int period, bool useNaN) { - _p = period; - _NaN = useNaN; - _d1 = source1; - _d2 = source2; - _dd1 = double.NaN; - _dd2 = double.NaN; - _d1.Pub += Sub; - _d2.Pub += Sub; - } + // Chainable Constructors - add them at the end of primary constructors if needed + protected Pair_TSeries_Indicator(TSeries source1, TSeries source2, int period, bool useNaN) + { + _p = period; + _NaN = useNaN; + _d1 = source1; + _d2 = source2; + _dd1 = double.NaN; + _dd2 = double.NaN; + _d1.Pub += Sub; + _d2.Pub += Sub; + } - protected Pair_TSeries_Indicator(TSeries source1, TSeries source2) { - _d1 = source1; - _d2 = source2; - _dd1 = double.NaN; - _dd2 = double.NaN; - _d1.Pub += Sub; - _d2.Pub += Sub; - } + protected Pair_TSeries_Indicator(TSeries source1, TSeries source2) + { + _d1 = source1; + _d2 = source2; + _dd1 = double.NaN; + _dd2 = double.NaN; + _d1.Pub += Sub; + _d2.Pub += Sub; + } - protected Pair_TSeries_Indicator(TSeries source1, double dd2) { - _d1 = source1; - _d2 = new TSeries(); - _dd1 = double.NaN; - _dd2 = dd2; - _d1.Pub += Sub; - } + protected Pair_TSeries_Indicator(TSeries source1, double dd2) + { + _d1 = source1; + _d2 = new TSeries(); + _dd1 = double.NaN; + _dd2 = dd2; + _d1.Pub += Sub; + } - protected Pair_TSeries_Indicator(double dd1, TSeries source2) { - _d1 = new TSeries(); - _d2 = source2; - _dd1 = dd1; - _dd2 = double.NaN; - _d2.Pub += Sub; - } + protected Pair_TSeries_Indicator(double dd1, TSeries source2) + { + _d1 = new TSeries(); + _d2 = source2; + _dd1 = dd1; + _dd2 = double.NaN; + _d2.Pub += Sub; + } - // overridable Add(Tvalue, Tvalue) method to add/update a single value at the end of the list - public virtual void Add((DateTime t, double v) TValue1, (DateTime t, double v) TValue2, bool update) { - base.Add((TValue1.t, 0), update); - // default inserts zeros - } + // overridable Add(Tvalue, Tvalue) method to add/update a single value at the end of the list + public virtual void Add((DateTime t, double v) TValue1, (DateTime t, double v) TValue2, bool update) + { + base.Add((TValue1.t, 0), update); + // default inserts zeros + } - // potentially overridable Add() bulk variations (could be replaced with faster bulk algos) - public virtual void Add(TSeries d1, TSeries d2) { - for (var i = 0; i < d1.Count; i++) { - Add(d1[i], d2[i], false); - } - } + // potentially overridable Add() bulk variations (could be replaced with faster bulk algos) + public virtual void Add(TSeries d1, TSeries d2) + { + for (var i = 0; i < d1.Count; i++) + { + Add(d1[i], d2[i], false); + } + } - public virtual void Add(TSeries d1, double dd2) { - for (var i = 0; i < d1.Count; i++) { - Add(d1[i], (d1[i].t, dd2), false); - } - } + public virtual void Add(TSeries d1, double dd2) + { + for (var i = 0; i < d1.Count; i++) + { + Add(d1[i], (d1[i].t, dd2), false); + } + } - public virtual void Add(double dd1, TSeries d2) { - for (var i = 0; i < d2.Count; i++) { - Add((d2[i].t, dd1), d2[i], false); - } - } + public virtual void Add(double dd1, TSeries d2) + { + for (var i = 0; i < d2.Count; i++) + { + Add((d2[i].t, dd1), d2[i], false); + } + } - public void Add((DateTime t, double v) TValue1, (DateTime t, double v) TValue2) { - Add(TValue1, TValue2, false); - } + public void Add((DateTime t, double v) TValue1, (DateTime t, double v) TValue2) + { + Add(TValue1, TValue2, false); + } - public void Add(bool update) { - if (_dd1 is double.NaN && _dd2 is double.NaN) { - // (Series, Series) - if (update || (_d1.Count > Count && _d2.Count > Count)) { - Add(_d1[_d1.Count - 1], _d2[_d2.Count - 1], update); - } - } - else if (_dd2 is not double.NaN && _dd1 is double.NaN) { - // (Series, Double) - Add(_d1[_d1.Count - 1], (_d1[_d1.Count - 1].t, _dd2), update); - } - else { - // (Double, Series) - Add((_d2[_d2.Count - 1].t, _dd1), _d2[_d2.Count - 1], update); - } - } + public void Add(bool update) + { + if (_dd1 is double.NaN && _dd2 is double.NaN) + { + // (Series, Series) + if (update || (_d1.Count > Count && _d2.Count > Count)) + { + Add(_d1[_d1.Count - 1], _d2[_d2.Count - 1], update); + } + } + else if (_dd2 is not double.NaN && _dd1 is double.NaN) + { + // (Series, Double) + Add(_d1[_d1.Count - 1], (_d1[_d1.Count - 1].t, _dd2), update); + } + else + { + // (Double, Series) + Add((_d2[_d2.Count - 1].t, _dd1), _d2[_d2.Count - 1], update); + } + } - public void Add() { - Add(false); - } + public void Add() + { + Add(false); + } - public new void Sub(object source, TSeriesEventArgs e) { - Add(e.update); - } + public new void Sub(object source, TSeriesEventArgs e) + { + Add(e.update); + } - protected static void Add_Replace(List l, double v, bool update) { - if (update) { - l[l.Count - 1] = v; - } - else { - l.Add(v); - } - } + protected static void Add_Replace(List l, double v, bool update) + { + if (update) + { + l[l.Count - 1] = v; + } + else + { + l.Add(v); + } + } - protected static void Add_Replace_Trim(List l, double v, int p, bool update) { - Add_Replace(l, v, update); - if (l.Count > p && p != 0) { - l.RemoveAt(0); - } - } + protected static void Add_Replace_Trim(List l, double v, int p, bool update) + { + Add_Replace(l, v, update); + if (l.Count > p && p != 0) + { + l.RemoveAt(0); + } + } } diff --git a/Calculations/Feeds/Alphavantage_Feed.cs b/Calculations/Feeds/Alphavantage_Feed.cs index 5c9fc35f..f2bcc60e 100644 --- a/Calculations/Feeds/Alphavantage_Feed.cs +++ b/Calculations/Feeds/Alphavantage_Feed.cs @@ -13,7 +13,7 @@ Alphavantage - Free API to collect 100 recent daily quotes. It requires a (free) */ public class Alphavantage_Feed : TBars { - public enum Interval { Month, Week, Day, Hour, Min30, Min15, Min5, Min1} + public enum Interval { Month, Week, Day, Hour, Min30, Min15, Min5, Min1 } public Alphavantage_Feed(string Symbol = "IBM", string APIkey = "demo") { System.Net.Http.HttpClient client = new(); @@ -22,8 +22,8 @@ public class Alphavantage_Feed : TBars var msg = client.GetStringAsync(req).Result; var jres = JsonSerializer.Deserialize(msg).RootElement; jres.TryGetProperty("Time Series (Daily)", out JsonElement json); - - if (json.ValueKind == JsonValueKind.Undefined) {throw new InvalidOperationException("Stock symbol "+Symbol+" not found"); } + + if (json.ValueKind == JsonValueKind.Undefined) { throw new InvalidOperationException("Stock symbol " + Symbol + " not found"); } foreach (var val in json.EnumerateObject()) { base.Add(GetOHLC(val)); } base.Reverse(); } diff --git a/Calculations/Feeds/GBM_Feed.cs b/Calculations/Feeds/GBM_Feed.cs index a08568c1..c99a282f 100644 --- a/Calculations/Feeds/GBM_Feed.cs +++ b/Calculations/Feeds/GBM_Feed.cs @@ -23,41 +23,45 @@ public class GBM_Feed : TBars private double seed; readonly double drift, volatility; readonly int precision; - public GBM_Feed(int Bars = 252, double Volatility = 1.0, double Drift = 0.05, double Seed = 100.0, int Precision = 2) { + public GBM_Feed(int Bars = 252, double Volatility = 1.0, double Drift = 0.05, double Seed = 100.0, int Precision = 2) + { this.seed = Seed; - volatility = Volatility*0.01; - drift = Drift*0.01; + volatility = Volatility * 0.01; + drift = Drift * 0.01; precision = Precision; - for (int i = 0; i OCMin)? (2 * OCMin) - Low : Low; + double OCMin = Math.Min(Open, Close); + double Low = (GBM_value(seed, volatility * 0.5, 0, precision)); + Low = (Low > OCMin) ? (2 * OCMin) - Low : Low; - double Volume = GBM_value(seed*10, volatility*2, Drift:0, precision: 1); + double Volume = GBM_value(seed * 10, volatility * 2, Drift: 0, precision: 1); base.Add((timestamp, Open, High, Low, Close, Volume), update); seed = Close; } - private static double GBM_value(double Seed, double Volatility, double Drift, int precision) { + private static double GBM_value(double Seed, double Volatility, double Drift, int precision) + { Random rnd = new(); - double U1 = 1.0-rnd.NextDouble(); - double U2 = 1.0-rnd.NextDouble(); + double U1 = 1.0 - rnd.NextDouble(); + double U2 = 1.0 - rnd.NextDouble(); double Z = Math.Sqrt(-2.0 * Math.Log(U1)) * Math.Sin(2.0 * Math.PI * U2); - return Math.Round(Seed * Math.Exp( Drift - (Volatility*Volatility*0.5) + (Volatility * Z)), digits: precision); + return Math.Round(Seed * Math.Exp(Drift - (Volatility * Volatility * 0.5) + (Volatility * Z)), digits: precision); } } \ No newline at end of file diff --git a/Calculations/Feeds/Yahoo_Feed.cs b/Calculations/Feeds/Yahoo_Feed.cs index 5b9029a1..1d87e97d 100644 --- a/Calculations/Feeds/Yahoo_Feed.cs +++ b/Calculations/Feeds/Yahoo_Feed.cs @@ -14,34 +14,36 @@ Yahoo Finance - Free API feed to collect daily market quotes */ public class Yahoo_Feed : TBars { - public Yahoo_Feed(string Symbol = "IBM", int Period = 252) { - Period = (int)(Period*1.45); - string requestUrl = "https://query1.finance.yahoo.com/v8/finance/chart/"+ - Symbol+"?interval=1d&period1="+ - (int)new DateTimeOffset(DateTime.UtcNow.AddDays(-Period+1)).ToUnixTimeSeconds()+"&period2="+ + public Yahoo_Feed(string Symbol = "IBM", int Period = 252) + { + Period = (int)(Period * 1.45); + string requestUrl = "https://query1.finance.yahoo.com/v8/finance/chart/" + + Symbol + "?interval=1d&period1=" + + (int)new DateTimeOffset(DateTime.UtcNow.AddDays(-Period + 1)).ToUnixTimeSeconds() + "&period2=" + (int)new DateTimeOffset(DateTime.UtcNow).ToUnixTimeSeconds(); System.Net.Http.HttpClient client = new(); var msg = client.GetStringAsync(requestUrl).Result; var jresult = JsonSerializer.Deserialize(msg).RootElement; - jresult.TryGetProperty("chart",out JsonElement json); - json.TryGetProperty("result",out json); - json[0].TryGetProperty("timestamp",out JsonElement datetime); - json[0].TryGetProperty("indicators",out json); - json.TryGetProperty("quote",out json); - json[0].TryGetProperty("open",out JsonElement open); - json[0].TryGetProperty("high",out JsonElement high); - json[0].TryGetProperty("low",out JsonElement low); - json[0].TryGetProperty("close",out JsonElement close); - json[0].TryGetProperty("volume",out JsonElement volume); + jresult.TryGetProperty("chart", out JsonElement json); + json.TryGetProperty("result", out json); + json[0].TryGetProperty("timestamp", out JsonElement datetime); + json[0].TryGetProperty("indicators", out json); + json.TryGetProperty("quote", out json); + json[0].TryGetProperty("open", out JsonElement open); + json[0].TryGetProperty("high", out JsonElement high); + json[0].TryGetProperty("low", out JsonElement low); + json[0].TryGetProperty("close", out JsonElement close); + json[0].TryGetProperty("volume", out JsonElement volume); - for (int i=0; i */ -public class COMPARE_Series : Pair_TSeries_Indicator { +public class COMPARE_Series : Pair_TSeries_Indicator +{ - public COMPARE_Series(TSeries d1, TSeries d2) : base(d1, d2) { - if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } - } - public COMPARE_Series(TSeries d1, double dd2) : base(d1, dd2) { - if (base._d1.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } - } - public COMPARE_Series(double dd1, TSeries d2) : base(dd1, d2) { - if (base._d2.Count > 0) { for (int i = 0; i < base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } - } + public COMPARE_Series(TSeries d1, TSeries d2) : base(d1, d2) + { + if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } + } + public COMPARE_Series(TSeries d1, double dd2) : base(d1, dd2) + { + if (base._d1.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } + } + public COMPARE_Series(double dd1, TSeries d2) : base(dd1, d2) + { + if (base._d2.Count > 0) { for (int i = 0; i < base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } + } - public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) { + public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) + { - double val = TValue1.v > TValue2.v ? 1 : -1; - val = TValue1.v == TValue2.v ? 0 : val; - (System.DateTime t, double v) over = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t, TValue1.v > TValue2.v ? 1 : val); - if (update) { base[^1] = over; } - else { base.Add(over); } + double val = TValue1.v > TValue2.v ? 1 : -1; + val = TValue1.v == TValue2.v ? 0 : val; + (System.DateTime t, double v) over = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t, TValue1.v > TValue2.v ? 1 : val); + if (update) { base[^1] = over; } + else { base.Add(over); } - } + } } diff --git a/Calculations/Logic/CROSS_Series.cs b/Calculations/Logic/CROSS_Series.cs index 2c4cad1c..6d62a54c 100644 --- a/Calculations/Logic/CROSS_Series.cs +++ b/Calculations/Logic/CROSS_Series.cs @@ -9,36 +9,41 @@ Remarks: */ -public class CROSS_Series : Pair_TSeries_Indicator { - public TSeries Cross { get; set; } = new(); +public class CROSS_Series : Pair_TSeries_Indicator +{ + public TSeries Cross { get; set; } = new(); - private double _previous = double.NaN; - public CROSS_Series(TSeries d1, TSeries d2) : base(d1, d2) { - if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } - } - public CROSS_Series(TSeries d1, double dd2) : base(d1, dd2) { - if (base._d1.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } - } - public CROSS_Series(double dd1, TSeries d2) : base(dd1, d2) { - if (base._d2.Count > 0) { for (int i = 0; i < base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } - } + private double _previous = double.NaN; + public CROSS_Series(TSeries d1, TSeries d2) : base(d1, d2) + { + if (base._d1.Count > 0 && base._d2.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], base._d2[i], false); } } + } + public CROSS_Series(TSeries d1, double dd2) : base(d1, dd2) + { + if (base._d1.Count > 0) { for (int i = 0; i < base._d1.Count; i++) { this.Add(base._d1[i], (base._d1[i].t, dd2), false); } } + } + public CROSS_Series(double dd1, TSeries d2) : base(dd1, d2) + { + if (base._d2.Count > 0) { for (int i = 0; i < base._d2.Count; i++) { this.Add((base._d2[i].t, dd1), base._d2[i], false); } } + } - public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) { + public override void Add((System.DateTime t, double v) TValue1, (System.DateTime t, double v) TValue2, bool update) + { - double val = TValue1.v > TValue2.v ? 1 : -1; - val = TValue1.v == TValue2.v ? 0 : val; - double over = TValue1.v > TValue2.v ? 1 : val; + double val = TValue1.v > TValue2.v ? 1 : -1; + val = TValue1.v == TValue2.v ? 0 : val; + double over = TValue1.v > TValue2.v ? 1 : val; - val = (_previous < over) ? 1 : -1; - val = ((_previous == over) || Double.IsNaN(this._previous) || (this._previous == 0)) ? 0 : val; - (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t,val); + val = (_previous < over) ? 1 : -1; + val = ((_previous == over) || Double.IsNaN(this._previous) || (this._previous == 0)) ? 0 : val; + (System.DateTime t, double v) result = ((TValue1.t > TValue2.t) ? TValue1.t : TValue2.t, val); - this._previous = over; + this._previous = over; - if (update) { base[^1] = result; } - else { base.Add(result); } + if (update) { base[^1] = result; } + else { base.Add(result); } - } + } } diff --git a/Calculations/Logic/TOrders.cs b/Calculations/Logic/TOrders.cs index 04d5ffa3..1e122b2b 100644 --- a/Calculations/Logic/TOrders.cs +++ b/Calculations/Logic/TOrders.cs @@ -6,29 +6,33 @@ using System.Data; using System.Linq; -public enum OType { - NIL = 0, // No position - BTO = 1, // Buy to Open - STC = 2, // Sell to Close - STO = 3, // Sell to Open - BTC = 4, // Buy to Close - END = 5, // Exit the trade +public enum OType +{ + NIL = 0, // No position + BTO = 1, // Buy to Open + STC = 2, // Sell to Close + STO = 3, // Sell to Open + BTC = 4, // Buy to Close + END = 5, // Exit the trade } -public class TOrders : List<(DateTime t, OType o)> { +public class TOrders : List<(DateTime t, OType o)> +{ - public void Add((DateTime t, OType o) TOrder, bool update = false) - { - if (update) { this[^1] = TOrder; } - else { base.Add(TOrder); } - OnEvent(update); - } + public void Add((DateTime t, OType o) TOrder, bool update = false) + { + if (update) { this[^1] = TOrder; } + else { base.Add(TOrder); } + OnEvent(update); + } - protected virtual void OnEvent(bool update = false) { - Pub?.Invoke(this, new TSeriesEventArgs { update = update }); } - public delegate void NewDataEventHandler(object source, TSeriesEventArgs args); - public event NewDataEventHandler Pub; + protected virtual void OnEvent(bool update = false) + { + Pub?.Invoke(this, new TSeriesEventArgs { update = update }); + } + public delegate void NewDataEventHandler(object source, TSeriesEventArgs args); + public event NewDataEventHandler Pub; } \ No newline at end of file diff --git a/Calculations/_Updated/ADL_Series.cs b/Calculations/_Updated/ADL_Series.cs index 2222887e..d84c1cf6 100644 --- a/Calculations/_Updated/ADL_Series.cs +++ b/Calculations/_Updated/ADL_Series.cs @@ -15,55 +15,65 @@ Sources: */ -public class ADL_Series : TSeries { - protected readonly TBars _data; - private double _lastadl, _lastlastadl; +public class ADL_Series : TSeries +{ + protected readonly TBars _data; + private double _lastadl, _lastlastadl; - //core constructors - public ADL_Series() { - Name = $"ADL()"; - _lastadl = _lastlastadl = 0; - } - public ADL_Series(TBars source) { - _data = source; - Name = $"ADL({(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _lastadl = _lastlastadl = 0; - _data.Pub += Sub; - Add(data: _data); - } + //core constructors + public ADL_Series() + { + Name = $"ADL()"; + _lastadl = _lastlastadl = 0; + } + public ADL_Series(TBars source) + { + _data = source; + Name = $"ADL({(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _lastadl = _lastlastadl = 0; + _data.Pub += Sub; + Add(data: _data); + } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - if (update) { this._lastadl = this._lastlastadl; } - else { this._lastlastadl = this._lastadl; } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) + { + if (update) { this._lastadl = this._lastlastadl; } + else { this._lastlastadl = this._lastadl; } - double _adl = 0; - double tmp = TBar.h - TBar.l; - if (tmp > 0.0) { - _adl = _lastadl + ((2 * TBar.c - TBar.l - TBar.h) / tmp * TBar.v); - } - _lastadl = _adl; + double _adl = 0; + double tmp = TBar.h - TBar.l; + if (tmp > 0.0) + { + _adl = _lastadl + ((2 * TBar.c - TBar.l - TBar.h) / tmp * TBar.v); + } + _lastadl = _adl; - var ret = (TBar.t, _adl); - return base.Add(ret, update); - } + var ret = (TBar.t, _adl); + return base.Add(ret, update); + } - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } + public new void Add(TBars data) + { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TBar: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _lastadl = _lastlastadl = 0; - } + //reset calculation + public override void Reset() + { + _lastadl = _lastlastadl = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/ADOSC_Series.cs b/Calculations/_Updated/ADOSC_Series.cs index 4c943b07..bc5b4305 100644 --- a/Calculations/_Updated/ADOSC_Series.cs +++ b/Calculations/_Updated/ADOSC_Series.cs @@ -14,77 +14,87 @@ Sources: */ -public class ADOSC_Series : TSeries { - protected readonly TBars _data; - private readonly double _k1, _k2; - private double _lastema1, _lastlastema1, _lastema2, _lastlastema2; - private double _lastadl, _lastlastadl; +public class ADOSC_Series : TSeries +{ + protected readonly TBars _data; + private readonly double _k1, _k2; + private double _lastema1, _lastlastema1, _lastema2, _lastlastema2; + private double _lastadl, _lastlastadl; - //core constructors - public ADOSC_Series(int shortPeriod, int longPeriod, bool useNaN = false) { - Name = $"ADOSC()"; - _k1 = 2.0 / (shortPeriod + 1); - _k2 = 2.0 / (longPeriod + 1); - _lastadl = _lastlastadl = _lastema1 = _lastlastema1 = _lastema2 = _lastlastema2 = 0; - } - public ADOSC_Series(TBars source, int shortPeriod, int longPeriod, bool useNaN = false) :this(shortPeriod, longPeriod, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _lastadl = _lastlastadl = 0; - _data.Pub += Sub; - Add(data: _data); - } + //core constructors + public ADOSC_Series(int shortPeriod, int longPeriod, bool useNaN = false) + { + Name = $"ADOSC()"; + _k1 = 2.0 / (shortPeriod + 1); + _k2 = 2.0 / (longPeriod + 1); + _lastadl = _lastlastadl = _lastema1 = _lastlastema1 = _lastema2 = _lastlastema2 = 0; + } + public ADOSC_Series(TBars source, int shortPeriod, int longPeriod, bool useNaN = false) : this(shortPeriod, longPeriod, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _lastadl = _lastlastadl = 0; + _data.Pub += Sub; + Add(data: _data); + } - public ADOSC_Series() : this(shortPeriod: 3, longPeriod: 10, useNaN: false) {} + public ADOSC_Series() : this(shortPeriod: 3, longPeriod: 10, useNaN: false) { } - public ADOSC_Series(TBars source) : this(source, shortPeriod: 3, longPeriod:10, useNaN:false) { } + public ADOSC_Series(TBars source) : this(source, shortPeriod: 3, longPeriod: 10, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update= false) { + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) + { - if (update) { - _lastadl = _lastlastadl; - _lastema1 = _lastlastema1; - _lastema2 = _lastlastema2; - } + if (update) + { + _lastadl = _lastlastadl; + _lastema1 = _lastlastema1; + _lastema2 = _lastlastema2; + } - double _adl = 0; - double tmp = TBar.h - TBar.l; - if (tmp > 0.0) { _adl = _lastadl + ((2 * TBar.c - TBar.l - TBar.h) / tmp * TBar.v); } - if (this.Count == 0) { _lastema1 = _lastema2 = _adl; } + double _adl = 0; + double tmp = TBar.h - TBar.l; + if (tmp > 0.0) { _adl = _lastadl + ((2 * TBar.c - TBar.l - TBar.h) / tmp * TBar.v); } + if (this.Count == 0) { _lastema1 = _lastema2 = _adl; } - double _ema1 = (_adl - _lastema1) * _k1 + _lastema1; - double _ema2 = (_adl - _lastema2) * _k2 + _lastema2; + double _ema1 = (_adl - _lastema1) * _k1 + _lastema1; + double _ema2 = (_adl - _lastema2) * _k2 + _lastema2; - _lastlastadl = _lastadl; - _lastadl = _adl; - _lastlastema1 = _lastema1; - _lastema1 = _ema1; - _lastlastema2 = _lastema2; - _lastema2 = _ema2; + _lastlastadl = _lastadl; + _lastadl = _adl; + _lastlastema1 = _lastema1; + _lastema1 = _ema1; + _lastlastema2 = _lastema2; + _lastema2 = _ema2; - double _adosc = _ema1 - _ema2; - - var ret = (TBar.t, _adosc); - return base.Add(ret, update); - } + double _adosc = _ema1 - _ema2; - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } + var ret = (TBar.t, _adosc); + return base.Add(ret, update); + } - //reset calculation - public override void Reset() { - _lastadl = _lastlastadl = _lastema1 = _lastlastema1 = _lastema2 = _lastlastema2 = 0; - } + public new void Add(TBars data) + { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TBar: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() + { + _lastadl = _lastlastadl = _lastema1 = _lastlastema1 = _lastema2 = _lastlastema2 = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/ALMA_Series.cs b/Calculations/_Updated/ALMA_Series.cs index e4c8c3de..a25ac7d5 100644 --- a/Calculations/_Updated/ALMA_Series.cs +++ b/Calculations/_Updated/ALMA_Series.cs @@ -19,96 +19,111 @@ Sources: Discrepancy with Pandas-TA (but passes the validation with Skender.GetAlma) */ -public class ALMA_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class ALMA_Series : TSeries +{ + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - private readonly System.Collections.Generic.List _buffer = new(); - private readonly System.Collections.Generic.List _weight; - private double _norm; - private readonly double _offset, _sigma; + private readonly System.Collections.Generic.List _buffer = new(); + private readonly System.Collections.Generic.List _weight; + private double _norm; + private readonly double _offset, _sigma; - //core constructors - public ALMA_Series(int period, double offset, double sigma, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"ALMA({period})"; - _offset = offset; - _sigma = sigma; - _weight = new(); - } - public ALMA_Series(TSeries source, int period, double offset, double sigma, bool useNaN) : this(period, offset, sigma, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } + //core constructors + public ALMA_Series(int period, double offset, double sigma, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"ALMA({period})"; + _offset = offset; + _sigma = sigma; + _weight = new(); + } + public ALMA_Series(TSeries source, int period, double offset, double sigma, bool useNaN) : this(period, offset, sigma, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } - public ALMA_Series() : this(period:0, offset:0.85, sigma:6.0, useNaN: false) { } - public ALMA_Series(int period) : this(period: period, offset:0.85, sigma:6.0, useNaN:false) { } - public ALMA_Series(TBars source) : this(source:source.Close, period:0, offset:0.85, sigma:6.0, useNaN:false) { } - public ALMA_Series(TBars source, int period) : this(source:source.Close, period:period, offset: 0.85, sigma: 6.0, useNaN: false) { } - public ALMA_Series(TBars source, int period, double offset, double sigma, bool useNaN) : this(source.Close, period:period, offset: offset, sigma: sigma, useNaN: false) { } - public ALMA_Series(TSeries source) : this(source, period:0, offset:0.85, sigma:6.0, useNaN:false) { } - public ALMA_Series(TSeries source, int period) : this(source:source, period:period, offset:0.85, sigma:6.0, useNaN:false) { } - public ALMA_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, offset: 0.85, sigma: 6.0, useNaN: useNaN) { } + public ALMA_Series() : this(period: 0, offset: 0.85, sigma: 6.0, useNaN: false) { } + public ALMA_Series(int period) : this(period: period, offset: 0.85, sigma: 6.0, useNaN: false) { } + public ALMA_Series(TBars source) : this(source: source.Close, period: 0, offset: 0.85, sigma: 6.0, useNaN: false) { } + public ALMA_Series(TBars source, int period) : this(source: source.Close, period: period, offset: 0.85, sigma: 6.0, useNaN: false) { } + public ALMA_Series(TBars source, int period, double offset, double sigma, bool useNaN) : this(source.Close, period: period, offset: offset, sigma: sigma, useNaN: false) { } + public ALMA_Series(TSeries source) : this(source, period: 0, offset: 0.85, sigma: 6.0, useNaN: false) { } + public ALMA_Series(TSeries source, int period) : this(source: source, period: period, offset: 0.85, sigma: 6.0, useNaN: false) { } + public ALMA_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, offset: 0.85, sigma: 6.0, useNaN: useNaN) { } - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, double.NaN), update); - } + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, double.NaN), update); + } - BufferTrim(_buffer, TValue.v, _period, update); - if (_weight.Count < _buffer.Count) { - for (var i = 0; i < _buffer.Count - _weight.Count; i++) { - _weight.Add(0.0); - } - } - + BufferTrim(_buffer, TValue.v, _period, update); + if (_weight.Count < _buffer.Count) + { + for (var i = 0; i < _buffer.Count - _weight.Count; i++) + { + _weight.Add(0.0); + } + } - if (_buffer.Count <= _period || _period == 0) { - var _len = _buffer.Count; - _norm = 0; - var _m = _offset * (_len - 1); - var _s = _len / _sigma; - for (var i = 0; i < _len; i++) { - var _wt = Math.Exp(-((i - _m) * (i - _m)) / (2 * _s * _s)); - _weight[i] = _wt; - _norm += _wt; - } - } - double _weightedSum = 0; - for (var i = 0; i < _buffer.Count; i++) { - _weightedSum += _weight[i] * _buffer[i]; - } + if (_buffer.Count <= _period || _period == 0) + { + var _len = _buffer.Count; + _norm = 0; + var _m = _offset * (_len - 1); + var _s = _len / _sigma; + for (var i = 0; i < _len; i++) + { + var _wt = Math.Exp(-((i - _m) * (i - _m)) / (2 * _s * _s)); + _weight[i] = _wt; + _norm += _wt; + } + } - var _alma = _weightedSum / _norm; + double _weightedSum = 0; + for (var i = 0; i < _buffer.Count; i++) + { + _weightedSum += _weight[i] * _buffer[i]; + } - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _alma); - return base.Add(res, update); - } + var _alma = _weightedSum / _norm; - //variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - //reset calculation - public override void Reset() { - _buffer.Clear(); - _weight.Clear(); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _alma); + return base.Add(res, update); + } + + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + _weight.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/ATRP_Series.cs b/Calculations/_Updated/ATRP_Series.cs index 7f4cf4c7..ee429ea0 100644 --- a/Calculations/_Updated/ATRP_Series.cs +++ b/Calculations/_Updated/ATRP_Series.cs @@ -12,76 +12,86 @@ Sources: */ -public class ATRP_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TBars _data; - private double _k; - private int _len; - private double _lastatr, _lastlastatr, _cm1, _lastcm1, _sum, _oldsum; +public class ATRP_Series : TSeries +{ + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TBars _data; + private double _k; + private int _len; + private double _lastatr, _lastlastatr, _cm1, _lastcm1, _sum, _oldsum; - //core constructors - public ATRP_Series(int period, bool useNaN) { - _period = period; - _k = 1.0 / (double)(_period); - _NaN = useNaN; - _len = 0; - Name = $"ATRP({period})"; - } - public ATRP_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(data: _data); - } - public ATRP_Series() : this(period: 1, useNaN: false) { } - public ATRP_Series(int period) : this(period: period, useNaN: false) { } - public ATRP_Series(TBars source) : this(source, period: 1, useNaN: false) { } - public ATRP_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public ATRP_Series(int period, bool useNaN) + { + _period = period; + _k = 1.0 / (double)(_period); + _NaN = useNaN; + _len = 0; + Name = $"ATRP({period})"; + } + public ATRP_Series(TBars source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(data: _data); + } + public ATRP_Series() : this(period: 1, useNaN: false) { } + public ATRP_Series(int period) : this(period: period, useNaN: false) { } + public ATRP_Series(TBars source) : this(source, period: 1, useNaN: false) { } + public ATRP_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - if (update) { _lastatr = _lastlastatr; _cm1 = _lastcm1; _sum = _oldsum; } - else { - _lastlastatr = _lastatr; _lastcm1 = _cm1; _oldsum = _sum; - _k = (_period == 0) ? 1 / (double)_len : _k; - _len++; - } - - if (_len == 1) { _cm1 = TBar.c; } - double d1 = Math.Abs(TBar.h - TBar.l); - double d2 = Math.Abs(_cm1 - TBar.h); - double d3 = Math.Abs(_cm1 - TBar.l); - (DateTime t, double v) d = (TBar.t, Math.Max(d1, Math.Max(d2, d3))); - _cm1 = TBar.c; + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) + { + if (update) { _lastatr = _lastlastatr; _cm1 = _lastcm1; _sum = _oldsum; } + else + { + _lastlastatr = _lastatr; _lastcm1 = _cm1; _oldsum = _sum; + _k = (_period == 0) ? 1 / (double)_len : _k; + _len++; + } - double _atr = 0; - if (this.Count == 0) { _atr = d.v; } - else if (this.Count < _period + 1) { _sum += d.v; _atr = _sum / (this.Count); } - else { _atr = _k * (d.v - _lastatr) + _lastatr; } - _lastatr = _atr; - double _atrp = 100 * (_atr / TBar.c); + if (_len == 1) { _cm1 = TBar.c; } + double d1 = Math.Abs(TBar.h - TBar.l); + double d2 = Math.Abs(_cm1 - TBar.h); + double d3 = Math.Abs(_cm1 - TBar.l); + (DateTime t, double v) d = (TBar.t, Math.Max(d1, Math.Max(d2, d3))); + _cm1 = TBar.c; - var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _atrp); - return base.Add(res, update); - } + double _atr = 0; + if (this.Count == 0) { _atr = d.v; } + else if (this.Count < _period + 1) { _sum += d.v; _atr = _sum / (this.Count); } + else { _atr = _k * (d.v - _lastatr) + _lastatr; } + _lastatr = _atr; + double _atrp = 100 * (_atr / TBar.c); - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } + var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _atrp); + return base.Add(res, update); + } - //reset calculation - public override void Reset() { - _len = 0; - } + public new void Add(TBars data) + { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TBar: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() + { + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/ATR_Series.cs b/Calculations/_Updated/ATR_Series.cs index be22668d..c09ce7b2 100644 --- a/Calculations/_Updated/ATR_Series.cs +++ b/Calculations/_Updated/ATR_Series.cs @@ -14,75 +14,85 @@ Sources: */ -public class ATR_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TBars _data; - private double _k; - private int _len; - private double _lastatr, _lastlastatr, _cm1, _lastcm1, _sum, _oldsum; +public class ATR_Series : TSeries +{ + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TBars _data; + private double _k; + private int _len; + private double _lastatr, _lastlastatr, _cm1, _lastcm1, _sum, _oldsum; - //core constructors - public ATR_Series(int period, bool useNaN) { - _period = period; - _k = 1.0 / (double)(_period); - _NaN = useNaN; - _len = 0; - Name = $"ATR({period})"; - } - public ATR_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(data: _data); - } - public ATR_Series() : this(period: 1, useNaN: false) { } - public ATR_Series(int period) : this(period: period, useNaN: false) { } - public ATR_Series(TBars source) : this(source, period: 1, useNaN: false) { } - public ATR_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public ATR_Series(int period, bool useNaN) + { + _period = period; + _k = 1.0 / (double)(_period); + _NaN = useNaN; + _len = 0; + Name = $"ATR({period})"; + } + public ATR_Series(TBars source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(data: _data); + } + public ATR_Series() : this(period: 1, useNaN: false) { } + public ATR_Series(int period) : this(period: period, useNaN: false) { } + public ATR_Series(TBars source) : this(source, period: 1, useNaN: false) { } + public ATR_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - if (update) { _lastatr = _lastlastatr; _cm1 = _lastcm1; _sum = _oldsum; } - else { - _lastlastatr = _lastatr; _lastcm1 = _cm1; _oldsum = _sum; - _k = (_period == 0) ? 1 / (double)_len : _k; - _len++; - } - - if (_len == 1) { _cm1 = TBar.c; } - double d1 = Math.Abs(TBar.h - TBar.l); - double d2 = Math.Abs(_cm1 - TBar.h); - double d3 = Math.Abs(_cm1 - TBar.l); - (DateTime t, double v) d = (TBar.t, Math.Max(d1, Math.Max(d2, d3))); - _cm1 = TBar.c; + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) + { + if (update) { _lastatr = _lastlastatr; _cm1 = _lastcm1; _sum = _oldsum; } + else + { + _lastlastatr = _lastatr; _lastcm1 = _cm1; _oldsum = _sum; + _k = (_period == 0) ? 1 / (double)_len : _k; + _len++; + } - double _atr = 0; - if (this.Count == 0) { _atr = d.v; } - else if (this.Count < _period + 1) { _sum += d.v; _atr = _sum / (this.Count); } - else { _atr = _k * (d.v - _lastatr) + _lastatr; } - _lastatr = _atr; - - var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _atr); - return base.Add(res, update); - } + if (_len == 1) { _cm1 = TBar.c; } + double d1 = Math.Abs(TBar.h - TBar.l); + double d2 = Math.Abs(_cm1 - TBar.h); + double d3 = Math.Abs(_cm1 - TBar.l); + (DateTime t, double v) d = (TBar.t, Math.Max(d1, Math.Max(d2, d3))); + _cm1 = TBar.c; - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } + double _atr = 0; + if (this.Count == 0) { _atr = d.v; } + else if (this.Count < _period + 1) { _sum += d.v; _atr = _sum / (this.Count); } + else { _atr = _k * (d.v - _lastatr) + _lastatr; } + _lastatr = _atr; - //reset calculation - public override void Reset() { - _len = 0; - } + var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _atr); + return base.Add(res, update); + } + + public new void Add(TBars data) + { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TBar: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() + { + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/BBANDS_Series.cs b/Calculations/_Updated/BBANDS_Series.cs index 7cc5a3e0..e64398ff 100644 --- a/Calculations/_Updated/BBANDS_Series.cs +++ b/Calculations/_Updated/BBANDS_Series.cs @@ -26,87 +26,96 @@ Note: */ -public class BBANDS_Series : TSeries { - protected readonly int _period; - protected readonly double _multiplier; - protected readonly bool _NaN; - protected readonly TSeries _data; - public SMA_Series Mid { get; } - public TSeries Upper { get; } - public TSeries Lower { get; } - public TSeries PercentB { get; } - public TSeries Bandwidth { get; } - public TSeries Zscore { get; } - private readonly SDEV_Series _sdev; +public class BBANDS_Series : TSeries +{ + protected readonly int _period; + protected readonly double _multiplier; + protected readonly bool _NaN; + protected readonly TSeries _data; + public SMA_Series Mid { get; } + public TSeries Upper { get; } + public TSeries Lower { get; } + public TSeries PercentB { get; } + public TSeries Bandwidth { get; } + public TSeries Zscore { get; } + private readonly SDEV_Series _sdev; - //core constructors - public BBANDS_Series(int period, double multiplier, bool useNaN) { - _period = period; - _multiplier = multiplier; - _NaN = useNaN; - Name = $"BBANDS({period})"; - } - public BBANDS_Series(TSeries source, int period, double multiplier, bool useNaN) : this(period, multiplier, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - Upper = new("BB_Up"); - Lower = new("BB_Low"); - Bandwidth = new("BBandwidth"); - PercentB = new("%BBandwidth"); - Zscore = new("Zscore"); + //core constructors + public BBANDS_Series(int period, double multiplier, bool useNaN) + { + _period = period; + _multiplier = multiplier; + _NaN = useNaN; + Name = $"BBANDS({period})"; + } + public BBANDS_Series(TSeries source, int period, double multiplier, bool useNaN) : this(period, multiplier, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + Upper = new("BB_Up"); + Lower = new("BB_Low"); + Bandwidth = new("BBandwidth"); + PercentB = new("%BBandwidth"); + Zscore = new("Zscore"); - Mid = new(period, false); - _sdev = new(period, false); + Mid = new(period, false); + _sdev = new(period, false); - _data.Pub += Sub; - Add(_data); - } + _data.Pub += Sub; + Add(_data); + } - public BBANDS_Series() : this(period:0, multiplier: 2.0, useNaN: false) { } - public BBANDS_Series(int period) : this(period: period, multiplier: 2.0, useNaN:false) { } - public BBANDS_Series(TBars source) : this(source:source.Close, period:0, multiplier: 2.0, useNaN:false) { } - public BBANDS_Series(TBars source, int period) : this(source:source.Close, period:period, multiplier: 2.0, useNaN: false) { } - public BBANDS_Series(TBars source, int period, double multiplier, bool useNaN) : this(source.Close, period:period, multiplier:multiplier, useNaN: false) { } - public BBANDS_Series(TSeries source) : this(source, period:0, useNaN:false) { } - public BBANDS_Series(TSeries source, int period) : this(source:source, period:period, useNaN:false) { } - public BBANDS_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, multiplier: 2.0, useNaN: useNaN) { } + public BBANDS_Series() : this(period: 0, multiplier: 2.0, useNaN: false) { } + public BBANDS_Series(int period) : this(period: period, multiplier: 2.0, useNaN: false) { } + public BBANDS_Series(TBars source) : this(source: source.Close, period: 0, multiplier: 2.0, useNaN: false) { } + public BBANDS_Series(TBars source, int period) : this(source: source.Close, period: period, multiplier: 2.0, useNaN: false) { } + public BBANDS_Series(TBars source, int period, double multiplier, bool useNaN) : this(source.Close, period: period, multiplier: multiplier, useNaN: false) { } + public BBANDS_Series(TSeries source) : this(source, period: 0, useNaN: false) { } + public BBANDS_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + public BBANDS_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, multiplier: 2.0, useNaN: useNaN) { } - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update=false) { - var _mid = Mid.Add(TValue,update); - var _sd = this._sdev.Add(TValue, update); - var _upper = Upper.Add((TValue.t, _mid.v + _sd.v * _multiplier), update); - var _lower = Lower.Add((TValue.t, _mid.v - _sd.v * _multiplier), update); - double _pbdnd = TValue.v - _lower.v; - double _pbdvr = _upper.v - _lower.v; - PercentB.Add((TValue.t, _pbdnd/_pbdvr), update); - Zscore.Add((TValue.t, (TValue.v-_mid.v)/_sd.v), update); - Bandwidth.Add((TValue.t, _pbdvr / _mid.v), update); - - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _pbdvr / _mid.v); - return base.Add(res, update); - } + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + var _mid = Mid.Add(TValue, update); + var _sd = this._sdev.Add(TValue, update); + var _upper = Upper.Add((TValue.t, _mid.v + _sd.v * _multiplier), update); + var _lower = Lower.Add((TValue.t, _mid.v - _sd.v * _multiplier), update); + double _pbdnd = TValue.v - _lower.v; + double _pbdvr = _upper.v - _lower.v; + PercentB.Add((TValue.t, _pbdnd / _pbdvr), update); + Zscore.Add((TValue.t, (TValue.v - _mid.v) / _sd.v), update); + Bandwidth.Add((TValue.t, _pbdvr / _mid.v), update); - //variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - //reset calculation - public override void Reset() { - Mid.Clear(); - _sdev.Clear(); - Upper.Clear(); - Lower.Clear(); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _pbdvr / _mid.v); + return base.Add(res, update); + } + + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } + //reset calculation + public override void Reset() + { + Mid.Clear(); + _sdev.Clear(); + Upper.Clear(); + Lower.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/BIAS_Series.cs b/Calculations/_Updated/BIAS_Series.cs index 0c859ee4..cf724bf3 100644 --- a/Calculations/_Updated/BIAS_Series.cs +++ b/Calculations/_Updated/BIAS_Series.cs @@ -13,60 +13,69 @@ Sources: */ -public class BIAS_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly SMA_Series _sma; +public class BIAS_Series : TSeries +{ + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly SMA_Series _sma; - //core constructors - public BIAS_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"BIAS({period})"; - _sma = new(period, false); - } - public BIAS_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public BIAS_Series() : this(period: 0, useNaN: false) { } - public BIAS_Series(int period) : this(period: period, useNaN: false) { } - public BIAS_Series(TBars source) : this(source.Close, 0, false) { } - public BIAS_Series(TBars source, int period) : this(source.Close, period, false) { } - public BIAS_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public BIAS_Series(TSeries source) : this(source, 0, false) { } - public BIAS_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public BIAS_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"BIAS({period})"; + _sma = new(period, false); + } + public BIAS_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public BIAS_Series() : this(period: 0, useNaN: false) { } + public BIAS_Series(int period) : this(period: period, useNaN: false) { } + public BIAS_Series(TBars source) : this(source.Close, 0, false) { } + public BIAS_Series(TBars source, int period) : this(source.Close, period, false) { } + public BIAS_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public BIAS_Series(TSeries source) : this(source, 0, false) { } + public BIAS_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - var _s = _sma.Add(TValue,update); - double _bias = (TValue.v / ((_s.v!=0)?_s.v:1)) - 1; + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + var _s = _sma.Add(TValue, update); + double _bias = (TValue.v / ((_s.v != 0) ? _s.v : 1)) - 1; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _bias); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _bias); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _sma.Reset(); - } + //reset calculation + public override void Reset() + { + _sma.Reset(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/CCI_Series.cs b/Calculations/_Updated/CCI_Series.cs index 89efbc20..4886857b 100644 --- a/Calculations/_Updated/CCI_Series.cs +++ b/Calculations/_Updated/CCI_Series.cs @@ -18,69 +18,80 @@ Sources: */ -public class CCI_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TBars _data; - private readonly System.Collections.Generic.List _tp = new(); +public class CCI_Series : TSeries +{ + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TBars _data; + private readonly System.Collections.Generic.List _tp = new(); - //core constructors - public CCI_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"CCI({period})"; - } - public CCI_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(data: _data); - } - public CCI_Series() : this(period: 2, useNaN: false) { } - public CCI_Series(int period) : this(period: period, useNaN: false) { } - public CCI_Series(TBars source) : this(source, period: 2, useNaN: false) { } - public CCI_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public CCI_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"CCI({period})"; + } + public CCI_Series(TBars source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(data: _data); + } + public CCI_Series() : this(period: 2, useNaN: false) { } + public CCI_Series(int period) : this(period: period, useNaN: false) { } + public CCI_Series(TBars source) : this(source, period: 2, useNaN: false) { } + public CCI_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - double _tpItem = (TBar.h + TBar.l + TBar.c) / 3.0; - if (update) { - this._tp[this._tp.Count - 1] = _tpItem; - } - else { - this._tp.Add(_tpItem); - } - if (this._tp.Count > this._period) { this._tp.RemoveAt(0); } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) + { + double _tpItem = (TBar.h + TBar.l + TBar.c) / 3.0; + if (update) + { + this._tp[this._tp.Count - 1] = _tpItem; + } + else + { + this._tp.Add(_tpItem); + } + if (this._tp.Count > this._period) { this._tp.RemoveAt(0); } - // average TP over _tp buffer - double _avgTp = _tp.Average(); + // average TP over _tp buffer + double _avgTp = _tp.Average(); - // average Deviation over _tp buffer - double _avgDv = 0; - for (int i = 0; i < this._tp.Count; i++) { _avgDv += Math.Abs(_avgTp - this._tp[i]); } - _avgDv /= this._tp.Count; + // average Deviation over _tp buffer + double _avgDv = 0; + for (int i = 0; i < this._tp.Count; i++) { _avgDv += Math.Abs(_avgTp - this._tp[i]); } + _avgDv /= this._tp.Count; - double _cci = (_avgDv == 0) ? 0 : (this._tp[this._tp.Count - 1] - _avgTp) / (0.015 * _avgDv); - var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _cci); - return base.Add(res, update); - } + double _cci = (_avgDv == 0) ? 0 : (this._tp[this._tp.Count - 1] - _avgTp) / (0.015 * _avgDv); + var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _cci); + return base.Add(res, update); + } - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } + public new void Add(TBars data) + { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TBar: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _tp.Clear(); - } + //reset calculation + public override void Reset() + { + _tp.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/CMO_Series.cs b/Calculations/_Updated/CMO_Series.cs index ea800ed2..fe793bb0 100644 --- a/Calculations/_Updated/CMO_Series.cs +++ b/Calculations/_Updated/CMO_Series.cs @@ -18,73 +18,83 @@ Sources: */ -public class CMO_Series : TSeries { - private readonly System.Collections.Generic.List _buff_up = new(); - private readonly System.Collections.Generic.List _buff_dn = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private double _plast_value, _last_value; +public class CMO_Series : TSeries +{ + private readonly System.Collections.Generic.List _buff_up = new(); + private readonly System.Collections.Generic.List _buff_dn = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private double _plast_value, _last_value; - //core constructors - public CMO_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"CMO({period})"; - } - public CMO_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public CMO_Series() : this(period: 0, useNaN: false) { } - public CMO_Series(int period) : this(period: period, useNaN: false) { } - public CMO_Series(TBars source) : this(source.Close, 0, false) { } - public CMO_Series(TBars source, int period) : this(source.Close, period, false) { } - public CMO_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public CMO_Series(TSeries source) : this(source, 0, false) { } - public CMO_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public CMO_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"CMO({period})"; + } + public CMO_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public CMO_Series() : this(period: 0, useNaN: false) { } + public CMO_Series(int period) : this(period: period, useNaN: false) { } + public CMO_Series(TBars source) : this(source.Close, 0, false) { } + public CMO_Series(TBars source, int period) : this(source.Close, period, false) { } + public CMO_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public CMO_Series(TSeries source) : this(source, 0, false) { } + public CMO_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { _last_value = _plast_value; } else { _plast_value = _last_value; } - BufferTrim(buffer:_buff_up, (TValue.v > _last_value) ? TValue.v - _last_value : 0, period:_period, update: update); - BufferTrim(buffer: _buff_dn, (TValue.v < _last_value) ? _last_value - TValue.v : 0, period: _period, update: update); - _last_value = TValue.v; - double _cmo_up = 0; - double _cmo_dn = 0; - for (int i = 0; i < Math.Min(_buff_up.Count, _buff_dn.Count); i++) { - _cmo_up += _buff_up[i]; - _cmo_dn += _buff_dn[i]; - } - double _cmo = 100 * (_cmo_up - _cmo_dn) / (_cmo_up + _cmo_dn); - if (_cmo_up + _cmo_dn == 0) { _cmo = 0; } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (update) { _last_value = _plast_value; } else { _plast_value = _last_value; } + BufferTrim(buffer: _buff_up, (TValue.v > _last_value) ? TValue.v - _last_value : 0, period: _period, update: update); + BufferTrim(buffer: _buff_dn, (TValue.v < _last_value) ? _last_value - TValue.v : 0, period: _period, update: update); + _last_value = TValue.v; + double _cmo_up = 0; + double _cmo_dn = 0; + for (int i = 0; i < Math.Min(_buff_up.Count, _buff_dn.Count); i++) + { + _cmo_up += _buff_up[i]; + _cmo_dn += _buff_dn[i]; + } + double _cmo = 100 * (_cmo_up - _cmo_dn) / (_cmo_up + _cmo_dn); + if (_cmo_up + _cmo_dn == 0) { _cmo = 0; } - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _cmo); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _cmo); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buff_up.Clear(); - _buff_dn.Clear(); - } + //reset calculation + public override void Reset() + { + _buff_up.Clear(); + _buff_dn.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/CUSUM_Series.cs b/Calculations/_Updated/CUSUM_Series.cs index 08897f07..38036f94 100644 --- a/Calculations/_Updated/CUSUM_Series.cs +++ b/Calculations/_Updated/CUSUM_Series.cs @@ -11,61 +11,70 @@ Sources: https://en.wikipedia.org/wiki/CUSUM */ -public class CUSUM_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); +public class CUSUM_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public CUSUM_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"CUSUM({period})"; - } - public CUSUM_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public CUSUM_Series() : this(period: 0, useNaN: false) { } - public CUSUM_Series(int period) : this(period: period, useNaN: false) { } - public CUSUM_Series(TBars source) : this(source.Close, 0, false) { } - public CUSUM_Series(TBars source, int period) : this(source.Close, period, false) { } - public CUSUM_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public CUSUM_Series(TSeries source) : this(source, period: 0, useNaN: false) { } - public CUSUM_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public CUSUM_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"CUSUM({period})"; + } + public CUSUM_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public CUSUM_Series() : this(period: 0, useNaN: false) { } + public CUSUM_Series(int period) : this(period: period, useNaN: false) { } + public CUSUM_Series(TBars source) : this(source.Close, 0, false) { } + public CUSUM_Series(TBars source, int period) : this(source.Close, period, false) { } + public CUSUM_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public CUSUM_Series(TSeries source) : this(source, period: 0, useNaN: false) { } + public CUSUM_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _sum = 0; - for (int i = 0; i < _buffer.Count; i++) { _sum += _buffer[i]; } - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _sum); - return base.Add(res, update); - } + double _sum = 0; + for (int i = 0; i < _buffer.Count; i++) { _sum += _buffer[i]; } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _sum); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/DECAY_Series.cs b/Calculations/_Updated/DECAY_Series.cs index 263875c3..8236956e 100644 --- a/Calculations/_Updated/DECAY_Series.cs +++ b/Calculations/_Updated/DECAY_Series.cs @@ -13,71 +13,81 @@ DECAY: */ -public class DECAY_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly bool _exp; - private double _pdecay, _ppdecay; - private readonly double _dfactor; +public class DECAY_Series : TSeries +{ + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly bool _exp; + private double _pdecay, _ppdecay; + private readonly double _dfactor; - //core constructors - public DECAY_Series(int period, bool exponential, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"DECAY({period})"; - _exp = exponential; - _dfactor = (_exp) ? 1.0 - 1.0 / (double)_period : 1 / (double)_period; - _pdecay = _ppdecay = 0; - } - public DECAY_Series(TSeries source, int period, bool exponential, bool useNaN) : this(period, exponential, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public DECAY_Series() : this(period: 0, exponential: false, useNaN: false) { } - public DECAY_Series(int period) : this(period: period, exponential: false, useNaN: false) { } - public DECAY_Series(TBars source) : this(source.Close, period: 0, exponential: false, useNaN: false) { } - public DECAY_Series(TBars source, int period) : this(source.Close, period: period, exponential:false, useNaN:false) { } - public DECAY_Series(TBars source, int period, bool useNaN) : this(source.Close, period: period, exponential: false, useNaN) { } - public DECAY_Series(TSeries source) : this(source, period: 0, exponential: false, useNaN:false) { } - public DECAY_Series(TSeries source, int period) : this(source: source, period: period, exponential: false, useNaN: false) { } - public DECAY_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, exponential: false, useNaN: useNaN) { } + //core constructors + public DECAY_Series(int period, bool exponential, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"DECAY({period})"; + _exp = exponential; + _dfactor = (_exp) ? 1.0 - 1.0 / (double)_period : 1 / (double)_period; + _pdecay = _ppdecay = 0; + } + public DECAY_Series(TSeries source, int period, bool exponential, bool useNaN) : this(period, exponential, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public DECAY_Series() : this(period: 0, exponential: false, useNaN: false) { } + public DECAY_Series(int period) : this(period: period, exponential: false, useNaN: false) { } + public DECAY_Series(TBars source) : this(source.Close, period: 0, exponential: false, useNaN: false) { } + public DECAY_Series(TBars source, int period) : this(source.Close, period: period, exponential: false, useNaN: false) { } + public DECAY_Series(TBars source, int period, bool useNaN) : this(source.Close, period: period, exponential: false, useNaN) { } + public DECAY_Series(TSeries source) : this(source, period: 0, exponential: false, useNaN: false) { } + public DECAY_Series(TSeries source, int period) : this(source: source, period: period, exponential: false, useNaN: false) { } + public DECAY_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, exponential: false, useNaN: useNaN) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - if (update) { _pdecay = _ppdecay; } - else { _ppdecay = _pdecay; } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, Double.NaN), update); + } + if (update) { _pdecay = _ppdecay; } + else { _ppdecay = _pdecay; } - if (this.Count == 0) { _pdecay = TValue.v; } - double _decay = Math.Max(TValue.v, Math.Max((_exp) ? _pdecay * _dfactor : _pdecay - _dfactor, 0)); - _pdecay = _decay; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _decay); - return base.Add(res, update); - } + if (this.Count == 0) { _pdecay = TValue.v; } + double _decay = Math.Max(TValue.v, Math.Max((_exp) ? _pdecay * _dfactor : _pdecay - _dfactor, 0)); + _pdecay = _decay; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _decay); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _pdecay = _ppdecay = 0; - } + //reset calculation + public override void Reset() + { + _pdecay = _ppdecay = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/DEMA_Series.cs b/Calculations/_Updated/DEMA_Series.cs index dffd9f71..8bcf0ed7 100644 --- a/Calculations/_Updated/DEMA_Series.cs +++ b/Calculations/_Updated/DEMA_Series.cs @@ -17,111 +17,128 @@ Remark: */ -public class DEMA_Series : TSeries { - private double _k; - private double _sum, _oldsum; - private double _lastema1, _oldema1, _lastema2, _oldema2; - private int _len; - private readonly bool _useSMA; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class DEMA_Series : TSeries +{ + private double _k; + private double _sum, _oldsum; + private double _lastema1, _oldema1, _lastema2, _oldema2; + private int _len; + private readonly bool _useSMA; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; -//core constructor - public DEMA_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - _useSMA = useSMA; - Name = $"DEMA({period})"; - _k = 2.0 / (_period + 1); - _len = 0; - _sum = _oldsum = _lastema1 = _lastema2 = 0; - } - //generic constructors (source) + //core constructor + public DEMA_Series(int period, bool useNaN, bool useSMA) + { + _period = period; + _NaN = useNaN; + _useSMA = useSMA; + Name = $"DEMA({period})"; + _k = 2.0 / (_period + 1); + _len = 0; + _sum = _oldsum = _lastema1 = _lastema2 = 0; + } + //generic constructors (source) - public DEMA_Series() : this(0, false, true) {} - public DEMA_Series(int period) : this(period, false, true) {} - public DEMA_Series(TBars source) : this(source.Close, 0, false) {} - public DEMA_Series(TBars source, int period) : this(source.Close, period, false) {} - public DEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} - public DEMA_Series(TSeries source, int period) : this(source, period, false, true) {} - public DEMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} - public DEMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } + public DEMA_Series() : this(0, false, true) { } + public DEMA_Series(int period) : this(period, false, true) { } + public DEMA_Series(TBars source) : this(source.Close, 0, false) { } + public DEMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public DEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public DEMA_Series(TSeries source, int period) : this(source, period, false, true) { } + public DEMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) { } + public DEMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } -// core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { - _lastema1 = _oldema1; - _lastema2 = _oldema2; - _sum = _oldsum; - } - else { - _oldema1 = _lastema1; - _oldema2 = _lastema2; - _oldsum = _sum; - _len++; - } + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (update) + { + _lastema1 = _oldema1; + _lastema2 = _oldema2; + _sum = _oldsum; + } + else + { + _oldema1 = _lastema1; + _oldema2 = _lastema2; + _oldsum = _sum; + _len++; + } - if (_period == 0) { - _k = 2.0 / (_len + 1); - } + if (_period == 0) + { + _k = 2.0 / (_len + 1); + } - double _ema1, _ema2, _dema; - if (Count == 0) { - _ema1 = _ema2 = _sum = TValue.v; - } - else if (_len <= _period && _useSMA && _period != 0) { - _sum += TValue.v; - _ema1 = _sum / Math.Min(_len, _period); - _ema2 = _ema1; - } - else { - _ema1 = (TValue.v - _lastema1) * _k + _lastema1; - _ema2 = (_ema1 - _lastema2) * _k + _lastema2; - } + double _ema1, _ema2, _dema; + if (Count == 0) + { + _ema1 = _ema2 = _sum = TValue.v; + } + else if (_len <= _period && _useSMA && _period != 0) + { + _sum += TValue.v; + _ema1 = _sum / Math.Min(_len, _period); + _ema2 = _ema1; + } + else + { + _ema1 = (TValue.v - _lastema1) * _k + _lastema1; + _ema2 = (_ema1 - _lastema2) * _k + _lastema2; + } - _dema = 2 * _ema1 - _ema2; + _dema = 2 * _ema1 - _ema2; - _lastema1 = double.IsNaN(_ema1) ? _lastema1 : _ema1; - _lastema2 = double.IsNaN(_ema2) ? _lastema2 : _ema2; + _lastema1 = double.IsNaN(_ema1) ? _lastema1 : _ema1; + _lastema2 = double.IsNaN(_ema2) ? _lastema2 : _ema2; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _dema); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _dema); + return base.Add(res, update); + } -//variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { - return (DateTime.Today, double.NaN); - } + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) + { + return (DateTime.Today, double.NaN); + } - foreach (var item in data) { - Add(item, false); - } + foreach (var item in data) + { + Add(item, false); + } - return _data.Last; - } + return _data.Last; + } - public (DateTime t, double v) Add(bool update) { - return Add(_data.Last, update); - } + public (DateTime t, double v) Add(bool update) + { + return Add(_data.Last, update); + } - public (DateTime t, double v) Add() { - return Add(_data.Last, false); - } + public (DateTime t, double v) Add() + { + return Add(_data.Last, false); + } - private new void Sub(object source, TSeriesEventArgs e) { - Add(_data.Last, e.update); - } - - //reset calculation - public override void Reset() { - _sum = _oldsum = _lastema1 = _lastema2 = 0; - _len = 0; - } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(_data.Last, e.update); + } + + //reset calculation + public override void Reset() + { + _sum = _oldsum = _lastema1 = _lastema2 = 0; + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/DWMA_Series.cs b/Calculations/_Updated/DWMA_Series.cs index 20283c34..d9d9b847 100644 --- a/Calculations/_Updated/DWMA_Series.cs +++ b/Calculations/_Updated/DWMA_Series.cs @@ -11,112 +11,133 @@ DWMA: Double Weighted Moving Average */ -public class DWMA_Series : TSeries { - private readonly List _buffer = new(); - private List _weights; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - protected int _len; +public class DWMA_Series : TSeries +{ + private readonly List _buffer = new(); + private List _weights; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + protected int _len; -//core constructors - public DWMA_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"DWMA({period})"; - _len = 0; - _weights = CalculateWeights(_period); - } + //core constructors + public DWMA_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"DWMA({period})"; + _len = 0; + _weights = CalculateWeights(_period); + } - public DWMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } + public DWMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } - public DWMA_Series() : this(0, false) { - } + public DWMA_Series() : this(0, false) + { + } - public DWMA_Series(int period) : this(period, false) { - } + public DWMA_Series(int period) : this(period, false) + { + } - public DWMA_Series(TBars source) : this(source.Close, 0, false) { - } + public DWMA_Series(TBars source) : this(source.Close, 0, false) + { + } - public DWMA_Series(TBars source, int period) : this(source.Close, period, false) { - } + public DWMA_Series(TBars source, int period) : this(source.Close, period, false) + { + } - public DWMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { - } + public DWMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) + { + } - public DWMA_Series(TSeries source, int period) : this(source, period, false) { - } + public DWMA_Series(TSeries source, int period) : this(source, period, false) + { + } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(_buffer, TValue.v, _period, update); - if (_period == 0) { - _len++; - _weights = CalculateWeights(_len); - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(_buffer, TValue.v, _period, update); + if (_period == 0) + { + _len++; + _weights = CalculateWeights(_len); + } - double _dwma = 0, _wsum = 0; - var bufferCount = _buffer.Count; + double _dwma = 0, _wsum = 0; + var bufferCount = _buffer.Count; - var lockObj = new object(); - Parallel.For(0, bufferCount, i => - { - var temp = _buffer[i] * _weights[i]; - lock (lockObj) { - _dwma += temp; - _wsum += _weights[i]; - } - }); - _dwma /= _wsum; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _dwma); - return base.Add(res, update); - } + var lockObj = new object(); + Parallel.For(0, bufferCount, i => + { + var temp = _buffer[i] * _weights[i]; + lock (lockObj) + { + _dwma += temp; + _wsum += _weights[i]; + } + }); + _dwma /= _wsum; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _dwma); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { - return (DateTime.Today, double.NaN); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) + { + return (DateTime.Today, double.NaN); + } - foreach (var item in data) { - Add(item, false); - } + foreach (var item in data) + { + Add(item, false); + } - return _data.Last; - } + return _data.Last; + } - public (DateTime t, double v) Add(bool update) { - return Add(_data.Last, update); - } + public (DateTime t, double v) Add(bool update) + { + return Add(_data.Last, update); + } - public (DateTime t, double v) Add() { - return Add(_data.Last, false); - } + public (DateTime t, double v) Add() + { + return Add(_data.Last, false); + } - private new void Sub(object source, TSeriesEventArgs e) { - Add(_data.Last, e.update); - } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(_data.Last, e.update); + } - //calculating weights - private static List CalculateWeights(int period) { - var weights = new List(period); - for (var i = 0; i < period; i++) { - weights.Add((i + 1) * (i + 1)); - } + //calculating weights + private static List CalculateWeights(int period) + { + var weights = new List(period); + for (var i = 0; i < period; i++) + { + weights.Add((i + 1) * (i + 1)); + } - return weights; - } + return weights; + } - //reset calculation - public override void Reset() { - _len = 0; - _buffer.Clear(); - _weights = CalculateWeights(_period); - } + //reset calculation + public override void Reset() + { + _len = 0; + _buffer.Clear(); + _weights = CalculateWeights(_period); + } } \ No newline at end of file diff --git a/Calculations/_Updated/EMA_Series.cs b/Calculations/_Updated/EMA_Series.cs index 174d978a..26edf8ef 100644 --- a/Calculations/_Updated/EMA_Series.cs +++ b/Calculations/_Updated/EMA_Series.cs @@ -21,100 +21,116 @@ Issues: */ -public class EMA_Series : TSeries { - private double _k; - private double _lastema, _oldema; - private double _sum, _oldsum; - private int _len; - private readonly bool _useSMA; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class EMA_Series : TSeries +{ + private double _k; + private double _lastema, _oldema; + private double _sum, _oldsum; + private int _len; + private readonly bool _useSMA; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; -//core constructors + //core constructors - public EMA_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - _useSMA = useSMA; - Name = $"EMA({period})"; - _k = 2.0 / (_period + 1); - _len = 0; - _sum = _oldsum = _lastema = _oldema = 0; - } - public EMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public EMA_Series() : this(0, false, true) {} - public EMA_Series(int period) : this(period, false, true) {} - public EMA_Series(TBars source) : this(source.Close, 0, false) {} - public EMA_Series(TBars source, int period) : this(source.Close, period, false) {} - public EMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} - public EMA_Series(TSeries source, int period) : this(source, period, false, true) {} - public EMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} + public EMA_Series(int period, bool useNaN, bool useSMA) + { + _period = period; + _NaN = useNaN; + _useSMA = useSMA; + Name = $"EMA({period})"; + _k = 2.0 / (_period + 1); + _len = 0; + _sum = _oldsum = _lastema = _oldema = 0; + } + public EMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public EMA_Series() : this(0, false, true) { } + public EMA_Series(int period) : this(period, false, true) { } + public EMA_Series(TBars source) : this(source.Close, 0, false) { } + public EMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public EMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public EMA_Series(TSeries source, int period) : this(source, period, false, true) { } + public EMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { - _lastema = _oldema; - _sum = _oldsum; - } - else { - _oldema = _lastema; - _oldsum = _sum; - _len++; - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (update) + { + _lastema = _oldema; + _sum = _oldsum; + } + else + { + _oldema = _lastema; + _oldsum = _sum; + _len++; + } - double _ema = 0; - if (_period == 0) { - _k = 2.0 / (_len + 1); - } + double _ema = 0; + if (_period == 0) + { + _k = 2.0 / (_len + 1); + } - if (Count == 0) { - _ema = _sum = TValue.v; - } - else if (_len <= _period && _useSMA && _period != 0) { - _sum += TValue.v; - if (_period != 0 && _len > _period) { - _sum -= _data[Count - _period - (update ? 1 : 0)].v; - } + if (Count == 0) + { + _ema = _sum = TValue.v; + } + else if (_len <= _period && _useSMA && _period != 0) + { + _sum += TValue.v; + if (_period != 0 && _len > _period) + { + _sum -= _data[Count - _period - (update ? 1 : 0)].v; + } - _ema = _sum / Math.Min(_len, _period); - } - else { - _ema = _k * (TValue.v - _lastema) + _lastema; - } + _ema = _sum / Math.Min(_len, _period); + } + else + { + _ema = _k * (TValue.v - _lastema) + _lastema; + } - _lastema = double.IsNaN(_ema) ? _lastema : _ema; + _lastema = double.IsNaN(_ema) ? _lastema : _ema; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _ema); - return base.Add(res, update); - } - -//variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _sum = _oldsum = _lastema = _oldema = 0; - _len = 0; - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _ema); + return base.Add(res, update); + } + + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() + { + _sum = _oldsum = _lastema = _oldema = 0; + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/ENTROPY_Series.cs b/Calculations/_Updated/ENTROPY_Series.cs index 27592dfd..6c3bd399 100644 --- a/Calculations/_Updated/ENTROPY_Series.cs +++ b/Calculations/_Updated/ENTROPY_Series.cs @@ -18,70 +18,80 @@ Sources: */ -public class ENTROPY_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly double _logbase; - private readonly System.Collections.Generic.List _buffer = new(); - private readonly System.Collections.Generic.List _buff2 = new(); +public class ENTROPY_Series : TSeries +{ + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly double _logbase; + private readonly System.Collections.Generic.List _buffer = new(); + private readonly System.Collections.Generic.List _buff2 = new(); - //core constructors - public ENTROPY_Series(int period, double logbase, bool useNaN) { - _period = period; - _NaN = useNaN; - _logbase = logbase; - Name = $"ENTROPY({period})"; - } - public ENTROPY_Series(TSeries source, int period, double logbase, bool useNaN) : this(period, logbase, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public ENTROPY_Series() : this(period: 0, logbase: 2.0, useNaN: false) { } - public ENTROPY_Series(int period) : this(period: period, logbase: 2.0, useNaN: false) { } - public ENTROPY_Series(TBars source) : this(source.Close, period: 0, logbase: 2.0, useNaN: false) { } - public ENTROPY_Series(TBars source, int period) : this(source.Close, period, logbase: 2.0, useNaN: false) { } - public ENTROPY_Series(TBars source, int period, bool useNaN) : this(source.Close, period: period, logbase: 2.0, useNaN: useNaN) { } - public ENTROPY_Series(TSeries source) : this(source, period: 0, logbase: 2.0, useNaN: false) { } - public ENTROPY_Series(TSeries source, int period) : this(source: source, period: period, logbase: 2.0, useNaN: false) { } - public ENTROPY_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, logbase: 2.0, useNaN: useNaN) { } + //core constructors + public ENTROPY_Series(int period, double logbase, bool useNaN) + { + _period = period; + _NaN = useNaN; + _logbase = logbase; + Name = $"ENTROPY({period})"; + } + public ENTROPY_Series(TSeries source, int period, double logbase, bool useNaN) : this(period, logbase, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public ENTROPY_Series() : this(period: 0, logbase: 2.0, useNaN: false) { } + public ENTROPY_Series(int period) : this(period: period, logbase: 2.0, useNaN: false) { } + public ENTROPY_Series(TBars source) : this(source.Close, period: 0, logbase: 2.0, useNaN: false) { } + public ENTROPY_Series(TBars source, int period) : this(source.Close, period, logbase: 2.0, useNaN: false) { } + public ENTROPY_Series(TBars source, int period, bool useNaN) : this(source.Close, period: period, logbase: 2.0, useNaN: useNaN) { } + public ENTROPY_Series(TSeries source) : this(source, period: 0, logbase: 2.0, useNaN: false) { } + public ENTROPY_Series(TSeries source, int period) : this(source: source, period: period, logbase: 2.0, useNaN: false) { } + public ENTROPY_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, logbase: 2.0, useNaN: useNaN) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _sum = _buffer.Sum(); - double _pp = this._buffer[^1] / _sum; - double _ppp = -_pp * Math.Log(_pp) / Math.Log(this._logbase); - BufferTrim(_buff2, _ppp, _period, update); - double _entp = _buff2.Sum(); - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _entp); - return base.Add(res, update); - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, Double.NaN), update); + } + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + double _sum = _buffer.Sum(); + double _pp = this._buffer[^1] / _sum; + double _ppp = -_pp * Math.Log(_pp) / Math.Log(this._logbase); + BufferTrim(_buff2, _ppp, _period, update); + double _entp = _buff2.Sum(); + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _entp); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - _buff2.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + _buff2.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/FWMA_Series.cs b/Calculations/_Updated/FWMA_Series.cs index 81134148..f9976814 100644 --- a/Calculations/_Updated/FWMA_Series.cs +++ b/Calculations/_Updated/FWMA_Series.cs @@ -10,85 +10,96 @@ FWMA: Fibonacci's Weighted Moving Average is similar to a Weighted Moving Averag (WMA) where the weights are based on the Fibonacci Sequence. */ -public class FWMA_Series : TSeries { - private readonly List _buffer = new(); - private List _weights; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - protected int _len; +public class FWMA_Series : TSeries +{ + private readonly List _buffer = new(); + private List _weights; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + protected int _len; - public FWMA_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"FWMA({period})"; - _len = 0; - _weights = CalculateWeights(_period); - } + public FWMA_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"FWMA({period})"; + _len = 0; + _weights = CalculateWeights(_period); + } - public FWMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } + public FWMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } - public FWMA_Series() : this(period: 0, useNaN: false) { } - public FWMA_Series(int period) : this(period: period, useNaN: false) { } - public FWMA_Series(TBars source) : this(source.Close, 0, false) { } - public FWMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public FWMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public FWMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + public FWMA_Series() : this(period: 0, useNaN: false) { } + public FWMA_Series(int period) : this(period: period, useNaN: false) { } + public FWMA_Series(TBars source) : this(source.Close, 0, false) { } + public FWMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public FWMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public FWMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - if (_period == 0) { - _len++; - _weights = CalculateWeights(_len); - } - double _fwma = 0; - double totalWeights = _weights.Sum(); - object lockObj = new object(); - Parallel.For(0, _buffer.Count, i => - { - double temp = _buffer[i] * _weights[i]; - lock (lockObj) { _fwma += temp; } - }); - _fwma /= totalWeights; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _fwma); - return base.Add(res, update); - } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + if (_period == 0) + { + _len++; + _weights = CalculateWeights(_len); + } + double _fwma = 0; + double totalWeights = _weights.Sum(); + object lockObj = new object(); + Parallel.For(0, _buffer.Count, i => + { + double temp = _buffer[i] * _weights[i]; + lock (lockObj) { _fwma += temp; } + }); + _fwma /= totalWeights; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _fwma); + return base.Add(res, update); + } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - private static List CalculateWeights(int period) { - //to prevent overflow, max period can be no more than 1476 - period = (period > 1476) ? 1476 : period; - List weights = new List(period); - BigInteger a = 0; - BigInteger b = 1; - for (int i = 0; i < period; i++) { - BigInteger temp = a; - a = b; - b = temp + b; - weights.Add((double)Decimal.Parse(a.ToString())); - } - return weights; - } + private static List CalculateWeights(int period) + { + //to prevent overflow, max period can be no more than 1476 + period = (period > 1476) ? 1476 : period; + List weights = new List(period); + BigInteger a = 0; + BigInteger b = 1; + for (int i = 0; i < period; i++) + { + BigInteger temp = a; + a = b; + b = temp + b; + weights.Add((double)Decimal.Parse(a.ToString())); + } + return weights; + } - public override void Reset() { - _weights = CalculateWeights(_period); - _buffer.Clear(); - } + public override void Reset() + { + _weights = CalculateWeights(_period); + _buffer.Clear(); + } } diff --git a/Calculations/_Updated/HEMA_Series.cs b/Calculations/_Updated/HEMA_Series.cs index 60aacda4..c52f6c06 100644 --- a/Calculations/_Updated/HEMA_Series.cs +++ b/Calculations/_Updated/HEMA_Series.cs @@ -13,104 +13,121 @@ Raw HMA = (2 * EMA1) - EMA2 EMA3 = EMA(sqrt(n)) of Raw HMA - where k = 2/(sqrt(n)+1) */ -public class HEMA_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private double _k1, _k2, _k3; - private int _len; - private double _lastema1, _oldema1; - private double _lastema2, _oldema2; - private double _lasthema, _oldhema; +public class HEMA_Series : TSeries +{ + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private double _k1, _k2, _k3; + private int _len; + private double _lastema1, _oldema1; + private double _lastema2, _oldema2; + private double _lasthema, _oldhema; - //core constructors - public HEMA_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"HEMA({period})"; - (_k1, _k2, _k3) = CalculateK(_period); - _len = 0; - _lastema1 = _oldema1 = _lastema2 = _oldema2 = _lasthema = _oldhema = 0; - } - public HEMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public HEMA_Series() : this(period: 0, useNaN: false) { } - public HEMA_Series(int period) : this(period: period, useNaN: false) { } - public HEMA_Series(TBars source) : this(source.Close, 0, false) { } - public HEMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public HEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public HEMA_Series(TSeries source) : this(source, 0, false) { } - public HEMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public HEMA_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"HEMA({period})"; + (_k1, _k2, _k3) = CalculateK(_period); + _len = 0; + _lastema1 = _oldema1 = _lastema2 = _oldema2 = _lasthema = _oldhema = 0; + } + public HEMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public HEMA_Series() : this(period: 0, useNaN: false) { } + public HEMA_Series(int period) : this(period: period, useNaN: false) { } + public HEMA_Series(TBars source) : this(source.Close, 0, false) { } + public HEMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public HEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public HEMA_Series(TSeries source) : this(source, 0, false) { } + public HEMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { - _lastema1 = _oldema1; - _lastema2 = _oldema2; - _lasthema = _oldhema; - } - else { - _oldema1 = _lastema1; - _oldema2 = _lastema2; - _oldhema = _lasthema; - } - double _ema1, _ema2, _hema; - if (_period == 0) { - _len++; - (_k1, _k2, _k3) = CalculateK(_len); - } - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, double.NaN), update); - } else if (this.Count == 0) { - _ema1 = _ema2 = _hema = TValue.v; - } - else { - _ema1 = _k1 * (TValue.v - _lastema1) + _lastema1; - _ema2 = _k2 * (TValue.v - _lastema2) + _lastema2; - _hema = _k3 * (((2 * _ema1) - _ema2) - _lasthema) + _lasthema; - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (update) + { + _lastema1 = _oldema1; + _lastema2 = _oldema2; + _lasthema = _oldhema; + } + else + { + _oldema1 = _lastema1; + _oldema2 = _lastema2; + _oldhema = _lasthema; + } + double _ema1, _ema2, _hema; + if (_period == 0) + { + _len++; + (_k1, _k2, _k3) = CalculateK(_len); + } + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, double.NaN), update); + } + else if (this.Count == 0) + { + _ema1 = _ema2 = _hema = TValue.v; + } + else + { + _ema1 = _k1 * (TValue.v - _lastema1) + _lastema1; + _ema2 = _k2 * (TValue.v - _lastema2) + _lastema2; + _hema = _k3 * (((2 * _ema1) - _ema2) - _lasthema) + _lasthema; + } - _lastema1 = _ema1; - _lastema2 = _ema2; - _lasthema = _hema; + _lastema1 = _ema1; + _lastema2 = _ema2; + _lasthema = _hema; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _hema); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _hema); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _lastema1 = _lastema2 = _lasthema = 0; - _oldema1 = _oldema2 = _oldhema = 0; - _len = 0; - } + //reset calculation + public override void Reset() + { + _lastema1 = _lastema2 = _lasthema = 0; + _oldema1 = _oldema2 = _oldhema = 0; + _len = 0; + } - public static (double k1, double k2, double k3) CalculateK(int len) { - double k1 = 8 / (double)(len + 7); - double k2 = 3 / (double)(len + 2); - double k3 = 2 / Math.Sqrt(len + 3); + public static (double k1, double k2, double k3) CalculateK(int len) + { + double k1 = 8 / (double)(len + 7); + double k2 = 3 / (double)(len + 2); + double k3 = 2 / Math.Sqrt(len + 3); - return (k1, k2, k3); - } + return (k1, k2, k3); + } } \ No newline at end of file diff --git a/Calculations/_Updated/HMA_Series.cs b/Calculations/_Updated/HMA_Series.cs index 01736639..4401886e 100644 --- a/Calculations/_Updated/HMA_Series.cs +++ b/Calculations/_Updated/HMA_Series.cs @@ -18,71 +18,81 @@ HMA = WMA(sqrt(n)) of Raw HMA */ -public class HMA_Series : TSeries { - protected int _period, _period2, _psqrt; - protected readonly bool _NaN; - protected readonly TSeries _data; - protected WMA_Series _wma1, _wma2, _wma3; +public class HMA_Series : TSeries +{ + protected int _period, _period2, _psqrt; + protected readonly bool _NaN; + protected readonly TSeries _data; + protected WMA_Series _wma1, _wma2, _wma3; - //core constructors - public HMA_Series(int period, bool useNaN) { - _period = period; - _period2 = period /2; - _psqrt = (int)Math.Sqrt(period); - _NaN = useNaN; - _wma1 = new(Math.Max(_period2,1), false); - _wma2 = new(Math.Max(_period,1), false); - _wma3 = new(Math.Max(_psqrt,1), useNaN); - Name = $"HMA({period})"; - } - public HMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public HMA_Series() : this(period: 0, useNaN: false) { } - public HMA_Series(int period) : this(period: period, useNaN: false) { } - public HMA_Series(TBars source) : this(source.Close, 0, false) { } - public HMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public HMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public HMA_Series(TSeries source) : this(source, 0, false) { } - public HMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public HMA_Series(int period, bool useNaN) + { + _period = period; + _period2 = period / 2; + _psqrt = (int)Math.Sqrt(period); + _NaN = useNaN; + _wma1 = new(Math.Max(_period2, 1), false); + _wma2 = new(Math.Max(_period, 1), false); + _wma3 = new(Math.Max(_psqrt, 1), useNaN); + Name = $"HMA({period})"; + } + public HMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public HMA_Series() : this(period: 0, useNaN: false) { } + public HMA_Series(int period) : this(period: period, useNaN: false) { } + public HMA_Series(TBars source) : this(source.Close, 0, false) { } + public HMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public HMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public HMA_Series(TSeries source) : this(source, 0, false) { } + public HMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (_period == 0) { - _wma1.Len = this.Count / 2; - _wma2.Len = this.Count; - _wma1.Len = (int)Math.Sqrt(this.Count); - } - double _w1 = _wma1.Add(TValue, update).v; - double _w2 = _wma2.Add(TValue, update).v; - double _hma = _wma3.Add((2 * _w1) - _w2, update).v; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _hma); - return base.Add(res, update); - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (_period == 0) + { + _wma1.Len = this.Count / 2; + _wma2.Len = this.Count; + _wma1.Len = (int)Math.Sqrt(this.Count); + } + double _w1 = _wma1.Add(TValue, update).v; + double _w2 = _wma2.Add(TValue, update).v; + double _hma = _wma3.Add((2 * _w1) - _w2, update).v; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _hma); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _wma1.Reset(); - _wma2.Reset(); - _wma3.Reset(); - } + //reset calculation + public override void Reset() + { + _wma1.Reset(); + _wma2.Reset(); + _wma3.Reset(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/HWMA_Series.cs b/Calculations/_Updated/HWMA_Series.cs index 8f360b52..6e3f7896 100644 --- a/Calculations/_Updated/HWMA_Series.cs +++ b/Calculations/_Updated/HWMA_Series.cs @@ -31,102 +31,116 @@ HWMA[i] = F[i] + V[i] + 0.5 * A[i] */ -public class HWMA_Series : TSeries { - private int _len; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - double _nA, _nB, _nC; - double _pF, _pV, _pA; - double _ppF, _ppV, _ppA; +public class HWMA_Series : TSeries +{ + private int _len; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + double _nA, _nB, _nC; + double _pF, _pV, _pA; + double _ppF, _ppV, _ppA; - //core constructors + //core constructors - public HWMA_Series(double nA, double nB, double nC, bool useNaN) { - _period = (int)((2 - nA) / nA); - _nA = nA; - _nB = nB; - _nC = nC; - _NaN = useNaN; - Name = $"HWMA({_period})"; - _len = 0; - } - public HWMA_Series(TSeries source, double nA, double nB, double nC, bool useNaN = false) : this(nA, nB, nC, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public HWMA_Series() : this(period: 0, useNaN: false) { } - public HWMA_Series(int period) : this(period, useNaN: false) { } - public HWMA_Series(int period, bool useNaN) : this(nA: 2 / (1 + (double)period), nB: 1 / (double)period, nC: 1 / (double)period, useNaN) { - _period = period; - } - public HWMA_Series(TBars source) : this(source.Close, period: 0, useNaN: false) { } - public HWMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public HWMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public HWMA_Series(TSeries source, int period) : this(source, period, false) { } - public HWMA_Series(TSeries source, int period, bool useNaN) : this(source, nA: 2 / (1 + (double)period), nB: 1 / (double)period, nC: 1 / (double)period, useNaN: useNaN) { } + public HWMA_Series(double nA, double nB, double nC, bool useNaN) + { + _period = (int)((2 - nA) / nA); + _nA = nA; + _nB = nB; + _nC = nC; + _NaN = useNaN; + Name = $"HWMA({_period})"; + _len = 0; + } + public HWMA_Series(TSeries source, double nA, double nB, double nC, bool useNaN = false) : this(nA, nB, nC, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public HWMA_Series() : this(period: 0, useNaN: false) { } + public HWMA_Series(int period) : this(period, useNaN: false) { } + public HWMA_Series(int period, bool useNaN) : this(nA: 2 / (1 + (double)period), nB: 1 / (double)period, nC: 1 / (double)period, useNaN) + { + _period = period; + } + public HWMA_Series(TBars source) : this(source.Close, period: 0, useNaN: false) { } + public HWMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public HWMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public HWMA_Series(TSeries source, int period) : this(source, period, false) { } + public HWMA_Series(TSeries source, int period, bool useNaN) : this(source, nA: 2 / (1 + (double)period), nB: 1 / (double)period, nC: 1 / (double)period, useNaN: useNaN) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - double _F, _V, _A; - if (_len == 0) { _pF = TValue.v; _pA = _pV = 0; } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, Double.NaN), update); + } + double _F, _V, _A; + if (_len == 0) { _pF = TValue.v; _pA = _pV = 0; } - if (update) { _pF = _ppF; _pV = _ppV; _pA = _ppA; } - else { - _ppF = _pF; - _ppV = _pV; - _ppA = _pA; - _len++; - } + if (update) { _pF = _ppF; _pV = _ppV; _pA = _ppA; } + else + { + _ppF = _pF; + _ppV = _pV; + _ppA = _pA; + _len++; + } - if (_period == 0) { - _nA = 2 / (1 + (double)_len); - _nB = 1 / (double)_len; - _nC = 1 / (double)_len; - } - if (_period == 1) { - _nA = 1; - _nB = 0; - _nC = 0; - } + if (_period == 0) + { + _nA = 2 / (1 + (double)_len); + _nB = 1 / (double)_len; + _nC = 1 / (double)_len; + } + if (_period == 1) + { + _nA = 1; + _nB = 0; + _nC = 0; + } - _F = (1 - _nA) * (_pF + _pV + 0.5 * _pA) + _nA * TValue.v; - _V = (1 - _nB) * (_pV + _pA) + _nB * (_F - _pF); - _A = (1 - _nC) * _pA + _nC * (_V - _pV); + _F = (1 - _nA) * (_pF + _pV + 0.5 * _pA) + _nA * TValue.v; + _V = (1 - _nB) * (_pV + _pA) + _nB * (_F - _pF); + _A = (1 - _nC) * _pA + _nC * (_V - _pV); - double _hwma = _F + _V + 0.5 * _A; - _pF = _F; - _pV = _V; - _pA = _A; + double _hwma = _F + _V + 0.5 * _A; + _pF = _F; + _pV = _V; + _pA = _A; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _hwma); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _hwma); + return base.Add(res, update); + } - //variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _len = 0; - } + //reset calculation + public override void Reset() + { + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/KAMA_Series.cs b/Calculations/_Updated/KAMA_Series.cs index 74065605..ae3a96cd 100644 --- a/Calculations/_Updated/KAMA_Series.cs +++ b/Calculations/_Updated/KAMA_Series.cs @@ -24,84 +24,95 @@ Remark: */ -public class KAMA_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - private double _lastkama, _lastlastkama; - private readonly double _scFast, _scSlow; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class KAMA_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + private double _lastkama, _lastlastkama; + private readonly double _scFast, _scSlow; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public KAMA_Series(int period, int fast, int slow, bool useNaN) { - _period = period; - _NaN = useNaN; - _scFast = 2.0 / (((period < fast) ? period : fast) + 1); - _scSlow = 2.0 / (slow + 1); - _lastkama = _lastlastkama = 0; - Name = $"KAMA({period})"; - } - public KAMA_Series(TSeries source, int period, int fast, int slow, bool useNaN) : this(period, fast, slow, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public KAMA_Series() : this(period: 0, fast: 2, slow: 30, useNaN: false) { } - public KAMA_Series(int period) : this(period: period, fast: 2, slow: 30, useNaN: false) { } - public KAMA_Series(TBars source) : this(source.Close, period: 0, fast: 2, slow: 30, useNaN: false) { } - public KAMA_Series(TBars source, int period) : this(source.Close, period: period, fast: 2, slow: 30, useNaN: false) { } - public KAMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period: period, fast: 2, slow: 30, useNaN: useNaN) { } - public KAMA_Series(TSeries source) : this(source, period: 0, fast: 2, slow: 30, useNaN: false) { } - public KAMA_Series(TSeries source, int period) : this(source: source, period: period, fast: 2, slow: 30, useNaN: false) { } - public KAMA_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, fast: 2, slow: 30, useNaN: useNaN) { } - public KAMA_Series(TSeries source, int period, int fast, int slow) : this(source: source, period: period, fast: fast, slow: slow, useNaN: false) { } + //core constructors + public KAMA_Series(int period, int fast, int slow, bool useNaN) + { + _period = period; + _NaN = useNaN; + _scFast = 2.0 / (((period < fast) ? period : fast) + 1); + _scSlow = 2.0 / (slow + 1); + _lastkama = _lastlastkama = 0; + Name = $"KAMA({period})"; + } + public KAMA_Series(TSeries source, int period, int fast, int slow, bool useNaN) : this(period, fast, slow, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public KAMA_Series() : this(period: 0, fast: 2, slow: 30, useNaN: false) { } + public KAMA_Series(int period) : this(period: period, fast: 2, slow: 30, useNaN: false) { } + public KAMA_Series(TBars source) : this(source.Close, period: 0, fast: 2, slow: 30, useNaN: false) { } + public KAMA_Series(TBars source, int period) : this(source.Close, period: period, fast: 2, slow: 30, useNaN: false) { } + public KAMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period: period, fast: 2, slow: 30, useNaN: useNaN) { } + public KAMA_Series(TSeries source) : this(source, period: 0, fast: 2, slow: 30, useNaN: false) { } + public KAMA_Series(TSeries source, int period) : this(source: source, period: period, fast: 2, slow: 30, useNaN: false) { } + public KAMA_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, fast: 2, slow: 30, useNaN: useNaN) { } + public KAMA_Series(TSeries source, int period, int fast, int slow) : this(source: source, period: period, fast: fast, slow: slow, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, Double.NaN), update); + } - if (update) { _lastkama = _lastlastkama; } - else { _lastlastkama = _lastkama; } - BufferTrim(buffer: _buffer, value: TValue.v, period: _period + 1, update: update); + if (update) { _lastkama = _lastlastkama; } + else { _lastlastkama = _lastkama; } + BufferTrim(buffer: _buffer, value: TValue.v, period: _period + 1, update: update); - double _kama = 0; - if (this.Count < _period) { _kama = TValue.v; } - else { - double _change = Math.Abs(_buffer[^1] - _buffer[(_buffer.Count > _period + 1) ? 1 : 0]); - double _sumpv = 0; - for (int i = 1; i < _buffer.Count; i++) { _sumpv += Math.Abs(_buffer[(_buffer.Count > 0) ? i : 0] - _buffer[i - 1]); } - double _er = (_sumpv == 0) ? 0 : _change / _sumpv; - double _sc = (_er * (_scFast - _scSlow)) + _scSlow; - _kama = (_lastkama + (_sc * _sc * (TValue.v - _lastkama))); - } - _lastkama = _kama; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _kama); - return base.Add(res, update); - } + double _kama = 0; + if (this.Count < _period) { _kama = TValue.v; } + else + { + double _change = Math.Abs(_buffer[^1] - _buffer[(_buffer.Count > _period + 1) ? 1 : 0]); + double _sumpv = 0; + for (int i = 1; i < _buffer.Count; i++) { _sumpv += Math.Abs(_buffer[(_buffer.Count > 0) ? i : 0] - _buffer[i - 1]); } + double _er = (_sumpv == 0) ? 0 : _change / _sumpv; + double _sc = (_er * (_scFast - _scSlow)) + _scSlow; + _kama = (_lastkama + (_sc * _sc * (TValue.v - _lastkama))); + } + _lastkama = _kama; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _kama); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - _lastkama = _lastlastkama = 0; - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + _lastkama = _lastlastkama = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/KURTOSIS_Series.cs b/Calculations/_Updated/KURTOSIS_Series.cs index 2c4e10c8..bfc972af 100644 --- a/Calculations/_Updated/KURTOSIS_Series.cs +++ b/Calculations/_Updated/KURTOSIS_Series.cs @@ -25,72 +25,83 @@ Sources: */ -public class KURTOSIS_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly System.Collections.Generic.List _buffer = new(); +public class KURTOSIS_Series : TSeries +{ + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly System.Collections.Generic.List _buffer = new(); - //core constructors - public KURTOSIS_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"KURTOSIS({period})"; - } - public KURTOSIS_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public KURTOSIS_Series() : this(period: 0, useNaN: false) { } - public KURTOSIS_Series(int period) : this(period: period, useNaN: false) { } - public KURTOSIS_Series(TSeries source) : this(source, period: 0, useNaN: false) { } - public KURTOSIS_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public KURTOSIS_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"KURTOSIS({period})"; + } + public KURTOSIS_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public KURTOSIS_Series() : this(period: 0, useNaN: false) { } + public KURTOSIS_Series(int period) : this(period: period, useNaN: false) { } + public KURTOSIS_Series(TSeries source) : this(source, period: 0, useNaN: false) { } + public KURTOSIS_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, Double.NaN), update); + } - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _n = _buffer.Count; - double _avg = _buffer.Average(); + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + double _n = _buffer.Count; + double _avg = _buffer.Average(); - double _s2 = 0; - double _s4 = 0; - for (int i = 0; i < this._buffer.Count; i++) { - _s2 += (_buffer[i] - _avg) * (_buffer[i] - _avg); - _s4 += (_buffer[i] - _avg) * (_buffer[i] - _avg) * (_buffer[i] - _avg) * (_buffer[i] - _avg); - } + double _s2 = 0; + double _s4 = 0; + for (int i = 0; i < this._buffer.Count; i++) + { + _s2 += (_buffer[i] - _avg) * (_buffer[i] - _avg); + _s4 += (_buffer[i] - _avg) * (_buffer[i] - _avg) * (_buffer[i] - _avg) * (_buffer[i] - _avg); + } - double _Vx = _s2 / (_n - 1); - double _kurt = (_n > 3) ? - (_n * (_n + 1) * _s4) / (_Vx * _Vx * (_n - 3) * (_n - 1) * (_n - 2)) - (3 * (_n - 1) * (_n - 1) / ((_n - 2) * (_n - 3))) //using Sheskin Algo - : (_s2 * _s2) / _n - 3; //using Snedecor and Cochran (1967) algo - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _kurt); - return base.Add(res, update); - } + double _Vx = _s2 / (_n - 1); + double _kurt = (_n > 3) ? + (_n * (_n + 1) * _s4) / (_Vx * _Vx * (_n - 3) * (_n - 1) * (_n - 2)) - (3 * (_n - 1) * (_n - 1) / ((_n - 2) * (_n - 3))) //using Sheskin Algo + : (_s2 * _s2) / _n - 3; //using Snedecor and Cochran (1967) algo + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _kurt); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MACD_Series.cs b/Calculations/_Updated/MACD_Series.cs index 20d8c86b..251f6577 100644 --- a/Calculations/_Updated/MACD_Series.cs +++ b/Calculations/_Updated/MACD_Series.cs @@ -11,68 +11,78 @@ MACD: Moving Average Convergence/Divergence */ -public class MACD_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); +public class MACD_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _slow, _fast, _signal; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly EMA_Series _TSlow; - private readonly EMA_Series _TFast; - public EMA_Series Signal { get; } + protected readonly int _slow, _fast, _signal; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly EMA_Series _TSlow; + private readonly EMA_Series _TFast; + public EMA_Series Signal { get; } - //core constructors - public MACD_Series(int slow = 26, int fast = 12, int signal = 9, bool useNaN = false) { - _slow = slow; - _fast = fast; - _signal = signal; - _NaN = useNaN; - Name = $"MACD({slow},{fast},{signal})"; - _TSlow = new(slow, useNaN:false, useSMA:true); - _TFast = new(fast, useNaN: false, useSMA: true); - Signal = new(signal, useNaN: false, useSMA: true); - } - public MACD_Series(TSeries source, int slow, int fast, int signal, bool useNaN) : this(slow, fast, signal, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MACD_Series(TSeries source) : this(source:source, slow:26, fast:12, signal:9 , useNaN:false) { } - public MACD_Series(TSeries source, int slow, int fast, int signal) : this(source: source, slow: slow, fast:fast, signal:signal, useNaN: false) { } + //core constructors + public MACD_Series(int slow = 26, int fast = 12, int signal = 9, bool useNaN = false) + { + _slow = slow; + _fast = fast; + _signal = signal; + _NaN = useNaN; + Name = $"MACD({slow},{fast},{signal})"; + _TSlow = new(slow, useNaN: false, useSMA: true); + _TFast = new(fast, useNaN: false, useSMA: true); + Signal = new(signal, useNaN: false, useSMA: true); + } + public MACD_Series(TSeries source, int slow, int fast, int signal, bool useNaN) : this(slow, fast, signal, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MACD_Series(TSeries source) : this(source: source, slow: 26, fast: 12, signal: 9, useNaN: false) { } + public MACD_Series(TSeries source, int slow, int fast, int signal) : this(source: source, slow: slow, fast: fast, signal: signal, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, Double.NaN), update); + } - var _sslow = _TSlow.Add(TValue,update); - var _sfast = _TFast.Add(TValue, update); - Signal.Add((TValue.t, _sfast.v-_sslow.v)); + var _sslow = _TSlow.Add(TValue, update); + var _sfast = _TFast.Add(TValue, update); + Signal.Add((TValue.t, _sfast.v - _sslow.v)); - var res = (TValue.t, Count < _fast - 1 && _NaN ? double.NaN : _sfast.v-_sslow.v); - return base.Add(res, update); - } + var res = (TValue.t, Count < _fast - 1 && _NaN ? double.NaN : _sfast.v - _sslow.v); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MAD_Series.cs b/Calculations/_Updated/MAD_Series.cs index 4f18a04b..3ba8ca1b 100644 --- a/Calculations/_Updated/MAD_Series.cs +++ b/Calculations/_Updated/MAD_Series.cs @@ -17,63 +17,72 @@ Sources: */ -public class MAD_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class MAD_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public MAD_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MAD({period})"; - } - public MAD_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MAD_Series() : this(period: 0, useNaN: false) { } - public MAD_Series(int period) : this(period: period, useNaN: false) { } - public MAD_Series(TBars source) : this(source.Close, 0, false) { } - public MAD_Series(TBars source, int period) : this(source.Close, period, false) { } - public MAD_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MAD_Series(TSeries source) : this(source, 0, false) { } - public MAD_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public MAD_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"MAD({period})"; + } + public MAD_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MAD_Series() : this(period: 0, useNaN: false) { } + public MAD_Series(int period) : this(period: period, useNaN: false) { } + public MAD_Series(TBars source) : this(source.Close, 0, false) { } + public MAD_Series(TBars source, int period) : this(source.Close, period, false) { } + public MAD_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MAD_Series(TSeries source) : this(source, 0, false) { } + public MAD_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _sma = _buffer.Average(); - double _mad = 0; - for (int i = 0; i < _buffer.Count; i++) { _mad += Math.Abs(_buffer[i] - _sma); } - _mad /= this._buffer.Count; + double _sma = _buffer.Average(); + double _mad = 0; + for (int i = 0; i < _buffer.Count; i++) { _mad += Math.Abs(_buffer[i] - _sma); } + _mad /= this._buffer.Count; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mad); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mad); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MAE_Series.cs b/Calculations/_Updated/MAE_Series.cs index 9bf7a6a9..7a8d05f6 100644 --- a/Calculations/_Updated/MAE_Series.cs +++ b/Calculations/_Updated/MAE_Series.cs @@ -14,64 +14,73 @@ Sources: */ -public class MAE_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class MAE_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public MAE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MSE({period})"; - } - public MAE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MAE_Series() : this(period: 0, useNaN: false) { } - public MAE_Series(int period) : this(period: period, useNaN: false) { } - public MAE_Series(TBars source) : this(source.Close, 0, false) { } - public MAE_Series(TBars source, int period) : this(source.Close, period, false) { } - public MAE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MAE_Series(TSeries source) : this(source, 0, false) { } - public MAE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public MAE_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"MSE({period})"; + } + public MAE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MAE_Series() : this(period: 0, useNaN: false) { } + public MAE_Series(int period) : this(period: period, useNaN: false) { } + public MAE_Series(TBars source) : this(source.Close, 0, false) { } + public MAE_Series(TBars source, int period) : this(source.Close, period, false) { } + public MAE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MAE_Series(TSeries source) : this(source, 0, false) { } + public MAE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _sma = _buffer.Average(); + double _sma = _buffer.Average(); - double _mae = 0; - for (int i = 0; i < _buffer.Count; i++) { _mae += Math.Abs(_buffer[i] - _sma); } - _mae /= this._buffer.Count; + double _mae = 0; + for (int i = 0; i < _buffer.Count; i++) { _mae += Math.Abs(_buffer[i] - _sma); } + _mae /= this._buffer.Count; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mae); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mae); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MAMA_Series.cs b/Calculations/_Updated/MAMA_Series.cs index c4dd2a51..e450337a 100644 --- a/Calculations/_Updated/MAMA_Series.cs +++ b/Calculations/_Updated/MAMA_Series.cs @@ -17,173 +17,191 @@ Sources: */ -public class MAMA_Series : TSeries { - private int _len; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class MAMA_Series : TSeries +{ + private int _len; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - private double sumPr; - private double fastl, slowl; - private (double i, double i1, double i2, double i3, double i4, double i5, double i6, double io) pr, i1, q1, sm, dt; - private (double i, double i1, double io) i2, q2, re, im, pd, ph, mama, fama; - public TSeries Fama { get; } - private double mamaseed, famaseed; + private double sumPr; + private double fastl, slowl; + private (double i, double i1, double i2, double i3, double i4, double i5, double i6, double io) pr, i1, q1, sm, dt; + private (double i, double i1, double io) i2, q2, re, im, pd, ph, mama, fama; + public TSeries Fama { get; } + private double mamaseed, famaseed; - //core constructors + //core constructors - public MAMA_Series(double fastlimit, double slowlimit, bool useNaN) { - _period = (int)(2 / fastlimit) - 1; - fastl = fastlimit; - slowl = slowlimit; - Fama = new TSeries(); - _NaN = useNaN; - Name = $"MAMA({_period})"; - _len = 0; - } - public MAMA_Series(TSeries source, double fastlimit, double slowlimit, bool useNaN = false) : this(fastlimit, slowlimit, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MAMA_Series() : this(period: 0, useNaN: false) { } - public MAMA_Series(int period) : this(period, useNaN: false) { } - public MAMA_Series(int period, bool useNaN) : this(fastlimit: 2 / (period + 1), slowlimit: 0.2 / (period + 1), useNaN) { - _period = period; - } - public MAMA_Series(TBars source) : this(source.Close, period: 0, useNaN: false) { } - public MAMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public MAMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MAMA_Series(TSeries source, int period) : this(source, period, false) { } - public MAMA_Series(TSeries source, int period, bool useNaN) : this(source, fastlimit: 2 / ((double)period + 1), slowlimit: 0.2 / ((double)period + 1), useNaN: useNaN) { } + public MAMA_Series(double fastlimit, double slowlimit, bool useNaN) + { + _period = (int)(2 / fastlimit) - 1; + fastl = fastlimit; + slowl = slowlimit; + Fama = new TSeries(); + _NaN = useNaN; + Name = $"MAMA({_period})"; + _len = 0; + } + public MAMA_Series(TSeries source, double fastlimit, double slowlimit, bool useNaN = false) : this(fastlimit, slowlimit, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MAMA_Series() : this(period: 0, useNaN: false) { } + public MAMA_Series(int period) : this(period, useNaN: false) { } + public MAMA_Series(int period, bool useNaN) : this(fastlimit: 2 / (period + 1), slowlimit: 0.2 / (period + 1), useNaN) + { + _period = period; + } + public MAMA_Series(TBars source) : this(source.Close, period: 0, useNaN: false) { } + public MAMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public MAMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MAMA_Series(TSeries source, int period) : this(source, period, false) { } + public MAMA_Series(TSeries source, int period, bool useNaN) : this(source, fastlimit: 2 / ((double)period + 1), slowlimit: 0.2 / ((double)period + 1), useNaN: useNaN) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - if (!update) { - // roll forward (oldx = x) - pr.io = pr.i6; pr.i6 = pr.i5; pr.i5 = pr.i4; pr.i4 = pr.i3; pr.i3 = pr.i2; pr.i2 = pr.i1; pr.i1 = pr.i; - i1.io = i1.i6; i1.i6 = i1.i5; i1.i5 = i1.i4; i1.i4 = i1.i3; i1.i3 = i1.i2; i1.i2 = i1.i1; i1.i1 = i1.i; - q1.io = q1.i6; q1.i6 = q1.i5; q1.i5 = q1.i4; q1.i4 = q1.i3; q1.i3 = q1.i2; q1.i2 = q1.i1; q1.i1 = q1.i; - dt.io = dt.i6; dt.i6 = dt.i5; dt.i5 = dt.i4; dt.i4 = dt.i3; dt.i3 = dt.i2; dt.i2 = dt.i1; dt.i1 = dt.i; - sm.io = sm.i6; sm.i6 = sm.i5; sm.i5 = sm.i4; sm.i4 = sm.i3; sm.i3 = sm.i2; sm.i2 = sm.i1; sm.i1 = sm.i; - i2.io = i2.i1; i2.i1 = i2.i; q2.io = q2.i1; q2.i1 = q2.i; - re.io = re.i1; re.i1 = re.i; im.io = im.i1; im.i1 = im.i; - pd.io = pd.i1; pd.i1 = pd.i; ph.io = ph.i1; ph.i1 = ph.i; - mama.io = mama.i1; mama.i1 = mama.i; - fama.io = fama.i1; - fama.i1 = fama.i; - _len++; - } - if (_period == 0) { - fastl = 2 / (double)_len; - slowl = fastl * 0.1; - } - if (_period == 1) { - fastl = 1; - slowl = 1; - } - var i = _len - 1; - pr.i = TValue.v; - if (i > 5) { - var adj = 0.075 * pd.i1 + 0.54; + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, Double.NaN), update); + } + if (!update) + { + // roll forward (oldx = x) + pr.io = pr.i6; pr.i6 = pr.i5; pr.i5 = pr.i4; pr.i4 = pr.i3; pr.i3 = pr.i2; pr.i2 = pr.i1; pr.i1 = pr.i; + i1.io = i1.i6; i1.i6 = i1.i5; i1.i5 = i1.i4; i1.i4 = i1.i3; i1.i3 = i1.i2; i1.i2 = i1.i1; i1.i1 = i1.i; + q1.io = q1.i6; q1.i6 = q1.i5; q1.i5 = q1.i4; q1.i4 = q1.i3; q1.i3 = q1.i2; q1.i2 = q1.i1; q1.i1 = q1.i; + dt.io = dt.i6; dt.i6 = dt.i5; dt.i5 = dt.i4; dt.i4 = dt.i3; dt.i3 = dt.i2; dt.i2 = dt.i1; dt.i1 = dt.i; + sm.io = sm.i6; sm.i6 = sm.i5; sm.i5 = sm.i4; sm.i4 = sm.i3; sm.i3 = sm.i2; sm.i2 = sm.i1; sm.i1 = sm.i; + i2.io = i2.i1; i2.i1 = i2.i; q2.io = q2.i1; q2.i1 = q2.i; + re.io = re.i1; re.i1 = re.i; im.io = im.i1; im.i1 = im.i; + pd.io = pd.i1; pd.i1 = pd.i; ph.io = ph.i1; ph.i1 = ph.i; + mama.io = mama.i1; mama.i1 = mama.i; + fama.io = fama.i1; + fama.i1 = fama.i; + _len++; + } + if (_period == 0) + { + fastl = 2 / (double)_len; + slowl = fastl * 0.1; + } + if (_period == 1) + { + fastl = 1; + slowl = 1; + } + var i = _len - 1; + pr.i = TValue.v; + if (i > 5) + { + var adj = 0.075 * pd.i1 + 0.54; - // smooth and detrender - sm.i = (4 * pr.i + 3 * pr.i1 + 2 * pr.i2 + pr.i3) / 10; - dt.i = (0.0962 * sm.i + 0.5769 * sm.i2 - 0.5769 * sm.i4 - 0.0962 * sm.i6) * adj; + // smooth and detrender + sm.i = (4 * pr.i + 3 * pr.i1 + 2 * pr.i2 + pr.i3) / 10; + dt.i = (0.0962 * sm.i + 0.5769 * sm.i2 - 0.5769 * sm.i4 - 0.0962 * sm.i6) * adj; - // in-phase and quadrature - q1.i = (0.0962 * dt.i + 0.5769 * dt.i2 - 0.5769 * dt.i4 - 0.0962 * dt.i6) * adj; - i1.i = dt.i3; + // in-phase and quadrature + q1.i = (0.0962 * dt.i + 0.5769 * dt.i2 - 0.5769 * dt.i4 - 0.0962 * dt.i6) * adj; + i1.i = dt.i3; - // advance the phases by 90 degrees - double jI = (0.0962 * i1.i + 0.5769 * i1.i2 - 0.5769 * i1.i4 - 0.0962 * i1.i6) * adj; - double jQ = (0.0962 * q1.i + 0.5769 * q1.i2 - 0.5769 * q1.i4 - 0.0962 * q1.i6) * adj; + // advance the phases by 90 degrees + double jI = (0.0962 * i1.i + 0.5769 * i1.i2 - 0.5769 * i1.i4 - 0.0962 * i1.i6) * adj; + double jQ = (0.0962 * q1.i + 0.5769 * q1.i2 - 0.5769 * q1.i4 - 0.0962 * q1.i6) * adj; - // phasor addition for 3-bar averaging - i2.i = i1.i - jQ; - q2.i = q1.i + jI; + // phasor addition for 3-bar averaging + i2.i = i1.i - jQ; + q2.i = q1.i + jI; - i2.i = 0.2 * i2.i + 0.8 * i2.i1; // smoothing it - q2.i = 0.2 * q2.i + 0.8 * q2.i1; + i2.i = 0.2 * i2.i + 0.8 * i2.i1; // smoothing it + q2.i = 0.2 * q2.i + 0.8 * q2.i1; - // homodyne discriminator - re.i = i2.i * i2.i1 + q2.i * q2.i1; - im.i = i2.i * q2.i1 - q2.i * i2.i1; + // homodyne discriminator + re.i = i2.i * i2.i1 + q2.i * q2.i1; + im.i = i2.i * q2.i1 - q2.i * i2.i1; - re.i = 0.2 * re.i + 0.8 * re.i1; // smoothing it - im.i = 0.2 * im.i + 0.8 * im.i1; + re.i = 0.2 * re.i + 0.8 * re.i1; // smoothing it + im.i = 0.2 * im.i + 0.8 * im.i1; - // calculate period - pd.i = im.i != 0 && re.i != 0 ? 6.283185307179586 / Math.Atan(im.i / re.i) : 0d; + // calculate period + pd.i = im.i != 0 && re.i != 0 ? 6.283185307179586 / Math.Atan(im.i / re.i) : 0d; - // adjust period to thresholds - pd.i = pd.i > 1.5 * pd.i1 ? 1.5 * pd.i1 : pd.i; - pd.i = pd.i < 0.67 * pd.i1 ? 0.67 * pd.i1 : pd.i; - pd.i = pd.i < 6d ? 6d : pd.i; - pd.i = pd.i > 50d ? 50d : pd.i; + // adjust period to thresholds + pd.i = pd.i > 1.5 * pd.i1 ? 1.5 * pd.i1 : pd.i; + pd.i = pd.i < 0.67 * pd.i1 ? 0.67 * pd.i1 : pd.i; + pd.i = pd.i < 6d ? 6d : pd.i; + pd.i = pd.i > 50d ? 50d : pd.i; - // smooth the period - pd.i = 0.2 * pd.i + 0.8 * pd.i1; + // smooth the period + pd.i = 0.2 * pd.i + 0.8 * pd.i1; - // determine phase position - ph.i = i1.i != 0 ? Math.Atan(q1.i / i1.i) * 57.29577951308232 : 0; + // determine phase position + ph.i = i1.i != 0 ? Math.Atan(q1.i / i1.i) * 57.29577951308232 : 0; - // change in phase - var delta = Math.Max(ph.i1 - ph.i, 1d); + // change in phase + var delta = Math.Max(ph.i1 - ph.i, 1d); - // adaptive alpha value - var alpha = Math.Max(fastl / delta, slowl); + // adaptive alpha value + var alpha = Math.Max(fastl / delta, slowl); - // final indicators - mama.i = alpha * (pr.i - mama.i1) + mama.i1; - fama.i = 0.5d * alpha * (mama.i - fama.i1) + fama.i1; - } - else { - sumPr += pr.i; - pd.i = sm.i = dt.i = i1.i = q1.i = i2.i = q2.i = re.i = im.i = ph.i = 0; - mama.i = fama.i = sumPr / (i + 1); + // final indicators + mama.i = alpha * (pr.i - mama.i1) + mama.i1; + fama.i = 0.5d * alpha * (mama.i - fama.i1) + fama.i1; + } + else + { + sumPr += pr.i; + pd.i = sm.i = dt.i = i1.i = q1.i = i2.i = q2.i = re.i = im.i = ph.i = 0; + mama.i = fama.i = sumPr / (i + 1); - if (_len == 1) { - mamaseed = famaseed = TValue.v; - } - else { - mamaseed = fastl * (TValue.v - mamaseed) + mamaseed; - famaseed = slowl * (TValue.v - famaseed) + famaseed; - } - } + if (_len == 1) + { + mamaseed = famaseed = TValue.v; + } + else + { + mamaseed = fastl * (TValue.v - mamaseed) + mamaseed; + famaseed = slowl * (TValue.v - famaseed) + famaseed; + } + } - double _fama = (i > 5) ? fama.i : famaseed; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _fama); - Fama.Add(res, update); - double _mama = (i > 5) ? mama.i : mamaseed; - res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mama); - return base.Add(res, update); - } + double _fama = (i > 5) ? fama.i : famaseed; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _fama); + Fama.Add(res, update); + double _mama = (i > 5) ? mama.i : mamaseed; + res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mama); + return base.Add(res, update); + } - //variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _len = 0; - } + //reset calculation + public override void Reset() + { + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/MAPE_Series.cs b/Calculations/_Updated/MAPE_Series.cs index 944daf86..b2869c63 100644 --- a/Calculations/_Updated/MAPE_Series.cs +++ b/Calculations/_Updated/MAPE_Series.cs @@ -20,66 +20,76 @@ Remark: */ -public class MAPE_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class MAPE_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public MAPE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MAPE({period})"; - } - public MAPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MAPE_Series() : this(period: 0, useNaN: false) { } - public MAPE_Series(int period) : this(period: period, useNaN: false) { } - public MAPE_Series(TBars source) : this(source.Close, 0, false) { } - public MAPE_Series(TBars source, int period) : this(source.Close, period, false) { } - public MAPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MAPE_Series(TSeries source) : this(source, 0, false) { } - public MAPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public MAPE_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"MAPE({period})"; + } + public MAPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MAPE_Series() : this(period: 0, useNaN: false) { } + public MAPE_Series(int period) : this(period: period, useNaN: false) { } + public MAPE_Series(TBars source) : this(source.Close, 0, false) { } + public MAPE_Series(TBars source, int period) : this(source.Close, period, false) { } + public MAPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MAPE_Series(TSeries source) : this(source, 0, false) { } + public MAPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _sma = _buffer.Average(); + double _sma = _buffer.Average(); - double _mape = 0; - for (int i = 0; i < _buffer.Count; i++) { - _mape += (_buffer[i] != 0) ? Math.Abs(_buffer[i] - _sma) / Math.Abs(_buffer[i]) : double.PositiveInfinity; - } - _mape /= (_buffer.Count > 0) ? _buffer.Count : 1; + double _mape = 0; + for (int i = 0; i < _buffer.Count; i++) + { + _mape += (_buffer[i] != 0) ? Math.Abs(_buffer[i] - _sma) / Math.Abs(_buffer[i]) : double.PositiveInfinity; + } + _mape /= (_buffer.Count > 0) ? _buffer.Count : 1; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mape); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mape); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MAX_Series.cs b/Calculations/_Updated/MAX_Series.cs index 8a2facff..4c96e41e 100644 --- a/Calculations/_Updated/MAX_Series.cs +++ b/Calculations/_Updated/MAX_Series.cs @@ -10,59 +10,68 @@ MAX - Maximum value in the given period in the series. */ -public class MAX_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class MAX_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public MAX_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MAX({period})"; - } - public MAX_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MAX_Series() : this(period: 0, useNaN: false) { } - public MAX_Series(int period) : this(period: period, useNaN: false) { } - public MAX_Series(TBars source) : this(source.Close, 0, false) { } - public MAX_Series(TBars source, int period) : this(source.Close, period, false) { } - public MAX_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MAX_Series(TSeries source) : this(source, 0, false) { } - public MAX_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public MAX_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"MAX({period})"; + } + public MAX_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MAX_Series() : this(period: 0, useNaN: false) { } + public MAX_Series(int period) : this(period: period, useNaN: false) { } + public MAX_Series(TBars source) : this(source.Close, 0, false) { } + public MAX_Series(TBars source, int period) : this(source.Close, period, false) { } + public MAX_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MAX_Series(TSeries source) : this(source, 0, false) { } + public MAX_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _max= _buffer.Max(); - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _max); - return base.Add(res, update); - } + double _max = _buffer.Max(); + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _max); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MEDIAN_Series.cs b/Calculations/_Updated/MEDIAN_Series.cs index fde7c634..e896d1ce 100644 --- a/Calculations/_Updated/MEDIAN_Series.cs +++ b/Calculations/_Updated/MEDIAN_Series.cs @@ -23,64 +23,73 @@ Sources: */ -public class MEDIAN_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class MEDIAN_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public MEDIAN_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MEDIAN({period})"; - } - public MEDIAN_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MEDIAN_Series() : this(period: 0, useNaN: false) { } - public MEDIAN_Series(int period) : this(period: period, useNaN: false) { } - public MEDIAN_Series(TBars source) : this(source.Close, 0, false) { } - public MEDIAN_Series(TBars source, int period) : this(source.Close, period, false) { } - public MEDIAN_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MEDIAN_Series(TSeries source) : this(source, 0, false) { } - public MEDIAN_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public MEDIAN_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"MEDIAN({period})"; + } + public MEDIAN_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MEDIAN_Series() : this(period: 0, useNaN: false) { } + public MEDIAN_Series(int period) : this(period: period, useNaN: false) { } + public MEDIAN_Series(TBars source) : this(source.Close, 0, false) { } + public MEDIAN_Series(TBars source, int period) : this(source.Close, period, false) { } + public MEDIAN_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MEDIAN_Series(TSeries source) : this(source, 0, false) { } + public MEDIAN_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - System.Collections.Generic.List _s = new(this._buffer); - _s.Sort(); - int _p1 = _s.Count / 2; - int _p2 = Math.Max(0, (_s.Count / 2) - 1); - double _med = (_s.Count % 2 != 0) ? _s[_p1] : (_s[_p1] + _s[_p2]) / 2; + System.Collections.Generic.List _s = new(this._buffer); + _s.Sort(); + int _p1 = _s.Count / 2; + int _p2 = Math.Max(0, (_s.Count / 2) - 1); + double _med = (_s.Count % 2 != 0) ? _s[_p1] : (_s[_p1] + _s[_p2]) / 2; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _med); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _med); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MIDPOINT_Series.cs b/Calculations/_Updated/MIDPOINT_Series.cs index c410be23..ca27243a 100644 --- a/Calculations/_Updated/MIDPOINT_Series.cs +++ b/Calculations/_Updated/MIDPOINT_Series.cs @@ -13,60 +13,69 @@ Sources: */ -public class MIDPOINT_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class MIDPOINT_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public MIDPOINT_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MIDPOINT({period})"; - } - public MIDPOINT_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MIDPOINT_Series() : this(period: 0, useNaN: false) { } - public MIDPOINT_Series(int period) : this(period: period, useNaN: false) { } - public MIDPOINT_Series(TBars source) : this(source.Close, 0, false) { } - public MIDPOINT_Series(TBars source, int period) : this(source.Close, period, false) { } - public MIDPOINT_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MIDPOINT_Series(TSeries source) : this(source, 0, false) { } - public MIDPOINT_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public MIDPOINT_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"MIDPOINT({period})"; + } + public MIDPOINT_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MIDPOINT_Series() : this(period: 0, useNaN: false) { } + public MIDPOINT_Series(int period) : this(period: period, useNaN: false) { } + public MIDPOINT_Series(TBars source) : this(source.Close, 0, false) { } + public MIDPOINT_Series(TBars source, int period) : this(source.Close, period, false) { } + public MIDPOINT_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MIDPOINT_Series(TSeries source) : this(source, 0, false) { } + public MIDPOINT_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _max= _buffer.Max(); - double _min = _buffer.Min(); - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : (_max+_min)*0.5); - return base.Add(res, update); - } + double _max = _buffer.Max(); + double _min = _buffer.Min(); + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : (_max + _min) * 0.5); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MIDPRICE_Series.cs b/Calculations/_Updated/MIDPRICE_Series.cs index 5b2bf73f..099f90c8 100644 --- a/Calculations/_Updated/MIDPRICE_Series.cs +++ b/Calculations/_Updated/MIDPRICE_Series.cs @@ -9,57 +9,66 @@ MIDPRICE: Midpoint price (highhest high + lowest low)/2 in the given period in t */ -public class MIDPRICE_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TBars _data; - private readonly System.Collections.Generic.List _bufferhi = new(); - private readonly System.Collections.Generic.List _bufferlo = new(); +public class MIDPRICE_Series : TSeries +{ + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TBars _data; + private readonly System.Collections.Generic.List _bufferhi = new(); + private readonly System.Collections.Generic.List _bufferlo = new(); - //core constructors - public MIDPRICE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MIDPRICE({period})"; - } - public MIDPRICE_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(data: _data); - } - public MIDPRICE_Series() : this(period: 2, useNaN: false) { } - public MIDPRICE_Series(int period) : this(period: period, useNaN: false) { } - public MIDPRICE_Series(TBars source) : this(source, period: 2, useNaN: false) { } - public MIDPRICE_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public MIDPRICE_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"MIDPRICE({period})"; + } + public MIDPRICE_Series(TBars source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(data: _data); + } + public MIDPRICE_Series() : this(period: 2, useNaN: false) { } + public MIDPRICE_Series(int period) : this(period: period, useNaN: false) { } + public MIDPRICE_Series(TBars source) : this(source, period: 2, useNaN: false) { } + public MIDPRICE_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - BufferTrim(_bufferhi, TBar.h, _period, update); - BufferTrim(_bufferlo, TBar.l, _period, update); - double _mid = (_bufferhi.Max() + _bufferlo.Min()) * 0.5; + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) + { + BufferTrim(_bufferhi, TBar.h, _period, update); + BufferTrim(_bufferlo, TBar.l, _period, update); + double _mid = (_bufferhi.Max() + _bufferlo.Min()) * 0.5; - var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _mid); - return base.Add(res, update); - } + var res = (TBar.t, Count < _period - 1 && _NaN ? double.NaN : _mid); + return base.Add(res, update); + } - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } + public new void Add(TBars data) + { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TBar: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _bufferhi.Clear(); - _bufferlo.Clear(); - } + //reset calculation + public override void Reset() + { + _bufferhi.Clear(); + _bufferlo.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MIN_Series.cs b/Calculations/_Updated/MIN_Series.cs index 2b34aa7b..4bb44ca9 100644 --- a/Calculations/_Updated/MIN_Series.cs +++ b/Calculations/_Updated/MIN_Series.cs @@ -10,59 +10,68 @@ MIN - Minimum value in the given period in the series. */ -public class MIN_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class MIN_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public MIN_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MAX({period})"; - } - public MIN_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MIN_Series() : this(period: 0, useNaN: false) { } - public MIN_Series(int period) : this(period: period, useNaN: false) { } - public MIN_Series(TBars source) : this(source.Close, 0, false) { } - public MIN_Series(TBars source, int period) : this(source.Close, period, false) { } - public MIN_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MIN_Series(TSeries source) : this(source, 0, false) { } - public MIN_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public MIN_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"MAX({period})"; + } + public MIN_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MIN_Series() : this(period: 0, useNaN: false) { } + public MIN_Series(int period) : this(period: period, useNaN: false) { } + public MIN_Series(TBars source) : this(source.Close, 0, false) { } + public MIN_Series(TBars source, int period) : this(source.Close, period, false) { } + public MIN_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MIN_Series(TSeries source) : this(source, 0, false) { } + public MIN_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _max= _buffer.Min(); - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _max); - return base.Add(res, update); - } + double _max = _buffer.Min(); + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _max); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/MSE_Series.cs b/Calculations/_Updated/MSE_Series.cs index a918962e..7be1a30a 100644 --- a/Calculations/_Updated/MSE_Series.cs +++ b/Calculations/_Updated/MSE_Series.cs @@ -13,64 +13,73 @@ Sources: */ -public class MSE_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class MSE_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public MSE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"MSE({period})"; - } - public MSE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public MSE_Series() : this(period: 0, useNaN: false) { } - public MSE_Series(int period) : this(period: period, useNaN: false) { } - public MSE_Series(TBars source) : this(source.Close, 0, false) { } - public MSE_Series(TBars source, int period) : this(source.Close, period, false) { } - public MSE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public MSE_Series(TSeries source) : this(source, 0, false) { } - public MSE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public MSE_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"MSE({period})"; + } + public MSE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public MSE_Series() : this(period: 0, useNaN: false) { } + public MSE_Series(int period) : this(period: period, useNaN: false) { } + public MSE_Series(TBars source) : this(source.Close, 0, false) { } + public MSE_Series(TBars source, int period) : this(source.Close, period, false) { } + public MSE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public MSE_Series(TSeries source) : this(source, 0, false) { } + public MSE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _sma = _buffer.Average(); + double _sma = _buffer.Average(); - double _mse = 0; - for (int i = 0; i < _buffer.Count; i++) { _mse += (_buffer[i] - _sma) * (_buffer[i] - _sma); } - _mse /= this._buffer.Count; + double _mse = 0; + for (int i = 0; i < _buffer.Count; i++) { _mse += (_buffer[i] - _sma) * (_buffer[i] - _sma); } + _mse /= this._buffer.Count; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mse); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _mse); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/OBV_Series.cs b/Calculations/_Updated/OBV_Series.cs index 40aa2c6e..f589cc3b 100644 --- a/Calculations/_Updated/OBV_Series.cs +++ b/Calculations/_Updated/OBV_Series.cs @@ -26,71 +26,81 @@ Note: */ -public class OBV_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TBars _data; - private double _lastobv, _lastlastobv; - private double _lastclose, _lastlastclose; +public class OBV_Series : TSeries +{ + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TBars _data; + private double _lastobv, _lastlastobv; + private double _lastclose, _lastlastclose; - //core constructors - public OBV_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"OBV({period})"; - this._lastobv = this._lastlastobv = 0; - this._lastclose = this._lastlastclose = 0; - } - public OBV_Series(TBars source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(data: _data); - } - public OBV_Series() : this(period: 2, useNaN: false) { } - public OBV_Series(int period) : this(period: period, useNaN: false) { } - public OBV_Series(TBars source) : this(source, period: 2, useNaN: false) { } - public OBV_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public OBV_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"OBV({period})"; + this._lastobv = this._lastlastobv = 0; + this._lastclose = this._lastlastclose = 0; + } + public OBV_Series(TBars source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(data: _data); + } + public OBV_Series() : this(period: 2, useNaN: false) { } + public OBV_Series(int period) : this(period: period, useNaN: false) { } + public OBV_Series(TBars source) : this(source, period: 2, useNaN: false) { } + public OBV_Series(TBars source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) + { - if (update) { - this._lastobv = this._lastlastobv; - this._lastclose = this._lastlastclose; - } + if (update) + { + this._lastobv = this._lastlastobv; + this._lastclose = this._lastlastclose; + } - double _obv = this._lastobv; - if (TBar.c > this._lastclose) { _obv += TBar.v; } - if (TBar.c < this._lastclose) { _obv -= TBar.v; } + double _obv = this._lastobv; + if (TBar.c > this._lastclose) { _obv += TBar.v; } + if (TBar.c < this._lastclose) { _obv -= TBar.v; } - this._lastlastobv = this._lastobv; - this._lastobv = _obv; + this._lastlastobv = this._lastobv; + this._lastobv = _obv; - this._lastlastclose = this._lastclose; - this._lastclose = TBar.c; + this._lastlastclose = this._lastclose; + this._lastclose = TBar.c; - var res = (TBar.t, (this.Count < this._period && this._NaN) ? double.NaN : _obv); - return base.Add(res, update); - } + var res = (TBar.t, (this.Count < this._period && this._NaN) ? double.NaN : _obv); + return base.Add(res, update); + } - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } + public new void Add(TBars data) + { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TBar: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - this._lastobv = this._lastlastobv = 0; - this._lastclose = this._lastlastclose = 0; - } + //reset calculation + public override void Reset() + { + this._lastobv = this._lastlastobv = 0; + this._lastclose = this._lastlastclose = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/RMA_Series.cs b/Calculations/_Updated/RMA_Series.cs index 5a624bd9..217c895d 100644 --- a/Calculations/_Updated/RMA_Series.cs +++ b/Calculations/_Updated/RMA_Series.cs @@ -20,97 +20,114 @@ Issues: */ -public class RMA_Series : TSeries { - private double _k; - private double _lastrma, _oldrma; - private double _sum, _oldsum; - private readonly bool _useSMA; - private int _len; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class RMA_Series : TSeries +{ + private double _k; + private double _lastrma, _oldrma; + private double _sum, _oldsum; + private readonly bool _useSMA; + private int _len; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; -//core constructor - public RMA_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - _useSMA = useSMA; - Name = $"RMA({period})"; - _k = 1.0 / (double)(this._period); - _len = 0; - _sum = _oldsum = _lastrma = _oldrma = 0; - } - //generic constructors (source) + //core constructor + public RMA_Series(int period, bool useNaN, bool useSMA) + { + _period = period; + _NaN = useNaN; + _useSMA = useSMA; + Name = $"RMA({period})"; + _k = 1.0 / (double)(this._period); + _len = 0; + _sum = _oldsum = _lastrma = _oldrma = 0; + } + //generic constructors (source) - public RMA_Series() : this(0, false, true) {} - public RMA_Series(int period) : this(period, false, true) {} - public RMA_Series(TBars source) : this(source.Close, 0, false) {} - public RMA_Series(TBars source, int period) : this(source.Close, period, false) {} - public RMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} - public RMA_Series(TSeries source, int period) : this(source, period, false, true) {} - public RMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} - public RMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } + public RMA_Series() : this(0, false, true) { } + public RMA_Series(int period) : this(period, false, true) { } + public RMA_Series(TBars source) : this(source.Close, 0, false) { } + public RMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public RMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public RMA_Series(TSeries source, int period) : this(source, period, false, true) { } + public RMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) { } + public RMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } -// core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { - _lastrma = _oldrma; - _sum = _oldsum; - } - else { - _oldrma = _lastrma; - _oldsum = _sum; - _len++; - } + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (update) + { + _lastrma = _oldrma; + _sum = _oldsum; + } + else + { + _oldrma = _lastrma; + _oldsum = _sum; + _len++; + } - double _rma = 0; - if (_period == 0) { - _k = 1.0 / (double)(this._len); - } + double _rma = 0; + if (_period == 0) + { + _k = 1.0 / (double)(this._len); + } - if (Count == 0) { - _rma = _sum = TValue.v; + if (Count == 0) + { + _rma = _sum = TValue.v; - } else if (_len <= _period && _useSMA && _period != 0) { - _sum += TValue.v; - if (_period != 0 && _len > _period) { - _sum -= _data[Count - _period - (update ? 1 : 0)].v; - } - _rma = _sum / Math.Min(_len, _period); - } - else { - _rma = _k * (TValue.v - _lastrma) + _lastrma; - } + } + else if (_len <= _period && _useSMA && _period != 0) + { + _sum += TValue.v; + if (_period != 0 && _len > _period) + { + _sum -= _data[Count - _period - (update ? 1 : 0)].v; + } + _rma = _sum / Math.Min(_len, _period); + } + else + { + _rma = _k * (TValue.v - _lastrma) + _lastrma; + } - _lastrma = double.IsNaN(_rma) ? _lastrma : _rma; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _rma); - return base.Add(res, update); - } + _lastrma = double.IsNaN(_rma) ? _lastrma : _rma; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _rma); + return base.Add(res, update); + } -//variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _sum = _oldsum = _lastrma = _oldrma = 0; - _len = 0; - } + //reset calculation + public override void Reset() + { + _sum = _oldsum = _lastrma = _oldrma = 0; + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/RSI_Series.cs b/Calculations/_Updated/RSI_Series.cs index 8d3979a1..ee9c883b 100644 --- a/Calculations/_Updated/RSI_Series.cs +++ b/Calculations/_Updated/RSI_Series.cs @@ -15,106 +15,120 @@ Sources: */ -public class RSI_Series : TSeries { - private readonly System.Collections.Generic.List _gain = new(); - private readonly System.Collections.Generic.List _loss = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private double _avgGain, _avgLoss, _lastValue; - private double _avgGain_o, _avgLoss_o, _lastValue_o; - private int i; +public class RSI_Series : TSeries +{ + private readonly System.Collections.Generic.List _gain = new(); + private readonly System.Collections.Generic.List _loss = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private double _avgGain, _avgLoss, _lastValue; + private double _avgGain_o, _avgLoss_o, _lastValue_o; + private int i; - //core constructors - public RSI_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"RSI({period})"; - i = 0; - } - public RSI_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public RSI_Series() : this(period: 0, useNaN: false) { } - public RSI_Series(int period) : this(period: period, useNaN: false) { } - public RSI_Series(TBars source) : this(source.Close, 0, false) { } - public RSI_Series(TBars source, int period) : this(source.Close, period, false) { } - public RSI_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public RSI_Series(TSeries source) : this(source, 0, false) { } - public RSI_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public RSI_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"RSI({period})"; + i = 0; + } + public RSI_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public RSI_Series() : this(period: 0, useNaN: false) { } + public RSI_Series(int period) : this(period: period, useNaN: false) { } + public RSI_Series(TBars source) : this(source.Close, 0, false) { } + public RSI_Series(TBars source, int period) : this(source.Close, period, false) { } + public RSI_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public RSI_Series(TSeries source) : this(source, 0, false) { } + public RSI_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { - double _rsi = 0; - if (update) { - _lastValue = _lastValue_o; - _avgGain = _avgGain_o; - _avgLoss = _avgLoss_o; - } - else { - _lastValue_o = _lastValue; - _avgGain_o = _avgGain; - _avgLoss_o = _avgLoss; - } + double _rsi = 0; + if (update) + { + _lastValue = _lastValue_o; + _avgGain = _avgGain_o; + _avgLoss = _avgLoss_o; + } + else + { + _lastValue_o = _lastValue; + _avgGain_o = _avgGain; + _avgLoss_o = _avgLoss; + } - if (i == 0) { _lastValue = TValue.v; } + if (i == 0) { _lastValue = TValue.v; } - double _gainval = (TValue.v > _lastValue) ? TValue.v - _lastValue : 0; - BufferTrim(_gain, _gainval, _period, update); - double _lossval = (TValue.v < _lastValue) ? _lastValue - TValue.v : 0; - BufferTrim(_loss, _lossval, _period, update); - _lastValue = TValue.v; + double _gainval = (TValue.v > _lastValue) ? TValue.v - _lastValue : 0; + BufferTrim(_gain, _gainval, _period, update); + double _lossval = (TValue.v < _lastValue) ? _lastValue - TValue.v : 0; + BufferTrim(_loss, _lossval, _period, update); + _lastValue = TValue.v; - // calculate RSI - if (i > _period && _period != 0) { - _avgGain = ((_avgGain * (_period - 1)) + _gain[^1]) / _period; - _avgLoss = ((_avgLoss * (_period - 1)) + _loss[^1]) / _period; - if (_avgLoss > 0) { - double rs = _avgGain / _avgLoss; - _rsi = 100 - (100 / (1 + rs)); - } - else { _rsi = 100; } - } - // initialize average gain - else { - double _sumGain = 0; - for (int p = 0; p < _gain.Count; p++) { _sumGain += _gain[p]; } - double _sumLoss = 0; - for (int p = 0; p < _loss.Count; p++) { _sumLoss += _loss[p]; } + // calculate RSI + if (i > _period && _period != 0) + { + _avgGain = ((_avgGain * (_period - 1)) + _gain[^1]) / _period; + _avgLoss = ((_avgLoss * (_period - 1)) + _loss[^1]) / _period; + if (_avgLoss > 0) + { + double rs = _avgGain / _avgLoss; + _rsi = 100 - (100 / (1 + rs)); + } + else { _rsi = 100; } + } + // initialize average gain + else + { + double _sumGain = 0; + for (int p = 0; p < _gain.Count; p++) { _sumGain += _gain[p]; } + double _sumLoss = 0; + for (int p = 0; p < _loss.Count; p++) { _sumLoss += _loss[p]; } - _avgGain = _sumGain / _gain.Count; - _avgLoss = _sumLoss / _loss.Count; + _avgGain = _sumGain / _gain.Count; + _avgLoss = _sumLoss / _loss.Count; - _rsi = (_avgLoss > 0) ? 100 - (100 / (1 + (_avgGain / _avgLoss))) : 100; - } - if (!update) { i++; } + _rsi = (_avgLoss > 0) ? 100 - (100 / (1 + (_avgGain / _avgLoss))) : 100; + } + if (!update) { i++; } - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _rsi); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _rsi); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - i = 0; - } + //reset calculation + public override void Reset() + { + i = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/SDEV_Series.cs b/Calculations/_Updated/SDEV_Series.cs index ab5ca503..b7b5cc1a 100644 --- a/Calculations/_Updated/SDEV_Series.cs +++ b/Calculations/_Updated/SDEV_Series.cs @@ -18,65 +18,74 @@ Remark: */ -public class SDEV_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class SDEV_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public SDEV_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"SDEV({period})"; - } - public SDEV_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SDEV_Series() : this(period: 0, useNaN: false) { } - public SDEV_Series(int period) : this(period: period, useNaN: false) { } - public SDEV_Series(TBars source) : this(source.Close, 0, false) { } - public SDEV_Series(TBars source, int period) : this(source.Close, period, false) { } - public SDEV_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public SDEV_Series(TSeries source) : this(source, 0, false) { } - public SDEV_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public SDEV_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"SDEV({period})"; + } + public SDEV_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SDEV_Series() : this(period: 0, useNaN: false) { } + public SDEV_Series(int period) : this(period: period, useNaN: false) { } + public SDEV_Series(TBars source) : this(source.Close, 0, false) { } + public SDEV_Series(TBars source, int period) : this(source.Close, period, false) { } + public SDEV_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public SDEV_Series(TSeries source) : this(source, 0, false) { } + public SDEV_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _sma = _buffer.Average(); + double _sma = _buffer.Average(); - double _var = 0; - for (int i = 0; i < _buffer.Count; i++) { _var += (_buffer[i] - _sma) * (_buffer[i] - _sma); } - _var /= this._buffer.Count; - double _sdev = Math.Sqrt(_var); + double _var = 0; + for (int i = 0; i < _buffer.Count; i++) { _var += (_buffer[i] - _sma) * (_buffer[i] - _sma); } + _var /= this._buffer.Count; + double _sdev = Math.Sqrt(_var); - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _sdev); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _sdev); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/SLOPE_Series.cs b/Calculations/_Updated/SLOPE_Series.cs index 4aef642e..8d7580c2 100644 --- a/Calculations/_Updated/SLOPE_Series.cs +++ b/Calculations/_Updated/SLOPE_Series.cs @@ -22,101 +22,109 @@ Sources: */ -public class SLOPE_Series : TSeries { - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly TSeries p_Intercept = new(); - private readonly TSeries p_RSquared = new(); - private readonly TSeries p_StdDev = new(); - private readonly System.Collections.Generic.List _buffer = new(); - public TSeries Intercept => p_Intercept; - public TSeries RSquared => p_RSquared; - public TSeries StdDev => p_StdDev; - //core constructors - public SLOPE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"SLOPE({period})"; - } - public SLOPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SLOPE_Series() : this(period: 0, useNaN: false) { } - public SLOPE_Series(int period) : this(period: period, useNaN: false) { } - public SLOPE_Series(TBars source) : this(source.Close, 0, false) { } - public SLOPE_Series(TBars source, int period) : this(source.Close, period, false) { } - public SLOPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public SLOPE_Series(TSeries source) : this(source, 0, false) { } - public SLOPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } +public class SLOPE_Series : TSeries +{ + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly TSeries p_Intercept = new(); + private readonly TSeries p_RSquared = new(); + private readonly TSeries p_StdDev = new(); + private readonly System.Collections.Generic.List _buffer = new(); + public TSeries Intercept => p_Intercept; + public TSeries RSquared => p_RSquared; + public TSeries StdDev => p_StdDev; + //core constructors + public SLOPE_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"SLOPE({period})"; + } + public SLOPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SLOPE_Series() : this(period: 0, useNaN: false) { } + public SLOPE_Series(int period) : this(period: period, useNaN: false) { } + public SLOPE_Series(TBars source) : this(source.Close, 0, false) { } + public SLOPE_Series(TBars source, int period) : this(source.Close, period, false) { } + public SLOPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public SLOPE_Series(TSeries source) : this(source, 0, false) { } + public SLOPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - int _len = this._buffer.Count; + int _len = this._buffer.Count; - // get averages for period - double sumX = 0; - double sumY = 0; + // get averages for period + double sumX = 0; + double sumY = 0; - for (int p = 0; p < _len; p++) { - sumX += this.Count - _len + 2 + p; - sumY += _buffer[p]; - } - double avgX = sumX / _len; - double avgY = sumY / _len; + for (int p = 0; p < _len; p++) + { + sumX += this.Count - _len + 2 + p; + sumY += _buffer[p]; + } + double avgX = sumX / _len; + double avgY = sumY / _len; - // least squares method - double sumSqX = 0; - double sumSqY = 0; - double sumSqXY = 0; + // least squares method + double sumSqX = 0; + double sumSqY = 0; + double sumSqXY = 0; - for (int p = 0; p < _len; p++) { - double devX = this.Count - _len + 2 + p - avgX; - double devY = _buffer[p] - avgY; + for (int p = 0; p < _len; p++) + { + double devX = this.Count - _len + 2 + p - avgX; + double devY = _buffer[p] - avgY; - sumSqX += devX * devX; - sumSqY += devY * devY; - sumSqXY += devX * devY; - } + sumSqX += devX * devX; + sumSqY += devY * devY; + sumSqXY += devX * devY; + } - double _slope = sumSqXY / sumSqX; - double _intercept = avgY - (_slope * avgX); + double _slope = sumSqXY / sumSqX; + double _intercept = avgY - (_slope * avgX); - // calculate Standard Deviation and R-Squared - double stdDevX = Math.Sqrt(sumSqX / _len); - double stdDevY = Math.Sqrt(sumSqY / _len); - double _StdDev = stdDevY; + // calculate Standard Deviation and R-Squared + double stdDevX = Math.Sqrt(sumSqX / _len); + double stdDevY = Math.Sqrt(sumSqY / _len); + double _StdDev = stdDevY; - double arrr = (stdDevX * stdDevY != 0) ? sumSqXY / (stdDevX * stdDevY) / _len : 0; - double _RSquared = arrr * arrr; + double arrr = (stdDevX * stdDevY != 0) ? sumSqXY / (stdDevX * stdDevY) / _len : 0; + double _RSquared = arrr * arrr; - var ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _intercept); - p_Intercept.Add(ret, update); + var ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _intercept); + p_Intercept.Add(ret, update); - ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _StdDev); - p_StdDev.Add(ret, update); + ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _StdDev); + p_StdDev.Add(ret, update); - ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _RSquared); - p_RSquared.Add(ret, update); + ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _RSquared); + p_RSquared.Add(ret, update); - ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _slope); - return base.Add(ret, update); - } + ret = (TValue.t, this.Count < this._period - 1 && this._NaN ? double.NaN : _slope); + return base.Add(ret, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/SMAPE_Series.cs b/Calculations/_Updated/SMAPE_Series.cs index b79a3442..082fcd26 100644 --- a/Calculations/_Updated/SMAPE_Series.cs +++ b/Calculations/_Updated/SMAPE_Series.cs @@ -13,63 +13,72 @@ Sources: */ -public class SMAPE_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class SMAPE_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public SMAPE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"SMAPE({period})"; - } - public SMAPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SMAPE_Series() : this(period: 0, useNaN: false) { } - public SMAPE_Series(int period) : this(period: period, useNaN: false) { } - public SMAPE_Series(TBars source) : this(source.Close, 0, false) { } - public SMAPE_Series(TBars source, int period) : this(source.Close, period, false) { } - public SMAPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public SMAPE_Series(TSeries source) : this(source, 0, false) { } - public SMAPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public SMAPE_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"SMAPE({period})"; + } + public SMAPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SMAPE_Series() : this(period: 0, useNaN: false) { } + public SMAPE_Series(int period) : this(period: period, useNaN: false) { } + public SMAPE_Series(TBars source) : this(source.Close, 0, false) { } + public SMAPE_Series(TBars source, int period) : this(source.Close, period, false) { } + public SMAPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public SMAPE_Series(TSeries source) : this(source, 0, false) { } + public SMAPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _sma = _buffer.Average(); - double _smape = 0; - for (int i = 0; i < _buffer.Count; i++) { _smape += Math.Abs(_buffer[i] - _sma) / (Math.Abs(_buffer[i]) + Math.Abs(_sma)); } - _smape /= this._buffer.Count; + double _sma = _buffer.Average(); + double _smape = 0; + for (int i = 0; i < _buffer.Count; i++) { _smape += Math.Abs(_buffer[i] - _sma) / (Math.Abs(_buffer[i]) + Math.Abs(_sma)); } + _smape /= this._buffer.Count; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _smape); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _smape); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/SMA_Series.cs b/Calculations/_Updated/SMA_Series.cs index c9a817f0..0c5a19fa 100644 --- a/Calculations/_Updated/SMA_Series.cs +++ b/Calculations/_Updated/SMA_Series.cs @@ -16,81 +16,97 @@ Remark: implementation, but it does allow incremental additions of inputs and real-time calculations of SMA() */ -public class SMA_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); +public class SMA_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); - private double _sum, _oldsum; - private readonly int _period; - private readonly TSeries _data; - protected readonly bool _NaN; + private double _sum, _oldsum; + private readonly int _period; + private readonly TSeries _data; + protected readonly bool _NaN; - //core constructor - public SMA_Series(int period, bool useNaN) { - _period = Math.Max(0, period); - _NaN = useNaN; - Name = $"SMA({period})"; - _sum = _oldsum = 0; - } - public SMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SMA_Series() : this(0, false) {} - public SMA_Series(int period) : this(period, false) {} - public SMA_Series(TBars source) : this(source.Close, 0, false) {} - public SMA_Series(TBars source, int period) : this(source.Close, period, false) {} - public SMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} - public SMA_Series(TSeries source) : this(source, 0, false) {} - public SMA_Series(TSeries source, int period) : this(source, period, false) {} + //core constructor + public SMA_Series(int period, bool useNaN) + { + _period = Math.Max(0, period); + _NaN = useNaN; + Name = $"SMA({period})"; + _sum = _oldsum = 0; + } + public SMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SMA_Series() : this(0, false) { } + public SMA_Series(int period) : this(period, false) { } + public SMA_Series(TBars source) : this(source.Close, 0, false) { } + public SMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public SMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public SMA_Series(TSeries source) : this(source, 0, false) { } + public SMA_Series(TSeries source, int period) : this(source, period, false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { return (TValue.t, double.NaN); - } else { - if (update && _buffer.Count > 0) { - _sum -= _buffer[^1]; - _buffer[^1] = TValue.v; - _oldsum = _sum; - } - else { - _buffer.Add(TValue.v); - _oldsum = _sum; - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (double.IsNaN(TValue.v)) + { + return (TValue.t, double.NaN); + } + else + { + if (update && _buffer.Count > 0) + { + _sum -= _buffer[^1]; + _buffer[^1] = TValue.v; + _oldsum = _sum; + } + else + { + _buffer.Add(TValue.v); + _oldsum = _sum; + } - _sum += TValue.v; - if (_period != 0 && _buffer.Count > _period) { - _sum -= _buffer[0]; - _buffer.RemoveAt(0); - } - } + _sum += TValue.v; + if (_period != 0 && _buffer.Count > _period) + { + _sum -= _buffer[0]; + _buffer.RemoveAt(0); + } + } - double _div = _period == 0 ? _buffer.Count : Math.Min(_buffer.Count, _period); - var _sma = _sum / _div; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _sma); - return base.Add(res, update); - } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + double _div = _period == 0 ? _buffer.Count : Math.Min(_buffer.Count, _period); + var _sma = _sum / _div; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _sma); + return base.Add(res, update); + } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _sum = _oldsum = 0; - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _sum = _oldsum = 0; + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/SMMA_Series.cs b/Calculations/_Updated/SMMA_Series.cs index 8f464cc1..46b975c5 100644 --- a/Calculations/_Updated/SMMA_Series.cs +++ b/Calculations/_Updated/SMMA_Series.cs @@ -18,76 +18,88 @@ Sources: */ -public class SMMA_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); +public class SMMA_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private double _lastsmma, _lastlastsmma; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private double _lastsmma, _lastlastsmma; - //core constructors - public SMMA_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"SMMA({period})"; - } - public SMMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SMMA_Series() : this(period: 0, useNaN: false) { } - public SMMA_Series(int period) : this(period: period, useNaN: false) { } - public SMMA_Series(TBars source) : this(source.Close, 0, false) { } - public SMMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public SMMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public SMMA_Series(TSeries source) : this(source, 0, false) { } - public SMMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public SMMA_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"SMMA({period})"; + } + public SMMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SMMA_Series() : this(period: 0, useNaN: false) { } + public SMMA_Series(int period) : this(period: period, useNaN: false) { } + public SMMA_Series(TBars source) : this(source.Close, 0, false) { } + public SMMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public SMMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public SMMA_Series(TSeries source) : this(source, 0, false) { } + public SMMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, double.NaN),update); - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, double.NaN), update); + } - double _smma = 0; - if (update) { this._lastsmma = this._lastlastsmma; } + double _smma = 0; + if (update) { this._lastsmma = this._lastlastsmma; } - if (this.Count < this._period) { - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - _smma = _buffer.Average(); - } - else { - _smma = ((_lastsmma * (_period - 1)) + TValue.v) / _period; - } + if (this.Count < this._period) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + _smma = _buffer.Average(); + } + else + { + _smma = ((_lastsmma * (_period - 1)) + TValue.v) / _period; + } - this._lastlastsmma = this._lastsmma; - this._lastsmma = _smma; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _smma); - return base.Add(res, update); - } + this._lastlastsmma = this._lastsmma; + this._lastsmma = _smma; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _smma); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - this._lastsmma = this._lastlastsmma = 0; - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + this._lastsmma = this._lastlastsmma = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/SSDEV_Series.cs b/Calculations/_Updated/SSDEV_Series.cs index 2b5f62d3..490c579c 100644 --- a/Calculations/_Updated/SSDEV_Series.cs +++ b/Calculations/_Updated/SSDEV_Series.cs @@ -18,65 +18,74 @@ Remark: */ -public class SSDEV_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class SSDEV_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public SSDEV_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"SSDEV({period})"; - } - public SSDEV_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SSDEV_Series() : this(period: 0, useNaN: false) { } - public SSDEV_Series(int period) : this(period: period, useNaN: false) { } - public SSDEV_Series(TBars source) : this(source.Close, 0, false) { } - public SSDEV_Series(TBars source, int period) : this(source.Close, period, false) { } - public SSDEV_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public SSDEV_Series(TSeries source) : this(source, 0, false) { } - public SSDEV_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public SSDEV_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"SSDEV({period})"; + } + public SSDEV_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SSDEV_Series() : this(period: 0, useNaN: false) { } + public SSDEV_Series(int period) : this(period: period, useNaN: false) { } + public SSDEV_Series(TBars source) : this(source.Close, 0, false) { } + public SSDEV_Series(TBars source, int period) : this(source.Close, period, false) { } + public SSDEV_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public SSDEV_Series(TSeries source) : this(source, 0, false) { } + public SSDEV_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _sma = _buffer.Average(); + double _sma = _buffer.Average(); - double _svar = 0; - for (int i = 0; i < this._buffer.Count; i++) { _svar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } - _svar /= (_buffer.Count > 1) ? _buffer.Count - 1 : 1; // Bessel's correction - double _ssdev = Math.Sqrt(_svar); + double _svar = 0; + for (int i = 0; i < this._buffer.Count; i++) { _svar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } + _svar /= (_buffer.Count > 1) ? _buffer.Count - 1 : 1; // Bessel's correction + double _ssdev = Math.Sqrt(_svar); - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _ssdev); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _ssdev); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/SVAR_Series.cs b/Calculations/_Updated/SVAR_Series.cs index bb693c54..01020519 100644 --- a/Calculations/_Updated/SVAR_Series.cs +++ b/Calculations/_Updated/SVAR_Series.cs @@ -18,64 +18,73 @@ Remark: */ -public class SVAR_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class SVAR_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public SVAR_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"SVAR({period})"; - } - public SVAR_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public SVAR_Series() : this(period: 0, useNaN: false) { } - public SVAR_Series(int period) : this(period: period, useNaN: false) { } - public SVAR_Series(TBars source) : this(source.Close, 0, false) { } - public SVAR_Series(TBars source, int period) : this(source.Close, period, false) { } - public SVAR_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public SVAR_Series(TSeries source) : this(source, 0, false) { } - public SVAR_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public SVAR_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"SVAR({period})"; + } + public SVAR_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public SVAR_Series() : this(period: 0, useNaN: false) { } + public SVAR_Series(int period) : this(period: period, useNaN: false) { } + public SVAR_Series(TBars source) : this(source.Close, 0, false) { } + public SVAR_Series(TBars source, int period) : this(source.Close, period, false) { } + public SVAR_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public SVAR_Series(TSeries source) : this(source, 0, false) { } + public SVAR_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _sma = _buffer.Average(); + double _sma = _buffer.Average(); - double _svar = 0; - for (int i = 0; i < this._buffer.Count; i++) { _svar += (this._buffer[i] - _sma) * (this._buffer[i] - _sma); } - _svar /= (this._buffer.Count > 1) ? this._buffer.Count - 1 : 1; // Bessel's correction + double _svar = 0; + for (int i = 0; i < this._buffer.Count; i++) { _svar += (this._buffer[i] - _sma) * (this._buffer[i] - _sma); } + _svar /= (this._buffer.Count > 1) ? this._buffer.Count - 1 : 1; // Bessel's correction - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _svar); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _svar); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/T3_Series.cs b/Calculations/_Updated/T3_Series.cs index 7cba0dcd..6f87ea07 100644 --- a/Calculations/_Updated/T3_Series.cs +++ b/Calculations/_Updated/T3_Series.cs @@ -14,148 +14,160 @@ Sources: http://www.binarytribune.com/forex-trading-indicators/t3-moving-average-indicator/ */ -public class T3_Series : TSeries { - private readonly double _k, _k1m, _c1, _c2, _c3, _c4; - private readonly System.Collections.Generic.List _buffer1 = new(); - private readonly System.Collections.Generic.List _buffer2 = new(); - private readonly System.Collections.Generic.List _buffer3 = new(); - private readonly System.Collections.Generic.List _buffer4 = new(); - private readonly System.Collections.Generic.List _buffer5 = new(); - private readonly System.Collections.Generic.List _buffer6 = new(); - private readonly bool _useSMA; - private double _lastema1, _lastema2, _lastema3, _lastema4, _lastema5, _lastema6; - private double _llastema1, _llastema2, _llastema3, _llastema4, _llastema5, _llastema6; - protected int _len; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class T3_Series : TSeries +{ + private readonly double _k, _k1m, _c1, _c2, _c3, _c4; + private readonly System.Collections.Generic.List _buffer1 = new(); + private readonly System.Collections.Generic.List _buffer2 = new(); + private readonly System.Collections.Generic.List _buffer3 = new(); + private readonly System.Collections.Generic.List _buffer4 = new(); + private readonly System.Collections.Generic.List _buffer5 = new(); + private readonly System.Collections.Generic.List _buffer6 = new(); + private readonly bool _useSMA; + private double _lastema1, _lastema2, _lastema3, _lastema4, _lastema5, _lastema6; + private double _llastema1, _llastema2, _llastema3, _llastema4, _llastema5, _llastema6; + protected int _len; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public T3_Series(int period, double vfactor, bool useSMA, bool useNaN) { - _period = period; - _len = 0; - _NaN = useNaN; - Name = $"T3({period})"; - _useSMA = useSMA; - double _a = vfactor; //0.7; //0.618 - _c1 = -_a * _a * _a; - _c2 = 3 * _a * _a + 3 * _a * _a * _a; - _c3 = -6 * _a * _a - 3 * _a - 3 * _a * _a * _a; - _c4 = 1 + 3 * _a + _a * _a * _a + 3 * _a * _a; + //core constructors + public T3_Series(int period, double vfactor, bool useSMA, bool useNaN) + { + _period = period; + _len = 0; + _NaN = useNaN; + Name = $"T3({period})"; + _useSMA = useSMA; + double _a = vfactor; //0.7; //0.618 + _c1 = -_a * _a * _a; + _c2 = 3 * _a * _a + 3 * _a * _a * _a; + _c3 = -6 * _a * _a - 3 * _a - 3 * _a * _a * _a; + _c4 = 1 + 3 * _a + _a * _a * _a + 3 * _a * _a; - _k = 2.0 / (_period + 1); - _k1m = 1.0 - _k; - _lastema1 = _llastema1 = _lastema2 = _llastema2 = _lastema3 = _llastema3 = _lastema4 = _llastema4 = _lastema5 = _llastema5 = _lastema5 = _llastema5 = 0; - } - public T3_Series(TSeries source, int period, double vfactor, bool useSMA, bool useNaN) : this(period, vfactor, useSMA, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public T3_Series() : this(period: 0, vfactor: 0.7, useSMA: true, useNaN: false) { } - public T3_Series(int period) : this(period: period, vfactor: 0.7, useSMA: true, useNaN: false) { } - public T3_Series(TBars source) : this(source.Close, 0, vfactor: 0.7, useSMA: true, useNaN: false) { } - public T3_Series(TBars source, int period) : this(source.Close, period, vfactor: 0.7, useSMA: true, useNaN: false) { } - public T3_Series(TBars source, int period, bool useNaN) : this(source.Close, period, vfactor: 0.7, useSMA: true, useNaN: useNaN) { } - public T3_Series(TBars source, int period, double vfactor, bool useNaN) : this(source.Close, period, vfactor: vfactor, useSMA: true, useNaN: useNaN) { } - public T3_Series(TBars source, int period, bool useSMA, bool useNaN) : this(source.Close, period, vfactor: 0.7, useSMA: useSMA, useNaN: useNaN) { } - public T3_Series(TSeries source) : this(source, 0, vfactor: 0.7, useSMA: true, useNaN: false) { } - public T3_Series(TSeries source, int period) : this(source: source, period: period, vfactor: 0.7, useSMA: true, useNaN: false) { } - public T3_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, vfactor: 0.7, useSMA: true, useNaN: useNaN) { } - public T3_Series(TSeries source, int period, double vfactor) : this(source: source, period: period, vfactor: vfactor, useSMA: true, useNaN: false) { } - public T3_Series(TSeries source, int period, double vfactor, bool useNaN) : this(source: source, period: period, vfactor: vfactor, useSMA: true, useNaN: useNaN) { } + _k = 2.0 / (_period + 1); + _k1m = 1.0 - _k; + _lastema1 = _llastema1 = _lastema2 = _llastema2 = _lastema3 = _llastema3 = _lastema4 = _llastema4 = _lastema5 = _llastema5 = _lastema5 = _llastema5 = 0; + } + public T3_Series(TSeries source, int period, double vfactor, bool useSMA, bool useNaN) : this(period, vfactor, useSMA, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public T3_Series() : this(period: 0, vfactor: 0.7, useSMA: true, useNaN: false) { } + public T3_Series(int period) : this(period: period, vfactor: 0.7, useSMA: true, useNaN: false) { } + public T3_Series(TBars source) : this(source.Close, 0, vfactor: 0.7, useSMA: true, useNaN: false) { } + public T3_Series(TBars source, int period) : this(source.Close, period, vfactor: 0.7, useSMA: true, useNaN: false) { } + public T3_Series(TBars source, int period, bool useNaN) : this(source.Close, period, vfactor: 0.7, useSMA: true, useNaN: useNaN) { } + public T3_Series(TBars source, int period, double vfactor, bool useNaN) : this(source.Close, period, vfactor: vfactor, useSMA: true, useNaN: useNaN) { } + public T3_Series(TBars source, int period, bool useSMA, bool useNaN) : this(source.Close, period, vfactor: 0.7, useSMA: useSMA, useNaN: useNaN) { } + public T3_Series(TSeries source) : this(source, 0, vfactor: 0.7, useSMA: true, useNaN: false) { } + public T3_Series(TSeries source, int period) : this(source: source, period: period, vfactor: 0.7, useSMA: true, useNaN: false) { } + public T3_Series(TSeries source, int period, bool useNaN) : this(source: source, period: period, vfactor: 0.7, useSMA: true, useNaN: useNaN) { } + public T3_Series(TSeries source, int period, double vfactor) : this(source: source, period: period, vfactor: vfactor, useSMA: true, useNaN: false) { } + public T3_Series(TSeries source, int period, double vfactor, bool useNaN) : this(source: source, period: period, vfactor: vfactor, useSMA: true, useNaN: useNaN) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - double _ema1, _ema2, _ema3, _ema4, _ema5, _ema6; - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN),update); - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + double _ema1, _ema2, _ema3, _ema4, _ema5, _ema6; + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, Double.NaN), update); + } - if (update) { _lastema1 = _llastema1; _lastema2 = _llastema2; _lastema3 = _llastema3; _lastema4 = _llastema4; _lastema5 = _llastema5; _lastema6 = _llastema6; } - else { _llastema1 = _lastema1; _llastema2 = _lastema2; _llastema3 = _lastema3; _llastema4 = _lastema4; _llastema5 = _lastema5; _llastema6 = _lastema6; } + if (update) { _lastema1 = _llastema1; _lastema2 = _llastema2; _lastema3 = _llastema3; _lastema4 = _llastema4; _lastema5 = _llastema5; _lastema6 = _llastema6; } + else { _llastema1 = _lastema1; _llastema2 = _lastema2; _llastema3 = _lastema3; _llastema4 = _lastema4; _llastema5 = _lastema5; _llastema6 = _lastema6; } - if (_len == 0) { _lastema1 = _lastema2 = _lastema3 = _lastema4 = _lastema5 = _lastema6 = TValue.v; } - + if (_len == 0) { _lastema1 = _lastema2 = _lastema3 = _lastema4 = _lastema5 = _lastema6 = TValue.v; } - if ((_len < _period) && _useSMA) { - BufferTrim(_buffer1, TValue.v, _period, update); - _ema1 = 0; - for (int i = 0; i < _buffer1.Count; i++) { _ema1 += _buffer1[i]; } - _ema1 /= _buffer1.Count; - BufferTrim(_buffer2, _ema1, _period, update); - _ema2 = 0; - for (int i = 0; i < _buffer2.Count; i++) { _ema2 += _buffer2[i]; } - _ema2 /= _buffer2.Count; + if ((_len < _period) && _useSMA) + { + BufferTrim(_buffer1, TValue.v, _period, update); + _ema1 = 0; + for (int i = 0; i < _buffer1.Count; i++) { _ema1 += _buffer1[i]; } + _ema1 /= _buffer1.Count; - BufferTrim(_buffer3, _ema2, _period, update); - _ema3 = 0; - for (int i = 0; i < _buffer3.Count; i++) { _ema3 += _buffer3[i]; } - _ema3 /= _buffer3.Count; + BufferTrim(_buffer2, _ema1, _period, update); + _ema2 = 0; + for (int i = 0; i < _buffer2.Count; i++) { _ema2 += _buffer2[i]; } + _ema2 /= _buffer2.Count; - BufferTrim(_buffer4, _ema3, _period, update); - _ema4 = 0; - for (int i = 0; i < _buffer4.Count; i++) { _ema4 += _buffer4[i]; } - _ema4 /= _buffer4.Count; + BufferTrim(_buffer3, _ema2, _period, update); + _ema3 = 0; + for (int i = 0; i < _buffer3.Count; i++) { _ema3 += _buffer3[i]; } + _ema3 /= _buffer3.Count; - BufferTrim(_buffer5, _ema4, _period, update); - _ema5 = 0; - for (int i = 0; i < _buffer5.Count; i++) { _ema5 += _buffer5[i]; } - _ema5 /= _buffer5.Count; + BufferTrim(_buffer4, _ema3, _period, update); + _ema4 = 0; + for (int i = 0; i < _buffer4.Count; i++) { _ema4 += _buffer4[i]; } + _ema4 /= _buffer4.Count; - BufferTrim(_buffer6, _ema5, _period, update); - _ema6 = 0; - for (int i = 0; i < _buffer6.Count; i++) { _ema6 += _buffer6[i]; } - _ema6 /= _buffer6.Count; - } - else { - _ema1 = (TValue.v * this._k) + (this._lastema1 * this._k1m); - _ema2 = (_ema1 * this._k) + (this._lastema2 * this._k1m); - _ema3 = (_ema2 * this._k) + (this._lastema3 * this._k1m); - _ema4 = (_ema3 * this._k) + (this._lastema4 * this._k1m); - _ema5 = (_ema4 * this._k) + (this._lastema5 * this._k1m); - _ema6 = (_ema5 * this._k) + (this._lastema6 * this._k1m); - } - _len++; - _lastema1 = _ema1; - _lastema2 = _ema2; - _lastema3 = _ema3; - _lastema4 = _ema4; - _lastema5 = _ema5; - _lastema6 = _ema6; + BufferTrim(_buffer5, _ema4, _period, update); + _ema5 = 0; + for (int i = 0; i < _buffer5.Count; i++) { _ema5 += _buffer5[i]; } + _ema5 /= _buffer5.Count; - double _T3 = _c1 * _ema6 + _c2 * _ema5 + _c3 * _ema4 + _c4 * _ema3; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _T3); - return base.Add(res, update); - } + BufferTrim(_buffer6, _ema5, _period, update); + _ema6 = 0; + for (int i = 0; i < _buffer6.Count; i++) { _ema6 += _buffer6[i]; } + _ema6 /= _buffer6.Count; + } + else + { + _ema1 = (TValue.v * this._k) + (this._lastema1 * this._k1m); + _ema2 = (_ema1 * this._k) + (this._lastema2 * this._k1m); + _ema3 = (_ema2 * this._k) + (this._lastema3 * this._k1m); + _ema4 = (_ema3 * this._k) + (this._lastema4 * this._k1m); + _ema5 = (_ema4 * this._k) + (this._lastema5 * this._k1m); + _ema6 = (_ema5 * this._k) + (this._lastema6 * this._k1m); + } + _len++; + _lastema1 = _ema1; + _lastema2 = _ema2; + _lastema3 = _ema3; + _lastema4 = _ema4; + _lastema5 = _ema5; + _lastema6 = _ema6; - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + double _T3 = _c1 * _ema6 + _c2 * _ema5 + _c3 * _ema4 + _c4 * _ema3; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _T3); + return base.Add(res, update); + } - //reset calculation - public override void Reset() { - _lastema1 = _llastema1 = _lastema2 = _llastema2 = _lastema3 = _llastema3 = _lastema4 = _llastema4 = _lastema5 = _llastema5 = _lastema5 = _llastema5 = 0; - _buffer1.Clear(); - _buffer2.Clear(); - _buffer3.Clear(); - _buffer4.Clear(); - _buffer5.Clear(); - _buffer6.Clear(); - _len = 0; - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() + { + _lastema1 = _llastema1 = _lastema2 = _llastema2 = _lastema3 = _llastema3 = _lastema4 = _llastema4 = _lastema5 = _llastema5 = _lastema5 = _llastema5 = 0; + _buffer1.Clear(); + _buffer2.Clear(); + _buffer3.Clear(); + _buffer4.Clear(); + _buffer5.Clear(); + _buffer6.Clear(); + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/TBars.cs b/Calculations/_Updated/TBars.cs index 0c0274d4..7b9af4ce 100644 --- a/Calculations/_Updated/TBars.cs +++ b/Calculations/_Updated/TBars.cs @@ -12,127 +12,142 @@ TBars class - includes all series for common data used in indicators and other c public class TBars : System.Collections.Generic.List<(DateTime t, double o, double h, double l, double c, double v)> { - public string Name { get; set; } - private readonly TSeries _open = new("open"); - private readonly TSeries _high = new("high"); - private readonly TSeries _low = new("low"); - private readonly TSeries _close = new("close"); - private readonly TSeries _volume = new("volume"); - private readonly TSeries _hl2 = new("HL2"); - private readonly TSeries _oc2 = new("OC2"); - private readonly TSeries _ohl3 = new("OHL3"); - private readonly TSeries _hlc3 = new("HLC3"); - private readonly TSeries _ohlc4 = new("OHLC4"); - private readonly TSeries _hlcc4 = new("HLCC4"); + public string Name { get; set; } + private readonly TSeries _open = new("open"); + private readonly TSeries _high = new("high"); + private readonly TSeries _low = new("low"); + private readonly TSeries _close = new("close"); + private readonly TSeries _volume = new("volume"); + private readonly TSeries _hl2 = new("HL2"); + private readonly TSeries _oc2 = new("OC2"); + private readonly TSeries _ohl3 = new("OHL3"); + private readonly TSeries _hlc3 = new("HLC3"); + private readonly TSeries _ohlc4 = new("OHLC4"); + private readonly TSeries _hlcc4 = new("HLCC4"); - public TSeries Open => this._open; - public TSeries High => this._high; - public TSeries Low => this._low; - public TSeries Close => this._close; - public TSeries Volume => this._volume; - public TSeries HL2 => this._hl2; - public TSeries OC2 => this._oc2; - public TSeries OHL3 => this._ohl3; - public TSeries HLC3 => this._hlc3; - public TSeries OHLC4 => this._ohlc4; - public TSeries HLCC4 => this._hlcc4; + public TSeries Open => this._open; + public TSeries High => this._high; + public TSeries Low => this._low; + public TSeries Close => this._close; + public TSeries Volume => this._volume; + public TSeries HL2 => this._hl2; + public TSeries OC2 => this._oc2; + public TSeries OHL3 => this._ohl3; + public TSeries HLC3 => this._hlc3; + public TSeries OHLC4 => this._ohlc4; + public TSeries HLCC4 => this._hlcc4; - public TBars() { } + public TBars() { } - public TBars(string Name) { - this.Name = Name; - } - - public (DateTime t, double o, double h, double l, double c, double v) Last => this[^1]; - public TBars Tail(int count = 10) - { - TBars outBars = new(); - if (count > this.Count) { count = this.Count; } - for (int i = this.Count - count; i < this.Count; i++) { outBars.Add(this[i]); } - return outBars; - } - public TSeries Select(int source) - { - return source switch + public TBars(string Name) { - 0 => _open, - 1 => _high, - 2 => _low, - 3 => _close, - 4 => _hl2, - 5 => _oc2, - 6 => _ohl3, - 7 => _hlc3, - 8 => _ohlc4, - _ => _hlcc4, - }; - } - public static string SelectStr(int source) - { - return source switch - { - 0 => "Open", - 1 => "High", - 2 => "Low", - 3 => "Close", - 4 => "HL2", - 5 => "OC2", - 6 => "OHL3", - 7 => "HLC3", - 8 => "OHLC4", - _ => "HLCC4", - }; - } - - public virtual (DateTime t, double v) Add((double o, double h, double l, double c, double v) p, bool update = false) => - Add((t: (this.Count == 0) ? DateTime.Today : this[^1].t.AddDays(1),p.o,p.h,p.l,p.c,p.v),update); - - public virtual (DateTime t, double v) Add(double o, double h, double l, double c, double v, bool update = false) => - Add((o,h,l,c,v),update); - - public virtual (DateTime t, double v) Add(DateTime t, double o, double h, double l, double c, double v, bool update = false) => - this.Add((t, o, h, l, c, v), update); - - public virtual (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - if (update) { this[^1] = TBar; } else { base.Add(TBar); } - - _open.Add((TBar.t, TBar.o), update); - _high.Add((TBar.t, TBar.h), update); - _low.Add((TBar.t, TBar.l), update); - _close.Add((TBar.t, TBar.c), update); - _volume.Add((TBar.t, TBar.v), update); - _hl2.Add((TBar.t, (TBar.h + TBar.l) * 0.5), update); - _oc2.Add((TBar.t, (TBar.o + TBar.c) * 0.5), update); - _ohl3.Add((TBar.t, (TBar.o + TBar.h + TBar.l) * 0.333333333333333), update); - _hlc3.Add((TBar.t, (TBar.h + TBar.l + TBar.c) * 0.333333333333333), update); - _ohlc4.Add((TBar.t, (TBar.o + TBar.h + TBar.l + TBar.c) * 0.25), update); - _hlcc4.Add((TBar.t, (TBar.h + TBar.l + TBar.c + TBar.c) * 0.25), update); - - this.OnEvent(update); - return (TBar.t, (TBar.o + TBar.h + TBar.l + TBar.c) * 0.25); - } - - public delegate void NewDataEventHandler(object source, TSeriesEventArgs args); - public event NewDataEventHandler Pub; - protected virtual void OnEvent(bool update = false) { if (Pub != null && Pub.Target != this) { - Pub(this, new TSeriesEventArgs { update = update }); } } - - public void Sub(object source, TSeriesEventArgs e) { TBars ss = (TBars)source; if (ss.Count > 1) { - for (int i = 0; i < ss.Count; i++) { this.Add(ss[i]); } - } else { - this.Add(ss[^1], e.update); + this.Name = Name; } - } - /// common helpers - public static void BufferTrim(System.Collections.Generic.List buffer, double value, int period, bool update) { - if (!update) { - buffer.Add(value); - if (buffer.Count > period && period > 0) { buffer.RemoveAt(0); } - return; - } - buffer[^1] = value; - } - public virtual void Reset() { - } + public (DateTime t, double o, double h, double l, double c, double v) Last => this[^1]; + public TBars Tail(int count = 10) + { + TBars outBars = new(); + if (count > this.Count) { count = this.Count; } + for (int i = this.Count - count; i < this.Count; i++) { outBars.Add(this[i]); } + return outBars; + } + public TSeries Select(int source) + { + return source switch + { + 0 => _open, + 1 => _high, + 2 => _low, + 3 => _close, + 4 => _hl2, + 5 => _oc2, + 6 => _ohl3, + 7 => _hlc3, + 8 => _ohlc4, + _ => _hlcc4, + }; + } + public static string SelectStr(int source) + { + return source switch + { + 0 => "Open", + 1 => "High", + 2 => "Low", + 3 => "Close", + 4 => "HL2", + 5 => "OC2", + 6 => "OHL3", + 7 => "HLC3", + 8 => "OHLC4", + _ => "HLCC4", + }; + } + + public virtual (DateTime t, double v) Add((double o, double h, double l, double c, double v) p, bool update = false) => + Add((t: (this.Count == 0) ? DateTime.Today : this[^1].t.AddDays(1), p.o, p.h, p.l, p.c, p.v), update); + + public virtual (DateTime t, double v) Add(double o, double h, double l, double c, double v, bool update = false) => + Add((o, h, l, c, v), update); + + public virtual (DateTime t, double v) Add(DateTime t, double o, double h, double l, double c, double v, bool update = false) => + this.Add((t, o, h, l, c, v), update); + + public virtual (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) + { + if (update) { this[^1] = TBar; } else { base.Add(TBar); } + + _open.Add((TBar.t, TBar.o), update); + _high.Add((TBar.t, TBar.h), update); + _low.Add((TBar.t, TBar.l), update); + _close.Add((TBar.t, TBar.c), update); + _volume.Add((TBar.t, TBar.v), update); + _hl2.Add((TBar.t, (TBar.h + TBar.l) * 0.5), update); + _oc2.Add((TBar.t, (TBar.o + TBar.c) * 0.5), update); + _ohl3.Add((TBar.t, (TBar.o + TBar.h + TBar.l) * 0.333333333333333), update); + _hlc3.Add((TBar.t, (TBar.h + TBar.l + TBar.c) * 0.333333333333333), update); + _ohlc4.Add((TBar.t, (TBar.o + TBar.h + TBar.l + TBar.c) * 0.25), update); + _hlcc4.Add((TBar.t, (TBar.h + TBar.l + TBar.c + TBar.c) * 0.25), update); + + this.OnEvent(update); + return (TBar.t, (TBar.o + TBar.h + TBar.l + TBar.c) * 0.25); + } + + public delegate void NewDataEventHandler(object source, TSeriesEventArgs args); + public event NewDataEventHandler Pub; + protected virtual void OnEvent(bool update = false) + { + if (Pub != null && Pub.Target != this) + { + Pub(this, new TSeriesEventArgs { update = update }); + } + } + + public void Sub(object source, TSeriesEventArgs e) + { + TBars ss = (TBars)source; if (ss.Count > 1) + { + for (int i = 0; i < ss.Count; i++) { this.Add(ss[i]); } + } + else + { + this.Add(ss[^1], e.update); + } + } + + /// common helpers + public static void BufferTrim(System.Collections.Generic.List buffer, double value, int period, bool update) + { + if (!update) + { + buffer.Add(value); + if (buffer.Count > period && period > 0) { buffer.RemoveAt(0); } + return; + } + buffer[^1] = value; + } + public virtual void Reset() + { + } } diff --git a/Calculations/_Updated/TEMA_Series.cs b/Calculations/_Updated/TEMA_Series.cs index f48019fa..88218161 100644 --- a/Calculations/_Updated/TEMA_Series.cs +++ b/Calculations/_Updated/TEMA_Series.cs @@ -18,103 +18,117 @@ Remark: */ -public class TEMA_Series : TSeries { - private double _k; - private double _sum, _oldsum; - private double _lastema1, _oldema1, _lastema2, _oldema2, _lastema3, _oldema3; - private int _len; - private readonly bool _useSMA; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class TEMA_Series : TSeries +{ + private double _k; + private double _sum, _oldsum; + private double _lastema1, _oldema1, _lastema2, _oldema2, _lastema3, _oldema3; + private int _len; + private readonly bool _useSMA; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; -//core constructor - public TEMA_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - _useSMA = useSMA; - Name = $"TEMA({period})"; - _k = 2.0 / (_period + 1); - _len = 0; - _sum = _oldsum = _lastema1 = _lastema2 = _lastema3 = 0; - } - public TEMA_Series() : this(0, false, true) {} - public TEMA_Series(int period) : this(period, false, true) {} - public TEMA_Series(TBars source) : this(source.Close, 0, false) {} - public TEMA_Series(TBars source, int period) : this(source.Close, period, false) {} - public TEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} - public TEMA_Series(TSeries source, int period) : this(source, period, false, true) {} - public TEMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} - public TEMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - -// core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { - _lastema1 = _oldema1; - _lastema2 = _oldema2; - _lastema3 = _oldema3; - _sum = _oldsum; - } - else { - _oldema1 = _lastema1; - _oldema2 = _lastema2; - _oldema3 = _lastema3; - _oldsum = _sum; - _len++; - } + //core constructor + public TEMA_Series(int period, bool useNaN, bool useSMA) + { + _period = period; + _NaN = useNaN; + _useSMA = useSMA; + Name = $"TEMA({period})"; + _k = 2.0 / (_period + 1); + _len = 0; + _sum = _oldsum = _lastema1 = _lastema2 = _lastema3 = 0; + } + public TEMA_Series() : this(0, false, true) { } + public TEMA_Series(int period) : this(period, false, true) { } + public TEMA_Series(TBars source) : this(source.Close, 0, false) { } + public TEMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public TEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public TEMA_Series(TSeries source, int period) : this(source, period, false, true) { } + public TEMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) { } + public TEMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } - if (_period == 0) { _k = 2.0 / (_len + 1); } + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (update) + { + _lastema1 = _oldema1; + _lastema2 = _oldema2; + _lastema3 = _oldema3; + _sum = _oldsum; + } + else + { + _oldema1 = _lastema1; + _oldema2 = _lastema2; + _oldema3 = _lastema3; + _oldsum = _sum; + _len++; + } - double _ema1, _ema2, _ema3, _tema; - if (this.Count == 0) { - _ema1 = _ema2 = _ema3 =_sum = TValue.v; - } - else if (_len <= _period && _useSMA && _period != 0) { - _sum += TValue.v; - _ema1 = _sum / Math.Min(_len, _period); - _ema2 = _ema1; - _ema3 = _ema2; - } - else { - _ema1 = (TValue.v - _lastema1) * _k + _lastema1; - _ema2 = (_ema1 - _lastema2) * _k + _lastema2; - _ema3 = (_ema2 - _lastema3) * _k + _lastema3; - } + if (_period == 0) { _k = 2.0 / (_len + 1); } - _tema = (3 * (_ema1 - _ema2)) + _ema3; + double _ema1, _ema2, _ema3, _tema; + if (this.Count == 0) + { + _ema1 = _ema2 = _ema3 = _sum = TValue.v; + } + else if (_len <= _period && _useSMA && _period != 0) + { + _sum += TValue.v; + _ema1 = _sum / Math.Min(_len, _period); + _ema2 = _ema1; + _ema3 = _ema2; + } + else + { + _ema1 = (TValue.v - _lastema1) * _k + _lastema1; + _ema2 = (_ema1 - _lastema2) * _k + _lastema2; + _ema3 = (_ema2 - _lastema3) * _k + _lastema3; + } - _lastema1 = Double.IsNaN(_ema1)?_lastema1:_ema1; - _lastema2 = Double.IsNaN(_ema2)?_lastema2:_ema2; - _lastema3 = Double.IsNaN(_ema3) ? _lastema3 : _ema3; + _tema = (3 * (_ema1 - _ema2)) + _ema3; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _tema); - return base.Add(res, update); - } - -//variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _sum = _oldsum = _lastema1 = _lastema2 = 0; - _len = 0; - } + _lastema1 = Double.IsNaN(_ema1) ? _lastema1 : _ema1; + _lastema2 = Double.IsNaN(_ema2) ? _lastema2 : _ema2; + _lastema3 = Double.IsNaN(_ema3) ? _lastema3 : _ema3; + + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _tema); + return base.Add(res, update); + } + + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() + { + _sum = _oldsum = _lastema1 = _lastema2 = 0; + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/TRIMA_Series.cs b/Calculations/_Updated/TRIMA_Series.cs index 050a4b88..703f594d 100644 --- a/Calculations/_Updated/TRIMA_Series.cs +++ b/Calculations/_Updated/TRIMA_Series.cs @@ -15,70 +15,80 @@ Remark: */ -public class TRIMA_Series : TSeries { - private readonly int _p1a, _p1b; - private readonly SMA_Series sma, trima; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class TRIMA_Series : TSeries +{ + private readonly int _p1a, _p1b; + private readonly SMA_Series sma, trima; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public TRIMA_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"xMA({period})"; - _p1a = (int)Math.Floor((period * 0.5) + 1); - _p1b = (int)Math.Ceiling(0.5 * period); - sma = new(_p1a); - trima = new(_p1b); + //core constructors + public TRIMA_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"xMA({period})"; + _p1a = (int)Math.Floor((period * 0.5) + 1); + _p1b = (int)Math.Ceiling(0.5 * period); + sma = new(_p1a); + trima = new(_p1b); - } - public TRIMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public TRIMA_Series() : this(period: 0, useNaN: false) { } - public TRIMA_Series(int period) : this(period: period, useNaN: false) { } - public TRIMA_Series(TBars source) : this(source.Close, 0, false) { } - public TRIMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public TRIMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public TRIMA_Series(TSeries source) : this(source, 0, false) { } - public TRIMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + } + public TRIMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public TRIMA_Series() : this(period: 0, useNaN: false) { } + public TRIMA_Series(int period) : this(period: period, useNaN: false) { } + public TRIMA_Series(TBars source) : this(source.Close, 0, false) { } + public TRIMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public TRIMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public TRIMA_Series(TSeries source) : this(source, 0, false) { } + public TRIMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, Double.NaN), update); + } - var _sma = sma.Add(TValue, update); - var _trima = trima.Add(_sma, update); + var _sma = sma.Add(TValue, update); + var _trima = trima.Add(_sma, update); - var res = (_trima.t, Count < _period - 1 && _NaN ? double.NaN : _trima.v); - return base.Add(res, update); - } + var res = (_trima.t, Count < _period - 1 && _NaN ? double.NaN : _trima.v); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - sma.Reset(); - trima.Reset(); - } + //reset calculation + public override void Reset() + { + sma.Reset(); + trima.Reset(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/TRIX_Series.cs b/Calculations/_Updated/TRIX_Series.cs index f955bf04..049c23da 100644 --- a/Calculations/_Updated/TRIX_Series.cs +++ b/Calculations/_Updated/TRIX_Series.cs @@ -14,105 +14,119 @@ Sources: */ -public class TRIX_Series : TSeries { - private readonly double _k; - private readonly System.Collections.Generic.List _buffer1 = new(); - private readonly System.Collections.Generic.List _buffer2 = new(); - private readonly System.Collections.Generic.List _buffer3 = new(); - private double _lastema1, _lastema2, _lastema3; - private double _llastema1, _llastema2, _llastema3; - private int _len; - private readonly bool _useSMA; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class TRIX_Series : TSeries +{ + private readonly double _k; + private readonly System.Collections.Generic.List _buffer1 = new(); + private readonly System.Collections.Generic.List _buffer2 = new(); + private readonly System.Collections.Generic.List _buffer3 = new(); + private double _lastema1, _lastema2, _lastema3; + private double _llastema1, _llastema2, _llastema3; + private int _len; + private readonly bool _useSMA; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; -//core constructors + //core constructors - public TRIX_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - _useSMA = useSMA; - Name = $"TRIX({period})"; - _k = 2.0 / (_period + 1); - _len = 0; - _lastema1 = _llastema1 = _lastema2 = _llastema2 = _lastema3 = _llastema3 = 0; - } - public TRIX_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public TRIX_Series() : this(0, false, true) {} - public TRIX_Series(int period) : this(period, false, true) {} - public TRIX_Series(TBars source) : this(source.Close, 0, false) {} - public TRIX_Series(TBars source, int period) : this(source.Close, period, false) {} - public TRIX_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) {} - public TRIX_Series(TSeries source, int period) : this(source, period, false, true) {} - public TRIX_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) {} + public TRIX_Series(int period, bool useNaN, bool useSMA) + { + _period = period; + _NaN = useNaN; + _useSMA = useSMA; + Name = $"TRIX({period})"; + _k = 2.0 / (_period + 1); + _len = 0; + _lastema1 = _llastema1 = _lastema2 = _llastema2 = _lastema3 = _llastema3 = 0; + } + public TRIX_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public TRIX_Series() : this(0, false, true) { } + public TRIX_Series(int period) : this(period, false, true) { } + public TRIX_Series(TBars source) : this(source.Close, 0, false) { } + public TRIX_Series(TBars source, int period) : this(source.Close, period, false) { } + public TRIX_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public TRIX_Series(TSeries source, int period) : this(source, period, false, true) { } + public TRIX_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (double.IsNaN(TValue.v)) { - return base.Add((TValue.t, Double.NaN), update); - } - if (_len == 0) { _lastema1 = _lastema2 = _lastema3 = TValue.v; } - if (update) { _lastema1 = _llastema1; _lastema2 = _llastema2; _lastema3 = _llastema3; } - else { _llastema1 = _lastema1; _llastema2 = _lastema2; _llastema3 = _lastema3; _len++; - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (double.IsNaN(TValue.v)) + { + return base.Add((TValue.t, Double.NaN), update); + } + if (_len == 0) { _lastema1 = _lastema2 = _lastema3 = TValue.v; } + if (update) { _lastema1 = _llastema1; _lastema2 = _llastema2; _lastema3 = _llastema3; } + else + { + _llastema1 = _lastema1; _llastema2 = _lastema2; _llastema3 = _lastema3; _len++; + } - double _ema1, _ema2, _ema3; - if ((this.Count < _period) && _useSMA) { - BufferTrim(_buffer1, TValue.v, _period, update); - _ema1 = 0; - for (int i = 0; i < _buffer1.Count; i++) { _ema1 += _buffer1[i]; } - _ema1 /= _buffer1.Count; + double _ema1, _ema2, _ema3; + if ((this.Count < _period) && _useSMA) + { + BufferTrim(_buffer1, TValue.v, _period, update); + _ema1 = 0; + for (int i = 0; i < _buffer1.Count; i++) { _ema1 += _buffer1[i]; } + _ema1 /= _buffer1.Count; - BufferTrim(_buffer2, _ema1, _period, update); - _ema2 = 0; - for (int i = 0; i < _buffer2.Count; i++) { _ema2 += _buffer2[i]; } - _ema2 /= _buffer2.Count; + BufferTrim(_buffer2, _ema1, _period, update); + _ema2 = 0; + for (int i = 0; i < _buffer2.Count; i++) { _ema2 += _buffer2[i]; } + _ema2 /= _buffer2.Count; - BufferTrim(_buffer3, _ema2, _period, update); - _ema3 = 0; - for (int i = 0; i < _buffer3.Count; i++) { _ema3 += _buffer3[i]; } - _ema3 /= _buffer3.Count; - } - else { - _ema1 = (TValue.v - _lastema1) * _k + _lastema1; - _ema2 = (_ema1 - _lastema2) * _k + _lastema2; - _ema3 = (_ema2 - _lastema3) * _k + _lastema3; - } - double _trix = 100 * (_ema3 - _lastema3) / _lastema3; - _lastema1 = _ema1; - _lastema2 = _ema2; - _lastema3 = _ema3; + BufferTrim(_buffer3, _ema2, _period, update); + _ema3 = 0; + for (int i = 0; i < _buffer3.Count; i++) { _ema3 += _buffer3[i]; } + _ema3 /= _buffer3.Count; + } + else + { + _ema1 = (TValue.v - _lastema1) * _k + _lastema1; + _ema2 = (_ema1 - _lastema2) * _k + _lastema2; + _ema3 = (_ema2 - _lastema3) * _k + _lastema3; + } + double _trix = 100 * (_ema3 - _lastema3) / _lastema3; + _lastema1 = _ema1; + _lastema2 = _ema2; + _lastema3 = _ema3; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _trix); - return base.Add(res, update); - } - -//variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } - - //reset calculation - public override void Reset() { - _len = 0; - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _trix); + return base.Add(res, update); + } + + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } + + //reset calculation + public override void Reset() + { + _len = 0; + } } \ No newline at end of file diff --git a/Calculations/_Updated/TR_Series.cs b/Calculations/_Updated/TR_Series.cs index 9365d5f6..308e08e8 100644 --- a/Calculations/_Updated/TR_Series.cs +++ b/Calculations/_Updated/TR_Series.cs @@ -18,62 +18,74 @@ Sources: */ -public class TR_Series : TSeries { - protected readonly TBars _data; - private double _cm1, _cm1_o; +public class TR_Series : TSeries +{ + protected readonly TBars _data; + private double _cm1, _cm1_o; - //core constructors - public TR_Series() { - Name = $"TR()"; - _cm1 = _cm1_o = double.NaN; - } - public TR_Series(TBars source) { - _data = source; - Name = $"TR({(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _cm1 = _cm1_o = double.NaN; - _data.Pub += Sub; - Add(data: _data); - } + //core constructors + public TR_Series() + { + Name = $"TR()"; + _cm1 = _cm1_o = double.NaN; + } + public TR_Series(TBars source) + { + _data = source; + Name = $"TR({(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _cm1 = _cm1_o = double.NaN; + _data.Pub += Sub; + Add(data: _data); + } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) + { - if (update) { - _cm1 = _cm1_o; - } - else { - _cm1_o = _cm1; - } + if (update) + { + _cm1 = _cm1_o; + } + else + { + _cm1_o = _cm1; + } - if (_cm1 is double.NaN) { - _cm1 = TBar.c; - } + if (_cm1 is double.NaN) + { + _cm1 = TBar.c; + } - double d1 = Math.Abs(TBar.h - TBar.l); - double d2 = Math.Abs(_cm1 - TBar.h); - double d3 = Math.Abs(_cm1 - TBar.l); - _cm1 = TBar.c; - var ret = (TBar.t, Math.Max(d1, Math.Max(d2, d3))); - return base.Add(ret, update); + double d1 = Math.Abs(TBar.h - TBar.l); + double d2 = Math.Abs(_cm1 - TBar.h); + double d3 = Math.Abs(_cm1 - TBar.l); + _cm1 = TBar.c; + var ret = (TBar.t, Math.Max(d1, Math.Max(d2, d3))); + return base.Add(ret, update); - } + } - public new void Add(TBars data) { - foreach (var item in data) { Add(item, false); } - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TBar: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TBar: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TBar: _data.Last, update: e.update); - } + public new void Add(TBars data) + { + foreach (var item in data) { Add(item, false); } + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TBar: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TBar: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TBar: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _cm1 = _cm1_o = double.NaN; - } + //reset calculation + public override void Reset() + { + _cm1 = _cm1_o = double.NaN; + } } \ No newline at end of file diff --git a/Calculations/_Updated/TSeries.cs b/Calculations/_Updated/TSeries.cs index 4a8a6312..00ba50a2 100644 --- a/Calculations/_Updated/TSeries.cs +++ b/Calculations/_Updated/TSeries.cs @@ -15,103 +15,123 @@ TSeries is the cornerstone of all QuanTAlib classes. - includes publishing and subscribing methods that attach to events */ -public class TSeriesEventArgs : EventArgs { - public bool update { get; set; } +public class TSeriesEventArgs : EventArgs +{ + public bool update { get; set; } } -public class TSeries : List<(DateTime t, double v)> { - private readonly (DateTime t, double v) Default = (DateTime.MinValue, double.NaN); - public IEnumerable t => this.Select(item => item.t); - public IEnumerable v => this.Select(item => item.v); - public (DateTime t, double v) Last => Count > 0 ? this[^1] : Default; +public class TSeries : List<(DateTime t, double v)> +{ + private readonly (DateTime t, double v) Default = (DateTime.MinValue, double.NaN); + public IEnumerable t => this.Select(item => item.t); + public IEnumerable v => this.Select(item => item.v); + public (DateTime t, double v) Last => Count > 0 ? this[^1] : Default; - public int Length => Count; - public string Name { get; set; } - public int Keep = 0; + public int Length => Count; + public string Name { get; set; } + public int Keep = 0; - public TSeries() { - this.Name = "data"; - } + public TSeries() + { + this.Name = "data"; + } - public TSeries(string Name) { - this.Name = Name; - } + public TSeries(string Name) + { + this.Name = Name; + } - public virtual (DateTime t, double v) Add(double v, bool update = false) { - return Add((t: Count == 0 ? DateTime.Today : this[^1].t.AddDays(1), v), update); - } + public virtual (DateTime t, double v) Add(double v, bool update = false) + { + return Add((t: Count == 0 ? DateTime.Today : this[^1].t.AddDays(1), v), update); + } - public virtual (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - if (update) { - this[^1] = TValue; - } - else { - base.Add(TValue); - } + public virtual (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + if (update) + { + this[^1] = TValue; + } + else + { + base.Add(TValue); + } - OnEvent(update); - return TValue; - } + OnEvent(update); + return TValue; + } - public virtual (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) { - if (update) { - this[this.Count - 1] = (TBar.t, TBar.c); - } - else { - base.Add((TBar.t, TBar.c)); - } + public virtual (DateTime t, double v) Add((DateTime t, double o, double h, double l, double c, double v) TBar, bool update = false) + { + if (update) + { + this[this.Count - 1] = (TBar.t, TBar.c); + } + else + { + base.Add((TBar.t, TBar.c)); + } - OnEvent(update); - return (TBar.t, TBar.c); - } + OnEvent(update); + return (TBar.t, TBar.c); + } - public virtual (DateTime t, double v) Add(TSeries data) { - foreach (var item in data) { Add(item); } - return data.Last; - } + public virtual (DateTime t, double v) Add(TSeries data) + { + foreach (var item in data) { Add(item); } + return data.Last; + } - public virtual (DateTime t, double v) Add(TBars data) { - foreach (var item in data) { Add(item.c, false); } - return (data.Last.t, data.Last.c); - } + public virtual (DateTime t, double v) Add(TBars data) + { + foreach (var item in data) { Add(item.c, false); } + return (data.Last.t, data.Last.c); + } - public void Sub(object source, TSeriesEventArgs e) { - var data = (TSeries) source; - if (data == null) { return; } - foreach (var item in data) { Add(item); } - } + public void Sub(object source, TSeriesEventArgs e) + { + var data = (TSeries)source; + if (data == null) { return; } + foreach (var item in data) { Add(item); } + } - public delegate void NewEventHandler(object source, TSeriesEventArgs args); + public delegate void NewEventHandler(object source, TSeriesEventArgs args); - public event NewEventHandler Pub; + public event NewEventHandler Pub; - protected virtual void OnEvent(bool update = false) - { - if (Keep > 0) { - TrimToSize(keep:Keep); - } - Pub?.Invoke(this, new TSeriesEventArgs {update = update}); - } + protected virtual void OnEvent(bool update = false) + { + if (Keep > 0) + { + TrimToSize(keep: Keep); + } + Pub?.Invoke(this, new TSeriesEventArgs { update = update }); + } - /// common helpers - public static void BufferTrim(List buffer, double value, int period, bool update) { - if (!update) { - buffer.Add(value); - if (buffer.Count > period && period > 0) { buffer.RemoveAt(0); } - return; - } - buffer[^1] = value; - } - public virtual void Reset() { - } + /// common helpers + public static void BufferTrim(List buffer, double value, int period, bool update) + { + if (!update) + { + buffer.Add(value); + if (buffer.Count > period && period > 0) { buffer.RemoveAt(0); } + return; + } + buffer[^1] = value; + } + public virtual void Reset() + { + } - public void TrimToSize(int keep) { - if (keep >= this.Count) { - return; // No need to trim if the series is already smaller than or equal to n - } + public void TrimToSize(int keep) + { + if (keep >= this.Count) + { + return; // No need to trim if the series is already smaller than or equal to n + } - // Remove elements from the beginning of the list - int elementsToRemove = this.Count - keep; - RemoveRange(0, elementsToRemove); - } + // Remove elements from the beginning of the list + int elementsToRemove = this.Count - keep; + RemoveRange(0, elementsToRemove); + } } diff --git a/Calculations/_Updated/VAR_Series.cs b/Calculations/_Updated/VAR_Series.cs index fcd0caba..20b7a556 100644 --- a/Calculations/_Updated/VAR_Series.cs +++ b/Calculations/_Updated/VAR_Series.cs @@ -18,64 +18,73 @@ Remark: */ -public class VAR_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class VAR_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public VAR_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"VAR({period})"; - } - public VAR_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public VAR_Series() : this(period: 0, useNaN: false) { } - public VAR_Series(int period) : this(period: period, useNaN: false) { } - public VAR_Series(TBars source) : this(source.Close, 0, false) { } - public VAR_Series(TBars source, int period) : this(source.Close, period, false) { } - public VAR_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public VAR_Series(TSeries source) : this(source, 0, false) { } - public VAR_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public VAR_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"VAR({period})"; + } + public VAR_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public VAR_Series() : this(period: 0, useNaN: false) { } + public VAR_Series(int period) : this(period: period, useNaN: false) { } + public VAR_Series(TBars source) : this(source.Close, 0, false) { } + public VAR_Series(TBars source, int period) : this(source.Close, period, false) { } + public VAR_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public VAR_Series(TSeries source) : this(source, 0, false) { } + public VAR_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _sma = _buffer.Average(); + double _sma = _buffer.Average(); - double _pvar = 0; - for (int i = 0; i < _buffer.Count; i++) { _pvar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } - _pvar /= this._buffer.Count; + double _pvar = 0; + for (int i = 0; i < _buffer.Count; i++) { _pvar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } + _pvar /= this._buffer.Count; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _pvar); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _pvar); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/WMAPE_Series.cs b/Calculations/_Updated/WMAPE_Series.cs index bbbdeee4..7dfc95cb 100644 --- a/Calculations/_Updated/WMAPE_Series.cs +++ b/Calculations/_Updated/WMAPE_Series.cs @@ -15,68 +15,78 @@ Sources: */ -public class WMAPE_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class WMAPE_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public WMAPE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"WMAPE({period})"; - } - public WMAPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public WMAPE_Series() : this(period: 0, useNaN: false) { } - public WMAPE_Series(int period) : this(period: period, useNaN: false) { } - public WMAPE_Series(TBars source) : this(source.Close, 0, false) { } - public WMAPE_Series(TBars source, int period) : this(source.Close, period, false) { } - public WMAPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public WMAPE_Series(TSeries source) : this(source, 0, false) { } - public WMAPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public WMAPE_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"WMAPE({period})"; + } + public WMAPE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public WMAPE_Series() : this(period: 0, useNaN: false) { } + public WMAPE_Series(int period) : this(period: period, useNaN: false) { } + public WMAPE_Series(TBars source) : this(source.Close, 0, false) { } + public WMAPE_Series(TBars source, int period) : this(source.Close, period, false) { } + public WMAPE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public WMAPE_Series(TSeries source) : this(source, 0, false) { } + public WMAPE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - double _sma = _buffer.Average(); + double _sma = _buffer.Average(); - double _div = 0; - double _wmape = 0; - for (int i = 0; i < _buffer.Count; i++) { - _wmape += Math.Abs(_buffer[i] - _sma); - _div += Math.Abs(_buffer[i]); - } - _wmape = (_div != 0) ? _wmape / _div : double.PositiveInfinity; + double _div = 0; + double _wmape = 0; + for (int i = 0; i < _buffer.Count; i++) + { + _wmape += Math.Abs(_buffer[i] - _sma); + _div += Math.Abs(_buffer[i]); + } + _wmape = (_div != 0) ? _wmape / _div : double.PositiveInfinity; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _wmape); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _wmape); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/WMA_Series.cs b/Calculations/_Updated/WMA_Series.cs index f423e3ca..957660fd 100644 --- a/Calculations/_Updated/WMA_Series.cs +++ b/Calculations/_Updated/WMA_Series.cs @@ -17,88 +17,101 @@ Sources: */ -public class WMA_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - private System.Collections.Generic.List _weights; - protected int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - protected int _len; - public int Len { - get { return _len; } - set { _len = value; } - } +public class WMA_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + private System.Collections.Generic.List _weights; + protected int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + protected int _len; + public int Len + { + get { return _len; } + set { _len = value; } + } - //core constructors - public WMA_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"WMA({period})"; - _len = 1; - _weights = CalculateWeights(_period); - } - public WMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public WMA_Series() : this(period: 0, useNaN: false) { } - public WMA_Series(int period) : this(period: period, useNaN: false) { } - public WMA_Series(TBars source) : this(source.Close, 0, false) { } - public WMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public WMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public WMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public WMA_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"WMA({period})"; + _len = 1; + _weights = CalculateWeights(_period); + } + public WMA_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public WMA_Series() : this(period: 0, useNaN: false) { } + public WMA_Series(int period) : this(period: period, useNaN: false) { } + public WMA_Series(TBars source) : this(source.Close, 0, false) { } + public WMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public WMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public WMA_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update=false) { - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - if (_period == 0) { - _weights = CalculateWeights(_len); - _len++; - } - double _wma = 0; - double totalWeights = (_buffer.Count * (_buffer.Count + 1)) * 0.5; - object lockObj = new object(); - Parallel.For(0, _buffer.Count, i => - { - double temp = _buffer[i] * this._weights[i]; - lock (lockObj) { _wma += temp; } - }); - _wma /= totalWeights; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _wma); - return base.Add(res, update); - } + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + if (_period == 0) + { + _weights = CalculateWeights(_len); + _len++; + } + double _wma = 0; + double totalWeights = (_buffer.Count * (_buffer.Count + 1)) * 0.5; + object lockObj = new object(); + Parallel.For(0, _buffer.Count, i => + { + double temp = _buffer[i] * this._weights[i]; + lock (lockObj) { _wma += temp; } + }); + _wma /= totalWeights; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _wma); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //calculating weights - private static List CalculateWeights(int period) { - List weights = new List(period); - for (int i = 0; i < period; i++) { - weights.Add(i + 1); - } - return weights; - } + //calculating weights + private static List CalculateWeights(int period) + { + List weights = new List(period); + for (int i = 0; i < period; i++) + { + weights.Add(i + 1); + } + return weights; + } - //reset calculation - public override void Reset() { - _len = 0; - _weights = CalculateWeights(_period); - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _len = 0; + _weights = CalculateWeights(_period); + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/ZLEMA_Series.cs b/Calculations/_Updated/ZLEMA_Series.cs index 0f7cd56c..d0c2158d 100644 --- a/Calculations/_Updated/ZLEMA_Series.cs +++ b/Calculations/_Updated/ZLEMA_Series.cs @@ -21,75 +21,85 @@ Remark: */ -public class ZLEMA_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - private int _len; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly EMA_Series _ema; +public class ZLEMA_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + private int _len; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly EMA_Series _ema; - //core constructor - public ZLEMA_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - Name = $"ZLEMA({period})"; - _len = 1; - _ema = new(period); - } - //generic constructors (source) + //core constructor + public ZLEMA_Series(int period, bool useNaN, bool useSMA) + { + _period = period; + _NaN = useNaN; + Name = $"ZLEMA({period})"; + _len = 1; + _ema = new(period); + } + //generic constructors (source) - public ZLEMA_Series() : this(0, false, true) { } - public ZLEMA_Series(int period) : this(period, false, true) { } - public ZLEMA_Series(TBars source) : this(source.Close, 0, false) { } - public ZLEMA_Series(TBars source, int period) : this(source.Close, period, false) { } - public ZLEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public ZLEMA_Series(TSeries source, int period) : this(source, period, false, true) { } - public ZLEMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) { } - public ZLEMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } + public ZLEMA_Series() : this(0, false, true) { } + public ZLEMA_Series(int period) : this(period, false, true) { } + public ZLEMA_Series(TBars source) : this(source.Close, 0, false) { } + public ZLEMA_Series(TBars source, int period) : this(source.Close, period, false) { } + public ZLEMA_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public ZLEMA_Series(TSeries source, int period) : this(source, period, false, true) { } + public ZLEMA_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) { } + public ZLEMA_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - int _lag; - if (_period == 0) { - _lag = (int)((_len - 1) * 0.5); - _len++; - } - else { _lag = (int)((_period - 1) * 0.5); } - _lag = Math.Min(_lag, _buffer.Count - 1); - _lag = Math.Max(_lag, 0) + 1; - double _zlValue = 2 * TValue.v - _buffer[^_lag]; - double _zlema = _ema.Add((TValue.t, _zlValue), update).v; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _zlema); - return base.Add(res, update); - } + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + int _lag; + if (_period == 0) + { + _lag = (int)((_len - 1) * 0.5); + _len++; + } + else { _lag = (int)((_period - 1) * 0.5); } + _lag = Math.Min(_lag, _buffer.Count - 1); + _lag = Math.Max(_lag, 0) + 1; + double _zlValue = 2 * TValue.v - _buffer[^_lag]; + double _zlema = _ema.Add((TValue.t, _zlValue), update).v; + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _zlema); + return base.Add(res, update); + } - //variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - _ema.Reset(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + _ema.Reset(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/ZL_Series.cs b/Calculations/_Updated/ZL_Series.cs index 033088e4..f7f6088b 100644 --- a/Calculations/_Updated/ZL_Series.cs +++ b/Calculations/_Updated/ZL_Series.cs @@ -17,74 +17,84 @@ Sources: */ -public class ZL_Series: TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - private int _len; - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; - private readonly EMA_Series _ema; +public class ZL_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + private int _len; + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; + private readonly EMA_Series _ema; - //core constructor - public ZL_Series(int period, bool useNaN, bool useSMA) { - _period = period; - _NaN = useNaN; - Name = $"ZL({period})"; - _len = 1; - _ema = new(period); - } - //generic constructors (source) + //core constructor + public ZL_Series(int period, bool useNaN, bool useSMA) + { + _period = period; + _NaN = useNaN; + Name = $"ZL({period})"; + _len = 1; + _ema = new(period); + } + //generic constructors (source) - public ZL_Series() : this(0, false, true) { } - public ZL_Series(int period) : this(period, false, true) { } - public ZL_Series(TBars source) : this(source.Close, 0, false) { } - public ZL_Series(TBars source, int period) : this(source.Close, period, false) { } - public ZL_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public ZL_Series(TSeries source, int period) : this(source, period, false, true) { } - public ZL_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) { } - public ZL_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } + public ZL_Series() : this(0, false, true) { } + public ZL_Series(int period) : this(period, false, true) { } + public ZL_Series(TBars source) : this(source.Close, 0, false) { } + public ZL_Series(TBars source, int period) : this(source.Close, period, false) { } + public ZL_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public ZL_Series(TSeries source, int period) : this(source, period, false, true) { } + public ZL_Series(TSeries source, int period, bool useNaN) : this(source, period, useNaN, true) { } + public ZL_Series(TSeries source, int period, bool useNaN, bool useSMA) : this(period, useNaN, useSMA) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); - int _lag; - if (_period == 0) { - _lag = (int)((_len - 1) * 0.5); - _len++; - } - else { _lag = (int)((_period - 1) * 0.5); } - _lag = Math.Min(_lag, _buffer.Count - 1); - _lag = Math.Max(_lag, 0) + 1; - double _zlValue = 2 * TValue.v - _buffer[^_lag]; + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + int _lag; + if (_period == 0) + { + _lag = (int)((_len - 1) * 0.5); + _len++; + } + else { _lag = (int)((_period - 1) * 0.5); } + _lag = Math.Min(_lag, _buffer.Count - 1); + _lag = Math.Max(_lag, 0) + 1; + double _zlValue = 2 * TValue.v - _buffer[^_lag]; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _zlValue); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _zlValue); + return base.Add(res, update); + } - //variation of Add() - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + //variation of Add() + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - _ema.Reset(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + _ema.Reset(); + } } \ No newline at end of file diff --git a/Calculations/_Updated/ZSCORE_Series.cs b/Calculations/_Updated/ZSCORE_Series.cs index 06c37130..d8e5699d 100644 --- a/Calculations/_Updated/ZSCORE_Series.cs +++ b/Calculations/_Updated/ZSCORE_Series.cs @@ -24,65 +24,74 @@ Calculation: */ -public class ZSCORE_Series : TSeries { - private readonly System.Collections.Generic.List _buffer = new(); - protected readonly int _period; - protected readonly bool _NaN; - protected readonly TSeries _data; +public class ZSCORE_Series : TSeries +{ + private readonly System.Collections.Generic.List _buffer = new(); + protected readonly int _period; + protected readonly bool _NaN; + protected readonly TSeries _data; - //core constructors - public ZSCORE_Series(int period, bool useNaN) { - _period = period; - _NaN = useNaN; - Name = $"ZSCORE({period})"; - } - public ZSCORE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) { - _data = source; - Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; - _data.Pub += Sub; - Add(_data); - } - public ZSCORE_Series() : this(period: 0, useNaN: false) { } - public ZSCORE_Series(int period) : this(period: period, useNaN: false) { } - public ZSCORE_Series(TBars source) : this(source.Close, 0, false) { } - public ZSCORE_Series(TBars source, int period) : this(source.Close, period, false) { } - public ZSCORE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } - public ZSCORE_Series(TSeries source) : this(source, 0, false) { } - public ZSCORE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } + //core constructors + public ZSCORE_Series(int period, bool useNaN) + { + _period = period; + _NaN = useNaN; + Name = $"ZSCORE({period})"; + } + public ZSCORE_Series(TSeries source, int period, bool useNaN) : this(period, useNaN) + { + _data = source; + Name = Name.Substring(0, Name.IndexOf(")")) + $", {(string.IsNullOrEmpty(_data.Name) ? "data" : _data.Name)})"; + _data.Pub += Sub; + Add(_data); + } + public ZSCORE_Series() : this(period: 0, useNaN: false) { } + public ZSCORE_Series(int period) : this(period: period, useNaN: false) { } + public ZSCORE_Series(TBars source) : this(source.Close, 0, false) { } + public ZSCORE_Series(TBars source, int period) : this(source.Close, period, false) { } + public ZSCORE_Series(TBars source, int period, bool useNaN) : this(source.Close, period, useNaN) { } + public ZSCORE_Series(TSeries source) : this(source, 0, false) { } + public ZSCORE_Series(TSeries source, int period) : this(source: source, period: period, useNaN: false) { } - ////////////////// - // core Add() algo - public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) { - BufferTrim(buffer:_buffer, value:TValue.v, period:_period, update: update); - double _sma = _buffer.Average(); + ////////////////// + // core Add() algo + public override (DateTime t, double v) Add((DateTime t, double v) TValue, bool update = false) + { + BufferTrim(buffer: _buffer, value: TValue.v, period: _period, update: update); + double _sma = _buffer.Average(); - double _pvar = 0; - for (int i = 0; i < _buffer.Count; i++) { _pvar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } - _pvar /= this._buffer.Count; - double _psdev = Math.Sqrt(_pvar); - double _zscore = (_psdev == 0) ? 1 : (TValue.v - _sma) / _psdev; + double _pvar = 0; + for (int i = 0; i < _buffer.Count; i++) { _pvar += (_buffer[i] - _sma) * (_buffer[i] - _sma); } + _pvar /= this._buffer.Count; + double _psdev = Math.Sqrt(_pvar); + double _zscore = (_psdev == 0) ? 1 : (TValue.v - _sma) / _psdev; - var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _zscore); - return base.Add(res, update); - } + var res = (TValue.t, Count < _period - 1 && _NaN ? double.NaN : _zscore); + return base.Add(res, update); + } - public override (DateTime t, double v) Add(TSeries data) { - if (data == null) { return (DateTime.Today, Double.NaN); } - foreach (var item in data) { Add(item, false); } - return _data.Last; - } - public (DateTime t, double v) Add(bool update) { - return this.Add(TValue: _data.Last, update: update); - } - public (DateTime t, double v) Add() { - return Add(TValue: _data.Last, update: false); - } - private new void Sub(object source, TSeriesEventArgs e) { - Add(TValue: _data.Last, update: e.update); - } + public override (DateTime t, double v) Add(TSeries data) + { + if (data == null) { return (DateTime.Today, Double.NaN); } + foreach (var item in data) { Add(item, false); } + return _data.Last; + } + public (DateTime t, double v) Add(bool update) + { + return this.Add(TValue: _data.Last, update: update); + } + public (DateTime t, double v) Add() + { + return Add(TValue: _data.Last, update: false); + } + private new void Sub(object source, TSeriesEventArgs e) + { + Add(TValue: _data.Last, update: e.update); + } - //reset calculation - public override void Reset() { - _buffer.Clear(); - } + //reset calculation + public override void Reset() + { + _buffer.Clear(); + } } \ No newline at end of file diff --git a/Indicators/Charts/2MACross_chart.cs b/Indicators/Charts/2MACross_chart.cs index 58a8063d..19ead156 100644 --- a/Indicators/Charts/2MACross_chart.cs +++ b/Indicators/Charts/2MACross_chart.cs @@ -4,270 +4,284 @@ using System.Linq; using TradingPlatform.BusinessLayer; namespace QuanTAlib; -public class MovingAverage_chart : Indicator { - #region Parameters - [InputParameter("MA1: Type:", 0, variants: new object[] - { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, - "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] - private int MA1type = 15; +public class MovingAverage_chart : Indicator +{ + #region Parameters + [InputParameter("MA1: Type:", 0, variants: new object[] + { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, + "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] + private int MA1type = 15; - [InputParameter("MA1: Smoothing period:", 1, 1, 999, 1, 1)] - private int MA1Period = 10; + [InputParameter("MA1: Smoothing period:", 1, 1, 999, 1, 1)] + private int MA1Period = 10; - [InputParameter("MA1: Data source:", 2, variants: new object[] - { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, - "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] - private int MA1DataSource = 3; + [InputParameter("MA1: Data source:", 2, variants: new object[] + { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, + "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] + private int MA1DataSource = 3; - [InputParameter("MA2: Type:", 3, variants: new object[] - { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, - "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] - private int MA2type = 16; + [InputParameter("MA2: Type:", 3, variants: new object[] + { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, + "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] + private int MA2type = 16; - [InputParameter("MA2: Smoothing period:", 4, 1, 999, 1, 1)] - private int MA2Period = 50; + [InputParameter("MA2: Smoothing period:", 4, 1, 999, 1, 1)] + private int MA2Period = 50; - [InputParameter("MA2: Data source:", 5, variants: new object[] - { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, - "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] - private int MA2DataSource = 8; + [InputParameter("MA2: Data source:", 5, variants: new object[] + { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, + "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] + private int MA2DataSource = 8; - [InputParameter("Long trades", 6)] - private bool LongTrades = true; + [InputParameter("Long trades", 6)] + private bool LongTrades = true; - [InputParameter("Short trades", 6)] - private bool ShortTrades = true; + [InputParameter("Short trades", 6)] + private bool ShortTrades = true; - #endregion Parameters + #endregion Parameters - protected HistoricalData History; - private TBars bars; + protected HistoricalData History; + private TBars bars; - /////// - private TSeries MA1, MA2; - private CROSS_Series trades; - private COMPARE_Series overunder; + /////// + private TSeries MA1, MA2; + private CROSS_Series trades; + private COMPARE_Series overunder; - /////// + /////// - public MovingAverage_chart() { - this.SeparateWindow = false; - this.Name = "MAs Crossover"; - this.AddLineSeries("MA1", Color.LimeGreen, 2, LineStyle.Solid); - this.AddLineSeries("MA2", Color.OrangeRed, 2, LineStyle.Solid); - } + public MovingAverage_chart() + { + this.SeparateWindow = false; + this.Name = "MAs Crossover"; + this.AddLineSeries("MA1", Color.LimeGreen, 2, LineStyle.Solid); + this.AddLineSeries("MA2", Color.OrangeRed, 2, LineStyle.Solid); + } - protected override void OnInit() { - this.bars = new(); - this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); - for (int i = this.History.Count - 1; i >= 0; i--) { - var rec = this.History[i, SeekOriginHistory.Begin]; - bars.Add(rec.TimeLeft, rec[PriceType.Open], - rec[PriceType.High], rec[PriceType.Low], - rec[PriceType.Close], rec[PriceType.Volume]); - } - this.Name = "MAs Cross: [ "; - switch (MA1type) { - case 0: - MA1 = new SMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"SMA"; - break; - case 1: - MA1 = new EMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"EMA"; - break; - case 2: - MA1 = new WMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"WMA"; - break; - case 3: - MA1 = new T3_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"T3"; - break; - case 4: - MA1 = new SMMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"SMMA"; - break; - case 5: - MA1 = new TRIMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"TRIMA"; - break; - case 6: - MA1 = new DWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"DWMA"; - break; - case 7: - MA1 = new FWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period); - this.Name += $"FWMA"; - break; - case 8: - MA1 = new DEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"DEMA"; - break; - case 9: - MA1 = new TEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"TEMA"; - break; - case 10: - MA1 = new ALMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"ALMA"; - break; - case 11: - MA1 = new HMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"HMA"; - break; - case 12: - MA1 = new HEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"HEMA"; - break; - case 13: - double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA1Period); - MA1 = new MAMA_Series(source: bars.Select(this.MA1DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); - this.Name += $"MAMA"; - break; - case 14: - MA1 = new KAMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"KAMA"; - break; - case 15: - MA1 = new ZLEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"ZLEMA"; - break; - default: - MA1 = new JMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"JMA"; - break; - } + protected override void OnInit() + { + this.bars = new(); + this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); + for (int i = this.History.Count - 1; i >= 0; i--) + { + var rec = this.History[i, SeekOriginHistory.Begin]; + bars.Add(rec.TimeLeft, rec[PriceType.Open], + rec[PriceType.High], rec[PriceType.Low], + rec[PriceType.Close], rec[PriceType.Volume]); + } + this.Name = "MAs Cross: [ "; + switch (MA1type) + { + case 0: + MA1 = new SMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"SMA"; + break; + case 1: + MA1 = new EMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"EMA"; + break; + case 2: + MA1 = new WMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"WMA"; + break; + case 3: + MA1 = new T3_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"T3"; + break; + case 4: + MA1 = new SMMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"SMMA"; + break; + case 5: + MA1 = new TRIMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"TRIMA"; + break; + case 6: + MA1 = new DWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"DWMA"; + break; + case 7: + MA1 = new FWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period); + this.Name += $"FWMA"; + break; + case 8: + MA1 = new DEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"DEMA"; + break; + case 9: + MA1 = new TEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"TEMA"; + break; + case 10: + MA1 = new ALMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"ALMA"; + break; + case 11: + MA1 = new HMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"HMA"; + break; + case 12: + MA1 = new HEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"HEMA"; + break; + case 13: + double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA1Period); + MA1 = new MAMA_Series(source: bars.Select(this.MA1DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); + this.Name += $"MAMA"; + break; + case 14: + MA1 = new KAMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"KAMA"; + break; + case 15: + MA1 = new ZLEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"ZLEMA"; + break; + default: + MA1 = new JMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"JMA"; + break; + } - this.Name = this.Name + $" ({MA1Period}:{TBars.SelectStr(this.MA1DataSource)}) : "; + this.Name = this.Name + $" ({MA1Period}:{TBars.SelectStr(this.MA1DataSource)}) : "; - switch (MA2type) { - case 0: - MA2 = new SMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"SMA"; - break; - case 1: - MA2 = new EMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"EMA"; - break; - case 2: - MA2 = new WMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"WMA"; - break; - case 3: - MA2 = new T3_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"T3"; - break; - case 4: - MA2 = new SMMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"SMMA"; - break; - case 5: - MA2 = new TRIMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"TRIMA"; - break; - case 6: - MA2 = new DWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"DWMA"; - break; - case 7: - MA2 = new FWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period); - this.Name += $"FWMA"; - break; - case 8: - MA2 = new DEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"DEMA"; - break; - case 9: - MA2 = new TEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"TEMA"; - break; - case 10: - MA2 = new ALMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"ALMA"; - break; - case 11: - MA2 = new HMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"HMA"; - break; - case 12: - MA2 = new HEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"HEMA"; - break; - case 13: - double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA2Period); - MA2 = new MAMA_Series(source: bars.Select(this.MA2DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); - this.Name += $"MAMA"; - break; - case 14: - MA2 = new KAMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"KAMA"; - break; - case 15: - MA2 = new ZLEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"ZLEMA"; - break; - default: - MA2 = new JMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"JMA"; - break; - } - this.Name += $"({MA2Period}:{TBars.SelectStr(this.MA2DataSource)}) ]"; + switch (MA2type) + { + case 0: + MA2 = new SMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"SMA"; + break; + case 1: + MA2 = new EMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"EMA"; + break; + case 2: + MA2 = new WMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"WMA"; + break; + case 3: + MA2 = new T3_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"T3"; + break; + case 4: + MA2 = new SMMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"SMMA"; + break; + case 5: + MA2 = new TRIMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"TRIMA"; + break; + case 6: + MA2 = new DWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"DWMA"; + break; + case 7: + MA2 = new FWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period); + this.Name += $"FWMA"; + break; + case 8: + MA2 = new DEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"DEMA"; + break; + case 9: + MA2 = new TEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"TEMA"; + break; + case 10: + MA2 = new ALMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"ALMA"; + break; + case 11: + MA2 = new HMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"HMA"; + break; + case 12: + MA2 = new HEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"HEMA"; + break; + case 13: + double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA2Period); + MA2 = new MAMA_Series(source: bars.Select(this.MA2DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); + this.Name += $"MAMA"; + break; + case 14: + MA2 = new KAMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"KAMA"; + break; + case 15: + MA2 = new ZLEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"ZLEMA"; + break; + default: + MA2 = new JMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"JMA"; + break; + } + this.Name += $"({MA2Period}:{TBars.SelectStr(this.MA2DataSource)}) ]"; - int maxKeep = Math.Max(Math.Max(this.MA1Period, this.MA2Period), 100); - MA1.Keep = maxKeep; - MA2.Keep = maxKeep; - trades.Keep = maxKeep; - overunder.Keep = maxKeep; + int maxKeep = Math.Max(Math.Max(this.MA1Period, this.MA2Period), 100); + MA1.Keep = maxKeep; + MA2.Keep = maxKeep; + trades.Keep = maxKeep; + overunder.Keep = maxKeep; - overunder = new(MA1, MA2); - trades = new(MA1, MA2); - } + overunder = new(MA1, MA2); + trades = new(MA1, MA2); + } - protected override void OnUpdate(UpdateArgs args) { - bool update = !(args.Reason == UpdateReason.NewBar || - args.Reason == UpdateReason.HistoricalBar); - this.bars.Add(this.Time(), this.GetPrice(PriceType.Open), - this.GetPrice(PriceType.High), - this.GetPrice(PriceType.Low), - this.GetPrice(PriceType.Close), - this.GetPrice(PriceType.Volume), update); - this.SetValue(this.MA1[^1].v, lineIndex: 0); - this.SetValue(this.MA2[^1].v, lineIndex: 1); + protected override void OnUpdate(UpdateArgs args) + { + bool update = !(args.Reason == UpdateReason.NewBar || + args.Reason == UpdateReason.HistoricalBar); + this.bars.Add(this.Time(), this.GetPrice(PriceType.Open), + this.GetPrice(PriceType.High), + this.GetPrice(PriceType.Low), + this.GetPrice(PriceType.Close), + this.GetPrice(PriceType.Volume), update); + this.SetValue(this.MA1[^1].v, lineIndex: 0); + this.SetValue(this.MA2[^1].v, lineIndex: 1); - if (trades[^1].v == 1) { - this.EndCloud(0, 1, Color.Empty); - if (LongTrades) { - this.LinesSeries[0].SetMarker(0, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.UpArrow)); - this.BeginCloud(0, 1, Color.FromArgb(127, Color.Green)); - } - if (ShortTrades) { - this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.DownArrow)); - } - } - if (trades[^1].v == -1) { - this.EndCloud(0, 1, Color.Empty); - if (ShortTrades) { - this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.UpArrow)); - this.BeginCloud(0, 1, Color.FromArgb(127, Color.Red)); - } - if (LongTrades) { - this.LinesSeries[0].SetMarker(0, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.DownArrow)); - } - } - } - public override void OnPaintChart(PaintChartEventArgs args) { - base.OnPaintChart(args); - if (this.CurrentChart == null) {return;} - Graphics graphics = args.Graphics; - var mainWindow = this.CurrentChart.MainWindow; - int leftIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left)); - int rightIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right))); - int historycount = HistoricalData.Count; - int ymax = mainWindow.ClientRectangle.Height; - int xmax = mainWindow.ClientRectangle.Width; + if (trades[^1].v == 1) + { + this.EndCloud(0, 1, Color.Empty); + if (LongTrades) + { + this.LinesSeries[0].SetMarker(0, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.UpArrow)); + this.BeginCloud(0, 1, Color.FromArgb(127, Color.Green)); + } + if (ShortTrades) + { + this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.DownArrow)); + } + } + if (trades[^1].v == -1) + { + this.EndCloud(0, 1, Color.Empty); + if (ShortTrades) + { + this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.UpArrow)); + this.BeginCloud(0, 1, Color.FromArgb(127, Color.Red)); + } + if (LongTrades) + { + this.LinesSeries[0].SetMarker(0, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.DownArrow)); + } + } + } + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + if (this.CurrentChart == null) { return; } + Graphics graphics = args.Graphics; + var mainWindow = this.CurrentChart.MainWindow; + int leftIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left)); + int rightIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right))); + int historycount = HistoricalData.Count; + int ymax = mainWindow.ClientRectangle.Height; + int xmax = mainWindow.ClientRectangle.Width; - /* + /* for (int i = leftIndex; i <= rightIndex; i++) { int xi = (int)Math.Round(mainWindow.CoordinatesConverter.GetChartX(Time(Count - 1 - i))); int width = this.CurrentChart.BarsWidth; @@ -280,5 +294,5 @@ public class MovingAverage_chart : Indicator { graphics.FillRectangle(bb, xi, ymax - height, width, height); } */ - } + } } diff --git a/Indicators/Charts/2MASlope_chart.cs b/Indicators/Charts/2MASlope_chart.cs index 65acfa9c..c031e513 100644 --- a/Indicators/Charts/2MASlope_chart.cs +++ b/Indicators/Charts/2MASlope_chart.cs @@ -4,304 +4,317 @@ using System.Linq; using TradingPlatform.BusinessLayer; namespace QuanTAlib; -public class MovingAverageSlope_chart : Indicator { - #region Parameters - [InputParameter("MA1: Type:", 0, variants: new object[] - { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, - "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] - private int MA1type = 16; +public class MovingAverageSlope_chart : Indicator +{ + #region Parameters + [InputParameter("MA1: Type:", 0, variants: new object[] + { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, + "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] + private int MA1type = 16; - [InputParameter("MA1: Smoothing period:", 1, 1, 999, 1, 1)] - private int MA1Period = 10; + [InputParameter("MA1: Smoothing period:", 1, 1, 999, 1, 1)] + private int MA1Period = 10; - [InputParameter("MA1: Data source:", 2, variants: new object[] - { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, - "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] - private int MA1DataSource = 3; + [InputParameter("MA1: Data source:", 2, variants: new object[] + { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, + "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] + private int MA1DataSource = 3; - [InputParameter("MA2: Type:", 3, variants: new object[] - { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, - "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] - private int MA2type = 6; + [InputParameter("MA2: Type:", 3, variants: new object[] + { "SMA", 0, "EMA", 1, "WMA", 2, "T3", 3, "SMMA", 4, "TRIMA", 5, "DWMA", 6, "FWMA", 7, "DEMA", 8, "TEMA", 9, + "ALMA", 10, "HMA", 11, "HEMA", 12, "MAMA", 13, "KAMA", 14, "ZLEMA", 15, "JMA", 16})] + private int MA2type = 6; - [InputParameter("MA2: Smoothing period:", 4, 1, 999, 1, 1)] - private int MA2Period = 50; + [InputParameter("MA2: Smoothing period:", 4, 1, 999, 1, 1)] + private int MA2Period = 50; - [InputParameter("MA2: Data source:", 5, variants: new object[] - { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, - "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] - private int MA2DataSource = 8; + [InputParameter("MA2: Data source:", 5, variants: new object[] + { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, + "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] + private int MA2DataSource = 8; - [InputParameter("Data required for slope calc:", 6, 2, 10, 1, 1)] - private int SlopePeriod = 3; + [InputParameter("Data required for slope calc:", 6, 2, 10, 1, 1)] + private int SlopePeriod = 3; - [InputParameter("Long trades", 7)] - private bool LongTrades = true; + [InputParameter("Long trades", 7)] + private bool LongTrades = true; - [InputParameter("Short trades", 8)] - private bool ShortTrades; + [InputParameter("Short trades", 8)] + private bool ShortTrades; - #endregion Parameters + #endregion Parameters - protected HistoricalData History; - private TBars bars; + protected HistoricalData History; + private TBars bars; - /////// - private TSeries MA1, MA2; - private SLOPE_Series sMA1, sMA2; - private CROSS_Series sig1, sig2; + /////// + private TSeries MA1, MA2; + private SLOPE_Series sMA1, sMA2; + private CROSS_Series sig1, sig2; - private bool inLong, inShort; - /////// + private bool inLong, inShort; + /////// - public MovingAverageSlope_chart() { - this.SeparateWindow = false; - this.Name = "Slopes convergence"; - this.AddLineSeries("MA1", Color.DarkSlateGray, 2, LineStyle.Solid); - this.AddLineSeries("MA2", Color.DarkSlateGray, 2, LineStyle.Solid); - } + public MovingAverageSlope_chart() + { + this.SeparateWindow = false; + this.Name = "Slopes convergence"; + this.AddLineSeries("MA1", Color.DarkSlateGray, 2, LineStyle.Solid); + this.AddLineSeries("MA2", Color.DarkSlateGray, 2, LineStyle.Solid); + } - protected override void OnInit() { - this.bars = new(); - this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); - for (int i = this.History.Count - 1; i >= 0; i--) { - var rec = this.History[i, SeekOriginHistory.Begin]; - bars.Add(rec.TimeLeft, rec[PriceType.Open], - rec[PriceType.High], rec[PriceType.Low], - rec[PriceType.Close], rec[PriceType.Volume]); - } - this.Name = "Slopes convergence: [ "; - switch (MA1type) { - case 0: - MA1 = new SMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"SMA"; - break; - case 1: - MA1 = new EMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"EMA"; - break; - case 2: - MA1 = new WMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"WMA"; - break; - case 3: - MA1 = new T3_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"T3"; - break; - case 4: - MA1 = new SMMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"SMMA"; - break; - case 5: - MA1 = new TRIMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"TRIMA"; - break; - case 6: - MA1 = new DWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"DWMA"; - break; - case 7: - MA1 = new FWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period); - this.Name += $"FWMA"; - break; - case 8: - MA1 = new DEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"DEMA"; - break; - case 9: - MA1 = new TEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"TEMA"; - break; - case 10: - MA1 = new ALMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"ALMA"; - break; - case 11: - MA1 = new HMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"HMA"; - break; - case 12: - MA1 = new HEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"HEMA"; - break; - case 13: - double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA1Period); - MA1 = new MAMA_Series(source: bars.Select(this.MA1DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); - this.Name += $"MAMA"; - break; - case 14: - MA1 = new KAMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"KAMA"; - break; - case 15: - MA1 = new ZLEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"ZLEMA"; - break; - default: - MA1 = new JMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); - this.Name += $"JMA"; - break; - } + protected override void OnInit() + { + this.bars = new(); + this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); + for (int i = this.History.Count - 1; i >= 0; i--) + { + var rec = this.History[i, SeekOriginHistory.Begin]; + bars.Add(rec.TimeLeft, rec[PriceType.Open], + rec[PriceType.High], rec[PriceType.Low], + rec[PriceType.Close], rec[PriceType.Volume]); + } + this.Name = "Slopes convergence: [ "; + switch (MA1type) + { + case 0: + MA1 = new SMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"SMA"; + break; + case 1: + MA1 = new EMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"EMA"; + break; + case 2: + MA1 = new WMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"WMA"; + break; + case 3: + MA1 = new T3_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"T3"; + break; + case 4: + MA1 = new SMMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"SMMA"; + break; + case 5: + MA1 = new TRIMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"TRIMA"; + break; + case 6: + MA1 = new DWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"DWMA"; + break; + case 7: + MA1 = new FWMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period); + this.Name += $"FWMA"; + break; + case 8: + MA1 = new DEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"DEMA"; + break; + case 9: + MA1 = new TEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"TEMA"; + break; + case 10: + MA1 = new ALMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"ALMA"; + break; + case 11: + MA1 = new HMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"HMA"; + break; + case 12: + MA1 = new HEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"HEMA"; + break; + case 13: + double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA1Period); + MA1 = new MAMA_Series(source: bars.Select(this.MA1DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); + this.Name += $"MAMA"; + break; + case 14: + MA1 = new KAMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"KAMA"; + break; + case 15: + MA1 = new ZLEMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"ZLEMA"; + break; + default: + MA1 = new JMA_Series(source: bars.Select(this.MA1DataSource), period: this.MA1Period, useNaN: false); + this.Name += $"JMA"; + break; + } - this.Name = this.Name + $" ({MA1Period}:{TBars.SelectStr(this.MA1DataSource)}) : "; + this.Name = this.Name + $" ({MA1Period}:{TBars.SelectStr(this.MA1DataSource)}) : "; - switch (MA2type) { - case 0: - MA2 = new SMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"SMA"; - break; - case 1: - MA2 = new EMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"EMA"; - break; - case 2: - MA2 = new WMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"WMA"; - break; - case 3: - MA2 = new T3_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"T3"; - break; - case 4: - MA2 = new SMMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"SMMA"; - break; - case 5: - MA2 = new TRIMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"TRIMA"; - break; - case 6: - MA2 = new DWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"DWMA"; - break; - case 7: - MA2 = new FWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period); - this.Name += $"FWMA"; - break; - case 8: - MA2 = new DEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"DEMA"; - break; - case 9: - MA2 = new TEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"TEMA"; - break; - case 10: - MA2 = new ALMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"ALMA"; - break; - case 11: - MA2 = new HMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"HMA"; - break; - case 12: - MA2 = new HEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"HEMA"; - break; - case 13: - double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA2Period); - MA2 = new MAMA_Series(source: bars.Select(this.MA2DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); - this.Name += $"MAMA"; - break; - case 14: - MA2 = new KAMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"KAMA"; - break; - case 15: - MA2 = new ZLEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"ZLEMA"; - break; - default: - MA2 = new JMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); - this.Name += $"JMA"; - break; - } - this.Name += $"({MA2Period}:{TBars.SelectStr(this.MA2DataSource)}) ]"; + switch (MA2type) + { + case 0: + MA2 = new SMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"SMA"; + break; + case 1: + MA2 = new EMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"EMA"; + break; + case 2: + MA2 = new WMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"WMA"; + break; + case 3: + MA2 = new T3_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"T3"; + break; + case 4: + MA2 = new SMMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"SMMA"; + break; + case 5: + MA2 = new TRIMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"TRIMA"; + break; + case 6: + MA2 = new DWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"DWMA"; + break; + case 7: + MA2 = new FWMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period); + this.Name += $"FWMA"; + break; + case 8: + MA2 = new DEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"DEMA"; + break; + case 9: + MA2 = new TEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"TEMA"; + break; + case 10: + MA2 = new ALMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"ALMA"; + break; + case 11: + MA2 = new HMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"HMA"; + break; + case 12: + MA2 = new HEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"HEMA"; + break; + case 13: + double factor = 1.015 * Math.Exp(-0.043 * (double)this.MA2Period); + MA2 = new MAMA_Series(source: bars.Select(this.MA2DataSource), fastlimit: factor, slowlimit: factor * 0.1, useNaN: false); + this.Name += $"MAMA"; + break; + case 14: + MA2 = new KAMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"KAMA"; + break; + case 15: + MA2 = new ZLEMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"ZLEMA"; + break; + default: + MA2 = new JMA_Series(source: bars.Select(this.MA2DataSource), period: this.MA2Period, useNaN: false); + this.Name += $"JMA"; + break; + } + this.Name += $"({MA2Period}:{TBars.SelectStr(this.MA2DataSource)}) ]"; - sMA1 = new(MA1, SlopePeriod); - sMA2 = new(MA2, SlopePeriod); - sig1 = new(sMA1, 0); - sig2 = new(sMA2, 0); + sMA1 = new(MA1, SlopePeriod); + sMA2 = new(MA2, SlopePeriod); + sig1 = new(sMA1, 0); + sig2 = new(sMA2, 0); - int maxKeep = Math.Max(Math.Max(this.MA1Period, this.MA2Period), 100); + int maxKeep = Math.Max(Math.Max(this.MA1Period, this.MA2Period), 100); - MA1.Keep = maxKeep; - MA2.Keep = maxKeep; - sMA1.Keep = maxKeep; - sMA2.Keep = maxKeep; - sig1.Keep = maxKeep; - sig2.Keep = maxKeep; - } + MA1.Keep = maxKeep; + MA2.Keep = maxKeep; + sMA1.Keep = maxKeep; + sMA2.Keep = maxKeep; + sig1.Keep = maxKeep; + sig2.Keep = maxKeep; + } - protected override void OnUpdate(UpdateArgs args) { - bool update = !(args.Reason == UpdateReason.NewBar || - args.Reason == UpdateReason.HistoricalBar); - this.bars.Add(this.Time(),this.Open(), this.High(), this.Low(), this.Close(), this.Volume(), update); - this.SetValue(this.MA1[^1].v, lineIndex: 0); - this.SetValue(this.MA2[^1].v, lineIndex: 1); + protected override void OnUpdate(UpdateArgs args) + { + bool update = !(args.Reason == UpdateReason.NewBar || + args.Reason == UpdateReason.HistoricalBar); + this.bars.Add(this.Time(), this.Open(), this.High(), this.Low(), this.Close(), this.Volume(), update); + this.SetValue(this.MA1[^1].v, lineIndex: 0); + this.SetValue(this.MA2[^1].v, lineIndex: 1); - Color s1Color= (this.sMA1[^1].v > 0)?Color.LimeGreen:Color.OrangeRed; - Color s2Color = (this.sMA2[^1].v > 0) ? Color.LimeGreen : Color.OrangeRed; + Color s1Color = (this.sMA1[^1].v > 0) ? Color.LimeGreen : Color.OrangeRed; + Color s2Color = (this.sMA2[^1].v > 0) ? Color.LimeGreen : Color.OrangeRed; - this.LinesSeries[0].SetMarker(0,s1Color); - this.LinesSeries[1].SetMarker(0,s2Color); + this.LinesSeries[0].SetMarker(0, s1Color); + this.LinesSeries[1].SetMarker(0, s2Color); - if (sig1[^1].v > 0 || sig2[^1].v > 0) { - if (sMA1[^1].v >= 0 && sMA2[^1].v >= 0 && LongTrades) - { - inLong = true; - this.BeginCloud(0, 1, Color.FromArgb(127, Color.DarkGreen)); - this.LinesSeries[(this.MA1[^1].v < this.MA2[^1].v)? 0 : 1 ].SetMarker(0, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.UpArrow)); - } - else { - this.EndCloud(0, 1, Color.Empty); - if (inShort && this.Count > 1) - { - this.LinesSeries[(this.MA1[^1].v < this.MA2[^1].v) ? 1 : 0].SetMarker(1, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.DownArrow)); - inShort = false; - } - } - } + if (sig1[^1].v > 0 || sig2[^1].v > 0) + { + if (sMA1[^1].v >= 0 && sMA2[^1].v >= 0 && LongTrades) + { + inLong = true; + this.BeginCloud(0, 1, Color.FromArgb(127, Color.DarkGreen)); + this.LinesSeries[(this.MA1[^1].v < this.MA2[^1].v) ? 0 : 1].SetMarker(0, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.UpArrow)); + } + else + { + this.EndCloud(0, 1, Color.Empty); + if (inShort && this.Count > 1) + { + this.LinesSeries[(this.MA1[^1].v < this.MA2[^1].v) ? 1 : 0].SetMarker(1, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.DownArrow)); + inShort = false; + } + } + } - if (sig1[^1].v < 0 || sig2[^1].v < 0) { - if (sMA1[^1].v <= 0 && sMA2[^1].v <= 0 && ShortTrades) - { - inShort = true; - this.BeginCloud(0, 1, Color.FromArgb(100, Color.Red)); - this.LinesSeries[(this.MA1[^1].v > this.MA2[^1].v) ? 0 : 1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.UpArrow)); - } - else { - this.EndCloud(0, 1, Color.Empty); - if (inLong && this.Count > 1) { - LinesSeries[(this.MA1[^1].v > this.MA2[^1].v)?1:0].SetMarker(1, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.DownArrow)); - inLong = false; - } - } - } - } - public override void OnPaintChart(PaintChartEventArgs args) { - base.OnPaintChart(args); - if (this.CurrentChart == null) {return;} - Graphics graphics = args.Graphics; - var mainWindow = this.CurrentChart.MainWindow; - int leftIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left)); - int rightIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right))); -/* - int historycount = HistoricalData.Count; - int ymax = mainWindow.ClientRectangle.Height; + if (sig1[^1].v < 0 || sig2[^1].v < 0) + { + if (sMA1[^1].v <= 0 && sMA2[^1].v <= 0 && ShortTrades) + { + inShort = true; + this.BeginCloud(0, 1, Color.FromArgb(100, Color.Red)); + this.LinesSeries[(this.MA1[^1].v > this.MA2[^1].v) ? 0 : 1].SetMarker(0, new IndicatorLineMarker(Color.OrangeRed, upperIcon: IndicatorLineMarkerIconType.UpArrow)); + } + else + { + this.EndCloud(0, 1, Color.Empty); + if (inLong && this.Count > 1) + { + LinesSeries[(this.MA1[^1].v > this.MA2[^1].v) ? 1 : 0].SetMarker(1, new IndicatorLineMarker(Color.LimeGreen, bottomIcon: IndicatorLineMarkerIconType.DownArrow)); + inLong = false; + } + } + } + } + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + if (this.CurrentChart == null) { return; } + Graphics graphics = args.Graphics; + var mainWindow = this.CurrentChart.MainWindow; + int leftIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left)); + int rightIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right))); + /* + int historycount = HistoricalData.Count; + int ymax = mainWindow.ClientRectangle.Height; - for (int i = leftIndex; i <= rightIndex; i++) { - int xi = (int)Math.Round(mainWindow.CoordinatesConverter.GetChartX(Time(Count - 1 - i))); - int width = this.CurrentChart.BarsWidth; - int height = (int)((equity[i+historycount].v) *proportion); + for (int i = leftIndex; i <= rightIndex; i++) { + int xi = (int)Math.Round(mainWindow.CoordinatesConverter.GetChartX(Time(Count - 1 - i))); + int width = this.CurrentChart.BarsWidth; + int height = (int)((equity[i+historycount].v) *proportion); - Brush bb = Brushes.DarkSlateGray; - bb = (overunder[i+historycount].v>0 && LongTrades)? Brushes.Green : bb; - bb = (overunder[i + historycount].v < 0 && ShortTrades) ? Brushes.Red : bb; + Brush bb = Brushes.DarkSlateGray; + bb = (overunder[i+historycount].v>0 && LongTrades)? Brushes.Green : bb; + bb = (overunder[i + historycount].v < 0 && ShortTrades) ? Brushes.Red : bb; - graphics.FillRectangle(bb, xi, ymax - height, width, height); - } -*/ - } + graphics.FillRectangle(bb, xi, ymax - height, width, height); + } + */ + } } diff --git a/Indicators/Charts/JMA_chart.cs b/Indicators/Charts/JMA_chart.cs index 449f6965..d35174cf 100644 --- a/Indicators/Charts/JMA_chart.cs +++ b/Indicators/Charts/JMA_chart.cs @@ -6,92 +6,99 @@ using TradingPlatform.BusinessLayer; using TradingPlatform.BusinessLayer.Chart; namespace QuanTAlib; -public class JMA_chart : Indicator { - #region Parameters +public class JMA_chart : Indicator +{ + #region Parameters - [InputParameter("Data source", 0, variants: new object[] - { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, - "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] - private int DataSource = 3; + [InputParameter("Data source", 0, variants: new object[] + { "Open", 0, "High", 1, "Low", 2, "Close", 3, "HL2", 4, "OC2", 5, + "OHL3", 6, "HLC3", 7, "OHLC4", 8, "Weighted (HLCC4)", 9 })] + private int DataSource = 3; - [InputParameter("Smoothing period", 1, 1, 999, 1, 1)] - private int Period = 9; + [InputParameter("Smoothing period", 1, 1, 999, 1, 1)] + private int Period = 9; - [InputParameter("Volatility short", 2, 3, 50, 1, 1)] - private int Vshort = 10; + [InputParameter("Volatility short", 2, 3, 50, 1, 1)] + private int Vshort = 10; - [InputParameter("Volatility long", 3, 20, 500, 1, 1)] - private int Vlong = 65; + [InputParameter("Volatility long", 3, 20, 500, 1, 1)] + private int Vlong = 65; - [InputParameter("Phase", 4, -100, 100, 1, 2)] - private double Jphase; + [InputParameter("Phase", 4, -100, 100, 1, 2)] + private double Jphase; - #endregion Parameters + #endregion Parameters - /////// - private JMA_Series indicator; - /////// + /////// + private JMA_Series indicator; + /////// - protected TBars bars; - protected IChartWindow mainWindow; - protected Graphics graphics; - protected int firstOnScreenBarIndex, lastOnScreenBarIndex; - protected HistoricalData History; - protected int HistPeriod; - public JMA_chart() { - Name = "JMA - Jurik Moving Avg"; - Description = "Jurik Moving Average description"; - AddLineSeries(lineName: "JMA", lineColor: Color.Yellow, lineWidth: 3,lineStyle: LineStyle.Solid); - SeparateWindow = false; - HistPeriod = Period; - } + protected TBars bars; + protected IChartWindow mainWindow; + protected Graphics graphics; + protected int firstOnScreenBarIndex, lastOnScreenBarIndex; + protected HistoricalData History; + protected int HistPeriod; + public JMA_chart() + { + Name = "JMA - Jurik Moving Avg"; + Description = "Jurik Moving Average description"; + AddLineSeries(lineName: "JMA", lineColor: Color.Yellow, lineWidth: 3, lineStyle: LineStyle.Solid); + SeparateWindow = false; + HistPeriod = Period; + } - protected override void OnInit() { - base.OnInit(); - bars = new(); - var dur1 = this.HistoricalData.FromTime; - var dur = this.HistoricalData.Period.Duration.TotalSeconds * (HistPeriod * 4); //seconds of two periods + protected override void OnInit() + { + base.OnInit(); + bars = new(); + var dur1 = this.HistoricalData.FromTime; + var dur = this.HistoricalData.Period.Duration.TotalSeconds * (HistPeriod * 4); //seconds of two periods - this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); + this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); - for (int i = this.History.Count - 1; i >= 0; i--) { + for (int i = this.History.Count - 1; i >= 0; i--) + { - var rec = this.History[i, SeekOriginHistory.Begin]; + var rec = this.History[i, SeekOriginHistory.Begin]; - bars.Add(rec.TimeLeft, rec[PriceType.Open], - rec[PriceType.High], rec[PriceType.Low], - rec[PriceType.Close], rec[PriceType.Volume]); - } + bars.Add(rec.TimeLeft, rec[PriceType.Open], + rec[PriceType.High], rec[PriceType.Low], + rec[PriceType.Close], rec[PriceType.Volume]); + } - indicator = new(source: bars.Select(DataSource), period: Period, phase: Jphase, vshort: Vshort, vlong: Vlong, useNaN: true); - indicator.Keep = Math.Max(Period, 100); - } + indicator = new(source: bars.Select(DataSource), period: Period, phase: Jphase, vshort: Vshort, vlong: Vlong, useNaN: true); + indicator.Keep = Math.Max(Period, 100); + } - protected override void OnUpdate(UpdateArgs args) { - base.OnUpdate(args); - bars.Add(Time(), GetPrice(PriceType.Open), - GetPrice(PriceType.High), - GetPrice(PriceType.Low), - GetPrice(PriceType.Close), - GetPrice(PriceType.Volume), - update: !(args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar)); - - this.SetValue(indicator[^1].v, lineIndex: 0); - } - public override void OnPaintChart(PaintChartEventArgs args) { - base.OnPaintChart(args); - if (this.CurrentChart == null) { - return; - } + protected override void OnUpdate(UpdateArgs args) + { + base.OnUpdate(args); + bars.Add(Time(), GetPrice(PriceType.Open), + GetPrice(PriceType.High), + GetPrice(PriceType.Low), + GetPrice(PriceType.Close), + GetPrice(PriceType.Volume), + update: !(args.Reason == UpdateReason.NewBar || args.Reason == UpdateReason.HistoricalBar)); - graphics = args.Graphics; - mainWindow = this.CurrentChart.MainWindow; + this.SetValue(indicator[^1].v, lineIndex: 0); + } + public override void OnPaintChart(PaintChartEventArgs args) + { + base.OnPaintChart(args); + if (this.CurrentChart == null) + { + return; + } - DateTime leftTime = mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left); - DateTime rightTime = mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right); - firstOnScreenBarIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(leftTime); - lastOnScreenBarIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(rightTime)); - } + graphics = args.Graphics; + mainWindow = this.CurrentChart.MainWindow; + + DateTime leftTime = mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Left); + DateTime rightTime = mainWindow.CoordinatesConverter.GetTime(mainWindow.ClientRectangle.Right); + firstOnScreenBarIndex = (int)mainWindow.CoordinatesConverter.GetBarIndex(leftTime); + lastOnScreenBarIndex = (int)Math.Ceiling(mainWindow.CoordinatesConverter.GetBarIndex(rightTime)); + } } diff --git a/Indicators/Charts/TrailingStop.cs b/Indicators/Charts/TrailingStop.cs index 50a1d4f7..8ee9f476 100644 --- a/Indicators/Charts/TrailingStop.cs +++ b/Indicators/Charts/TrailingStop.cs @@ -5,91 +5,98 @@ using System.Linq; using TradingPlatform.BusinessLayer; namespace QuanTAlib; -public class TrailingStop_chart : Indicator { - #region Parameters +public class TrailingStop_chart : Indicator +{ + #region Parameters - [InputParameter("Period", 0, 1, 100, 1, 1)] - protected int _period = 30; - - [InputParameter("Factor", 1, 1, 100, 0.1, 1)] - protected double _factor = 10; + [InputParameter("Period", 0, 1, 100, 1, 1)] + protected int _period = 30; - [InputParameter("Long TS", 2)] - private bool _LongTS = true; + [InputParameter("Factor", 1, 1, 100, 0.1, 1)] + protected double _factor = 10; - [InputParameter("Short TS", 3)] - private bool _ShortTS = true; + [InputParameter("Long TS", 2)] + private bool _LongTS = true; - #endregion Parameters + [InputParameter("Short TS", 3)] + private bool _ShortTS = true; - /////// - private HistoricalData History; - private TBars bars; - private ATR_Series _atr; - private double _tslineL, _ratchetL, _tslineS, _ratchetS; + #endregion Parameters - /////// + /////// + private HistoricalData History; + private TBars bars; + private ATR_Series _atr; + private double _tslineL, _ratchetL, _tslineS, _ratchetS; - public TrailingStop_chart() { - Name = $"ATR Trailing Stop"; - AddLineSeries(lineName: "TrailingATR Long", lineColor: Color.Yellow, lineWidth: 1,lineStyle: LineStyle.Dot); - AddLineSeries(lineName: "Ratchet Long", lineColor: Color.Yellow, lineWidth: 3, lineStyle: LineStyle.Solid); + /////// - AddLineSeries(lineName: "TrailingATR Short", lineColor: Color.Yellow, lineWidth: 1, lineStyle: LineStyle.Dot); - AddLineSeries(lineName: "Ratchet Short", lineColor: Color.Yellow, lineWidth: 3, lineStyle: LineStyle.Solid); - - SeparateWindow = false; - } + public TrailingStop_chart() + { + Name = $"ATR Trailing Stop"; + AddLineSeries(lineName: "TrailingATR Long", lineColor: Color.Yellow, lineWidth: 1, lineStyle: LineStyle.Dot); + AddLineSeries(lineName: "Ratchet Long", lineColor: Color.Yellow, lineWidth: 3, lineStyle: LineStyle.Solid); + + AddLineSeries(lineName: "TrailingATR Short", lineColor: Color.Yellow, lineWidth: 1, lineStyle: LineStyle.Dot); + AddLineSeries(lineName: "Ratchet Short", lineColor: Color.Yellow, lineWidth: 3, lineStyle: LineStyle.Solid); + + SeparateWindow = false; + } - protected override void OnInit() { - this.Name = $"Trailing Stop (ATR:{_period}, Mult:{_factor:f2})"; - this.bars = new(); + protected override void OnInit() + { + this.Name = $"Trailing Stop (ATR:{_period}, Mult:{_factor:f2})"; + this.bars = new(); - this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); - for (int i = this.History.Count - 1; i >= 0; i--) { - var rec = this.History[i, SeekOriginHistory.Begin]; - bars.Add(rec.TimeLeft, rec[PriceType.Open], - rec[PriceType.High], rec[PriceType.Low], - rec[PriceType.Close], rec[PriceType.Volume]); - } - _atr = new(source: bars, _period, useNaN: true); - _ratchetL = Double.NegativeInfinity; - _ratchetS = Double.PositiveInfinity; + this.History = this.Symbol.GetHistory(period: this.HistoricalData.Period, fromTime: HistoricalData.FromTime); + for (int i = this.History.Count - 1; i >= 0; i--) + { + var rec = this.History[i, SeekOriginHistory.Begin]; + bars.Add(rec.TimeLeft, rec[PriceType.Open], + rec[PriceType.High], rec[PriceType.Low], + rec[PriceType.Close], rec[PriceType.Volume]); + } + _atr = new(source: bars, _period, useNaN: true); + _ratchetL = Double.NegativeInfinity; + _ratchetS = Double.PositiveInfinity; - this.LinesSeries[0].Visible = _LongTS; - this.LinesSeries[1].Visible = _LongTS; - this.LinesSeries[2].Visible = _ShortTS; - this.LinesSeries[3].Visible = _ShortTS; - } + this.LinesSeries[0].Visible = _LongTS; + this.LinesSeries[1].Visible = _LongTS; + this.LinesSeries[2].Visible = _ShortTS; + this.LinesSeries[3].Visible = _ShortTS; + } - protected override void OnUpdate(UpdateArgs args) { - bool update = !(args.Reason == UpdateReason.NewBar || - args.Reason == UpdateReason.HistoricalBar); - this.bars.Add(this.Time(), this.GetPrice(PriceType.Open), - this.GetPrice(PriceType.High), - this.GetPrice(PriceType.Low), - this.GetPrice(PriceType.Close), - this.GetPrice(PriceType.Volume), update); - - _tslineL = bars.High[^1].v - (_factor * _atr[^1].v); - _ratchetL = Math.Max(_tslineL,_ratchetL); - if (_ratchetL > bars.Low[^1].v) { - this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.Yellow, bottomIcon: IndicatorLineMarkerIconType.DownArrow)); - _ratchetL = _tslineL; - } + protected override void OnUpdate(UpdateArgs args) + { + bool update = !(args.Reason == UpdateReason.NewBar || + args.Reason == UpdateReason.HistoricalBar); + this.bars.Add(this.Time(), this.GetPrice(PriceType.Open), + this.GetPrice(PriceType.High), + this.GetPrice(PriceType.Low), + this.GetPrice(PriceType.Close), + this.GetPrice(PriceType.Volume), update); - _tslineS = bars.High[^1].v + (_factor * _atr[^1].v); - _ratchetS = Math.Min(_tslineS, _ratchetS); - if (_ratchetS < bars.High[^1].v) { - this.LinesSeries[3].SetMarker(0, new IndicatorLineMarker(Color.Yellow, upperIcon: IndicatorLineMarkerIconType.UpArrow)); - _ratchetS = _tslineS; - } + _tslineL = bars.High[^1].v - (_factor * _atr[^1].v); + _ratchetL = Math.Max(_tslineL, _ratchetL); + if (_ratchetL > bars.Low[^1].v) + { + this.LinesSeries[1].SetMarker(0, new IndicatorLineMarker(Color.Yellow, bottomIcon: IndicatorLineMarkerIconType.DownArrow)); + _ratchetL = _tslineL; + } - this.SetValue(_tslineL, lineIndex: 0); - this.SetValue(_ratchetL, lineIndex: 1); - this.SetValue(_tslineS, lineIndex: 2); - this.SetValue(_ratchetS, lineIndex: 3); - } + _tslineS = bars.High[^1].v + (_factor * _atr[^1].v); + _ratchetS = Math.Min(_tslineS, _ratchetS); + if (_ratchetS < bars.High[^1].v) + { + this.LinesSeries[3].SetMarker(0, new IndicatorLineMarker(Color.Yellow, upperIcon: IndicatorLineMarkerIconType.UpArrow)); + _ratchetS = _tslineS; + } + + this.SetValue(_tslineL, lineIndex: 0); + this.SetValue(_ratchetL, lineIndex: 1); + this.SetValue(_tslineS, lineIndex: 2); + this.SetValue(_ratchetS, lineIndex: 3); + } } diff --git a/Tests/Basic tests/Indicators.cs b/Tests/Basic tests/Indicators.cs index a9a9175e..0bac1c17 100644 --- a/Tests/Basic tests/Indicators.cs +++ b/Tests/Basic tests/Indicators.cs @@ -6,8 +6,8 @@ namespace Basics; #nullable disable public class Indicators { - private static Type[] maSeriesTypes = new Type[] - { + private static Type[] maSeriesTypes = new Type[] + { typeof(SMA_Series), typeof(EMA_Series), typeof(DEMA_Series), @@ -28,128 +28,129 @@ public class Indicators typeof(TRIMA_Series), typeof(MAMA_Series), typeof(HWMA_Series), -}; + }; - [Theory] - [MemberData(nameof(MASeriesData))] - public void Name_exists(Type classType) - { - TSeries data = new("Data") {1,2,3}; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - Assert.NotEmpty(MA_Series.Name); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Series_Length(Type classType) - { - GBM_Feed feed = new(1000); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - Assert.Equal(1000, MA_Series.Count); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Return_data(Type classType) - { - TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - var result = MA_Series.Add(20); - Assert.Equal(result.v, MA_Series.Last.v); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Update(Type classType) - { - TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - var pre_update = MA_Series.Last.v; - - double pre_data = data.Last.v; - data.Add(20, true); - data.Add(pre_data, true); - - Assert.Equal(pre_update, MA_Series.Last.v); - Assert.Equal(data.Count, MA_Series.Count); -} - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Period_zero(Type classType) - { - GBM_Feed feed = new(100); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 0, false) as TSeries; - Assert.Equal(data.Count, MA_Series.Count); - Assert.False(double.IsNaN(MA_Series.Last.v)); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Reset(Type classType) - { - GBM_Feed feed = new(10); - TSeries data = feed.OHLC4; - var MA_Series = Activator.CreateInstance(classType, data, 10, false) as TSeries; - MA_Series.Reset(); - data.Add(0); - Assert.Equal(data.Last.v, MA_Series.Last.v); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Period_one(Type classType) - { - GBM_Feed feed = new(100); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 1, false) as TSeries; - Assert.InRange(MA_Series.Last.v - data.Last.v, -10e-6, 10e-6); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void NaN_test(Type classType) - { - GBM_Feed feed = new(100); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; - Assert.True(double.IsNaN(MA_Series[0].v)); - Assert.True(double.IsNaN(MA_Series[8].v)); - Assert.False(double.IsNaN(MA_Series[9].v)); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Edge_numbers(Type classType) - { - TSeries data = new() { double.Epsilon, double.PositiveInfinity, double.MaxValue, double.NegativeInfinity }; - var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; - Assert.Equal(4, MA_Series.Count); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void handling_NaN(Type classType) { - TSeries data = new("Name") { 1, 2, 3, 4, 5, 6, double.NaN, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; -var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; - Assert.False(double.IsNaN(MA_Series.Last.v)); - } - -public static IEnumerable MASeriesData() - { - foreach (var type in maSeriesTypes) + [Theory] + [MemberData(nameof(MASeriesData))] + public void Name_exists(Type classType) { - yield return new object[] { type }; + TSeries data = new("Data") { 1, 2, 3 }; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + Assert.NotEmpty(MA_Series.Name); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Series_Length(Type classType) + { + GBM_Feed feed = new(1000); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + Assert.Equal(1000, MA_Series.Count); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Return_data(Type classType) + { + TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + var result = MA_Series.Add(20); + Assert.Equal(result.v, MA_Series.Last.v); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Update(Type classType) + { + TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + var pre_update = MA_Series.Last.v; + + double pre_data = data.Last.v; + data.Add(20, true); + data.Add(pre_data, true); + + Assert.Equal(pre_update, MA_Series.Last.v); + Assert.Equal(data.Count, MA_Series.Count); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Period_zero(Type classType) + { + GBM_Feed feed = new(100); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 0, false) as TSeries; + Assert.Equal(data.Count, MA_Series.Count); + Assert.False(double.IsNaN(MA_Series.Last.v)); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Reset(Type classType) + { + GBM_Feed feed = new(10); + TSeries data = feed.OHLC4; + var MA_Series = Activator.CreateInstance(classType, data, 10, false) as TSeries; + MA_Series.Reset(); + data.Add(0); + Assert.Equal(data.Last.v, MA_Series.Last.v); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Period_one(Type classType) + { + GBM_Feed feed = new(100); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 1, false) as TSeries; + Assert.InRange(MA_Series.Last.v - data.Last.v, -10e-6, 10e-6); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void NaN_test(Type classType) + { + GBM_Feed feed = new(100); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; + Assert.True(double.IsNaN(MA_Series[0].v)); + Assert.True(double.IsNaN(MA_Series[8].v)); + Assert.False(double.IsNaN(MA_Series[9].v)); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Edge_numbers(Type classType) + { + TSeries data = new() { double.Epsilon, double.PositiveInfinity, double.MaxValue, double.NegativeInfinity }; + var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; + Assert.Equal(4, MA_Series.Count); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void handling_NaN(Type classType) + { + TSeries data = new("Name") { 1, 2, 3, 4, 5, 6, double.NaN, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; + var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; + Assert.False(double.IsNaN(MA_Series.Last.v)); + } + + public static IEnumerable MASeriesData() + { + foreach (var type in maSeriesTypes) + { + yield return new object[] { type }; + } } - } } #nullable restore \ No newline at end of file diff --git a/Tests/Basic tests/Oscillators.cs b/Tests/Basic tests/Oscillators.cs index 8ca89557..61911d66 100644 --- a/Tests/Basic tests/Oscillators.cs +++ b/Tests/Basic tests/Oscillators.cs @@ -7,8 +7,8 @@ namespace Basics; #nullable disable public class Oscillators { - private static Type[] maSeriesTypes = new[] - { + private static Type[] maSeriesTypes = new[] + { typeof(BIAS_Series), typeof(MAX_Series), typeof(MIN_Series), @@ -20,7 +20,7 @@ public class Oscillators typeof(MAD_Series), typeof(MAPE_Series), typeof(MAE_Series), - typeof(MSE_Series), + typeof(MSE_Series), typeof(SDEV_Series), typeof(SMAPE_Series), typeof(WMAPE_Series), @@ -35,126 +35,127 @@ public class Oscillators typeof(BBANDS_Series), }; - [Theory] - [MemberData(nameof(MASeriesData))] - public void Name_exists(Type classType) - { - TSeries data = new("Data") {1,2,3}; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - Assert.NotEmpty(MA_Series.Name); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Series_Length(Type classType) - { - GBM_Feed feed = new(1000); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - Assert.Equal(1000, MA_Series.Count); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Return_data(Type classType) - { - TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - var result = MA_Series.Add(20); - Assert.Equal(result.v, MA_Series.Last.v); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Update(Type classType) - { - TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; - - var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; - var pre_update = MA_Series.Last.v; - - double pre_data = data.Last.v; - data.Add(20, true); - data.Add(pre_data, true); - - Assert.Equal(pre_update, MA_Series.Last.v); - Assert.Equal(data.Count, MA_Series.Count); -} - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Period_zero(Type classType) - { - GBM_Feed feed = new(100); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 0, false) as TSeries; - Assert.Equal(data.Count, MA_Series.Count); - Assert.False(double.IsNaN(MA_Series.Last.v)); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Reset(Type classType) - { - GBM_Feed feed = new(10); - TSeries data = feed.OHLC4; - var MA_Series = Activator.CreateInstance(classType, data, 10, false) as TSeries; - MA_Series.Reset(); - data.Add(1); - Assert.False(double.IsNaN(MA_Series.Last.v)); -} - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Period_one(Type classType) - { - GBM_Feed feed = new(100); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 1, false) as TSeries; - Assert.False(double.IsNaN(MA_Series[^1].v)); -} - - [Theory] - [MemberData(nameof(MASeriesData))] - public void NaN_test(Type classType) - { - GBM_Feed feed = new(100); - TSeries data = feed.OHLC4; - - var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; - Assert.True(double.IsNaN(MA_Series[0].v)); - Assert.True(double.IsNaN(MA_Series[8].v)); - Assert.False(double.IsNaN(MA_Series[9].v)); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Edge_numbers(Type classType) - { - TSeries data = new() { double.Epsilon, double.PositiveInfinity, double.MaxValue, double.NegativeInfinity }; - var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; - Assert.Equal(4, MA_Series.Count); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void handling_NaN(Type classType) { - TSeries data = new("Name") { 1, 2, 3, 4, 5, 6, double.NaN, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; -var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; - Assert.False(double.IsNaN(MA_Series.Last.v)); - } - -public static IEnumerable MASeriesData() - { - foreach (var type in maSeriesTypes) + [Theory] + [MemberData(nameof(MASeriesData))] + public void Name_exists(Type classType) { - yield return new object[] { type }; + TSeries data = new("Data") { 1, 2, 3 }; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + Assert.NotEmpty(MA_Series.Name); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Series_Length(Type classType) + { + GBM_Feed feed = new(1000); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + Assert.Equal(1000, MA_Series.Count); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Return_data(Type classType) + { + TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + var result = MA_Series.Add(20); + Assert.Equal(result.v, MA_Series.Last.v); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Update(Type classType) + { + TSeries data = new() { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + + var MA_Series = Activator.CreateInstance(classType, data, 5, false) as TSeries; + var pre_update = MA_Series.Last.v; + + double pre_data = data.Last.v; + data.Add(20, true); + data.Add(pre_data, true); + + Assert.Equal(pre_update, MA_Series.Last.v); + Assert.Equal(data.Count, MA_Series.Count); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Period_zero(Type classType) + { + GBM_Feed feed = new(100); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 0, false) as TSeries; + Assert.Equal(data.Count, MA_Series.Count); + Assert.False(double.IsNaN(MA_Series.Last.v)); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Reset(Type classType) + { + GBM_Feed feed = new(10); + TSeries data = feed.OHLC4; + var MA_Series = Activator.CreateInstance(classType, data, 10, false) as TSeries; + MA_Series.Reset(); + data.Add(1); + Assert.False(double.IsNaN(MA_Series.Last.v)); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Period_one(Type classType) + { + GBM_Feed feed = new(100); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 1, false) as TSeries; + Assert.False(double.IsNaN(MA_Series[^1].v)); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void NaN_test(Type classType) + { + GBM_Feed feed = new(100); + TSeries data = feed.OHLC4; + + var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; + Assert.True(double.IsNaN(MA_Series[0].v)); + Assert.True(double.IsNaN(MA_Series[8].v)); + Assert.False(double.IsNaN(MA_Series[9].v)); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Edge_numbers(Type classType) + { + TSeries data = new() { double.Epsilon, double.PositiveInfinity, double.MaxValue, double.NegativeInfinity }; + var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; + Assert.Equal(4, MA_Series.Count); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void handling_NaN(Type classType) + { + TSeries data = new("Name") { 1, 2, 3, 4, 5, 6, double.NaN, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; + var MA_Series = Activator.CreateInstance(classType, data, 10, true) as TSeries; + Assert.False(double.IsNaN(MA_Series.Last.v)); + } + + public static IEnumerable MASeriesData() + { + foreach (var type in maSeriesTypes) + { + yield return new object[] { type }; + } } - } } #nullable restore \ No newline at end of file diff --git a/Tests/Basic tests/TBars_input.cs b/Tests/Basic tests/TBars_input.cs index 0b984821..39850ef6 100644 --- a/Tests/Basic tests/TBars_input.cs +++ b/Tests/Basic tests/TBars_input.cs @@ -7,8 +7,8 @@ namespace Basics; #nullable disable public class TBars { - private static Type[] maSeriesTypes = new Type[] - { + private static Type[] maSeriesTypes = new Type[] + { typeof(ATR_Series), typeof(ATRP_Series), typeof(TR_Series), @@ -17,80 +17,81 @@ public class TBars typeof(OBV_Series), typeof(ADOSC_Series), typeof(MIDPRICE_Series), -}; + }; - [Theory] - [MemberData(nameof(MASeriesData))] - public void Name_exists(Type classType) - { - GBM_Feed data = new(10); - - var MA_Series = Activator.CreateInstance(classType, data) as TSeries; - Assert.NotEmpty(MA_Series.Name); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Series_Length(Type classType) - { - GBM_Feed data = new(1000); - - var MA_Series = Activator.CreateInstance(classType, data) as TSeries; - Assert.Equal(1000, MA_Series.Count); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Return_data(Type classType) - { - GBM_Feed data = new(10); - var MA_Series = Activator.CreateInstance(classType, data) as TSeries; - var result = MA_Series.Add((DateTime.Today, 1,2,3,4,5)); - Assert.Equal(result.v, MA_Series.Last.v); - } - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Update(Type classType) - { - GBM_Feed data = new(10); - var MA_Series = Activator.CreateInstance(classType, data) as TSeries; - var pre_update = MA_Series.Last; - - var pre_data = data.Last; - data.Add((DateTime.Today, 1, 2, 3, 4, 5), true); - data.Add(pre_data, true); - - Assert.Equal(pre_update.v, MA_Series.Last.v); - Assert.Equal(data.Count, MA_Series.Count); -} - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Reset(Type classType) - { - GBM_Feed data = new(10); - var MA_Series = Activator.CreateInstance(classType, data) as TSeries; - MA_Series.Reset(); - data.Add(); - Assert.False(double.IsNaN(MA_Series.Last.v)); -} - - [Theory] - [MemberData(nameof(MASeriesData))] - public void Period_default(Type classType) { - GBM_Feed data = new(100); - - var MA_Series = Activator.CreateInstance(classType, data) as TSeries; - Assert.False(double.IsNaN(MA_Series.Last.v)); - } - - public static IEnumerable MASeriesData() - { - foreach (var type in maSeriesTypes) + [Theory] + [MemberData(nameof(MASeriesData))] + public void Name_exists(Type classType) { - yield return new object[] { type }; + GBM_Feed data = new(10); + + var MA_Series = Activator.CreateInstance(classType, data) as TSeries; + Assert.NotEmpty(MA_Series.Name); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Series_Length(Type classType) + { + GBM_Feed data = new(1000); + + var MA_Series = Activator.CreateInstance(classType, data) as TSeries; + Assert.Equal(1000, MA_Series.Count); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Return_data(Type classType) + { + GBM_Feed data = new(10); + var MA_Series = Activator.CreateInstance(classType, data) as TSeries; + var result = MA_Series.Add((DateTime.Today, 1, 2, 3, 4, 5)); + Assert.Equal(result.v, MA_Series.Last.v); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Update(Type classType) + { + GBM_Feed data = new(10); + var MA_Series = Activator.CreateInstance(classType, data) as TSeries; + var pre_update = MA_Series.Last; + + var pre_data = data.Last; + data.Add((DateTime.Today, 1, 2, 3, 4, 5), true); + data.Add(pre_data, true); + + Assert.Equal(pre_update.v, MA_Series.Last.v); + Assert.Equal(data.Count, MA_Series.Count); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Reset(Type classType) + { + GBM_Feed data = new(10); + var MA_Series = Activator.CreateInstance(classType, data) as TSeries; + MA_Series.Reset(); + data.Add(); + Assert.False(double.IsNaN(MA_Series.Last.v)); + } + + [Theory] + [MemberData(nameof(MASeriesData))] + public void Period_default(Type classType) + { + GBM_Feed data = new(100); + + var MA_Series = Activator.CreateInstance(classType, data) as TSeries; + Assert.False(double.IsNaN(MA_Series.Last.v)); + } + + public static IEnumerable MASeriesData() + { + foreach (var type in maSeriesTypes) + { + yield return new object[] { type }; + } } - } } #nullable restore \ No newline at end of file diff --git a/Tests/Pairs/DIV_Test.cs b/Tests/Pairs/DIV_Test.cs index d3e57573..824bd553 100644 --- a/Tests/Pairs/DIV_Test.cs +++ b/Tests/Pairs/DIV_Test.cs @@ -5,60 +5,60 @@ using QuanTAlib; namespace Pairs; public class DIV_Test { - [Fact] - public void DIVSeriesSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 15 }; - TSeries b = new() { 5, 4, 3, 2, 1, 3 }; - DIV_Series c = new(a, b); - Assert.Equal(5, c.Last().v); - } + [Fact] + public void DIVSeriesSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 15 }; + TSeries b = new() { 5, 4, 3, 2, 1, 3 }; + DIV_Series c = new(a, b); + Assert.Equal(5, c.Last().v); + } - [Fact] - public void DIVSeriesDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 15.0 }; - DIV_Series c = new(a, 0); - Assert.Equal(double.PositiveInfinity, c.Last().v); - } + [Fact] + public void DIVSeriesDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 15.0 }; + DIV_Series c = new(a, 0); + Assert.Equal(double.PositiveInfinity, c.Last().v); + } - [Fact] - public void DIVDoubleSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 3.0 }; - DIV_Series c = new(12.0, a); - Assert.Equal(4.0, c.Last().v); - } + [Fact] + public void DIVDoubleSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 3.0 }; + DIV_Series c = new(12.0, a); + Assert.Equal(4.0, c.Last().v); + } - [Fact] - public void DIVEventing_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 0 }; - DIV_Series c = new(a, b); - a.Add(12.0); - b.Add(2); - Assert.Equal(6.0, c.Last().v); - } + [Fact] + public void DIVEventing_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 0 }; + DIV_Series c = new(a, b); + a.Add(12.0); + b.Add(2); + Assert.Equal(6.0, c.Last().v); + } - [Fact] - public void DIVUpdatewDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 15 }; - double b = 2; - DIV_Series c = new(a, b); - a.Add(10, true); - Assert.Equal(5, c.Last().v); - } + [Fact] + public void DIVUpdatewDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 15 }; + double b = 2; + DIV_Series c = new(a, b); + a.Add(10, true); + Assert.Equal(5, c.Last().v); + } - [Fact] - public void DIVUpdating_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 1 }; - DIV_Series c = new(a, b); - a.Add(10, true); - b.Add(2, true); - Assert.Equal(5, c.Last().v); - } + [Fact] + public void DIVUpdating_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 1 }; + DIV_Series c = new(a, b); + a.Add(10, true); + b.Add(2, true); + Assert.Equal(5, c.Last().v); + } } diff --git a/Tests/Pairs/MUL_Test.cs b/Tests/Pairs/MUL_Test.cs index 22a343e5..583422b8 100644 --- a/Tests/Pairs/MUL_Test.cs +++ b/Tests/Pairs/MUL_Test.cs @@ -5,60 +5,60 @@ using QuanTAlib; namespace Pairs; public class MUL_Test { - [Fact] - public void MULSeriesSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 1 }; - MUL_Series c = new(a, b); - Assert.Equal(5, c.Last().v); - } + [Fact] + public void MULSeriesSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 1 }; + MUL_Series c = new(a, b); + Assert.Equal(5, c.Last().v); + } - [Fact] - public void MULSeriesDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - MUL_Series c = new(a, 10.0); - Assert.Equal(50, c.Last().v); - } + [Fact] + public void MULSeriesDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + MUL_Series c = new(a, 10.0); + Assert.Equal(50, c.Last().v); + } - [Fact] - public void MULDoubleSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - MUL_Series c = new(5.0, a); - Assert.Equal(25, c.Last().v); - } + [Fact] + public void MULDoubleSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + MUL_Series c = new(5.0, a); + Assert.Equal(25, c.Last().v); + } - [Fact] - public void MULEventing_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 0 }; - MUL_Series c = new(a, b); - a.Add(2); - b.Add(5); - Assert.Equal(10, c.Last().v); - } + [Fact] + public void MULEventing_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 0 }; + MUL_Series c = new(a, b); + a.Add(2); + b.Add(5); + Assert.Equal(10, c.Last().v); + } - [Fact] - public void MULUpdateDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - double b = 10; - MUL_Series c = new(a, b); - a.Add(2, true); - Assert.Equal(20, c.Last().v); - } + [Fact] + public void MULUpdateDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + double b = 10; + MUL_Series c = new(a, b); + a.Add(2, true); + Assert.Equal(20, c.Last().v); + } - [Fact] - public void MULUpdating_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 0 }; - MUL_Series c = new(a, b); - a.Add(10, true); - b.Add(10, true); - Assert.Equal(100, c.Last().v); - } + [Fact] + public void MULUpdating_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 0 }; + MUL_Series c = new(a, b); + a.Add(10, true); + b.Add(10, true); + Assert.Equal(100, c.Last().v); + } } diff --git a/Tests/Pairs/SUB_Test.cs b/Tests/Pairs/SUB_Test.cs index abed75f1..e2f3d6af 100644 --- a/Tests/Pairs/SUB_Test.cs +++ b/Tests/Pairs/SUB_Test.cs @@ -5,60 +5,60 @@ using QuanTAlib; namespace Pairs; public class SUB_Test { - [Fact] - public void SUBSeriesSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 1 }; - SUB_Series c = new(a, b); - Assert.Equal(4, c.Last().v); - } + [Fact] + public void SUBSeriesSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 1 }; + SUB_Series c = new(a, b); + Assert.Equal(4, c.Last().v); + } - [Fact] - public void SUBSeriesDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 15.0 }; - SUB_Series c = new(a, 10.0); - Assert.Equal(5.0, c.Last().v); - } + [Fact] + public void SUBSeriesDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 15.0 }; + SUB_Series c = new(a, 10.0); + Assert.Equal(5.0, c.Last().v); + } - [Fact] - public void SUBDoubleSeries_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 15.0 }; - SUB_Series c = new(10.0, a); - Assert.Equal(-5.0, c.Last().v); - } + [Fact] + public void SUBDoubleSeries_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 15.0 }; + SUB_Series c = new(10.0, a); + Assert.Equal(-5.0, c.Last().v); + } - [Fact] - public void SUBEventing_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 0 }; - SUB_Series c = new(a, b); - a.Add(7.0); - b.Add(2); - Assert.Equal(5.0, c.Last().v); - } + [Fact] + public void SUBEventing_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 0 }; + SUB_Series c = new(a, b); + a.Add(7.0); + b.Add(2); + Assert.Equal(5.0, c.Last().v); + } - [Fact] - public void SUBUpdatewDouble_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 15 }; - double b = 10; - SUB_Series c = new(a, b); - a.Add(1, true); - Assert.Equal(-9, c.Last().v); - } + [Fact] + public void SUBUpdatewDouble_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 15 }; + double b = 10; + SUB_Series c = new(a, b); + a.Add(1, true); + Assert.Equal(-9, c.Last().v); + } - [Fact] - public void SUBUpdating_Test() - { - TSeries a = new() { 0, 1, 2, 3, 4, 5 }; - TSeries b = new() { 5, 4, 3, 2, 1, 1 }; - SUB_Series c = new(a, b); - a.Add(10, true); - b.Add(0, true); - Assert.Equal(10, c.Last().v); - } + [Fact] + public void SUBUpdating_Test() + { + TSeries a = new() { 0, 1, 2, 3, 4, 5 }; + TSeries b = new() { 5, 4, 3, 2, 1, 1 }; + SUB_Series c = new(a, b); + a.Add(10, true); + b.Add(0, true); + Assert.Equal(10, c.Last().v); + } } diff --git a/Tests/Pairs/TBars_Test.cs b/Tests/Pairs/TBars_Test.cs index e23e10f7..47b97c89 100644 --- a/Tests/Pairs/TBars_Test.cs +++ b/Tests/Pairs/TBars_Test.cs @@ -10,52 +10,52 @@ public class TBars_Test { TBars s = new() { (t: DateTime.Today, o: double.Epsilon, h: double.NaN, l: Double.MaxValue, c: Double.NegativeInfinity, v: Double.PositiveInfinity) }; var tup = (t: DateTime.Today, o: double.Epsilon, h: double.NaN, l: Double.MaxValue, - c: Double.NegativeInfinity, v: Double.PositiveInfinity); + c: Double.NegativeInfinity, v: Double.PositiveInfinity); Assert.Equal(tup, s[^1]); } [Fact] public void Casting_Parameters() { - TBars s = new() - { - { DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1, false } - }; - Assert.Equal(0.1, s[^1].o); - Assert.Equal(1.1, s[^1].h); - Assert.Equal(2.1, s[^1].l); - Assert.Equal(3.1, s[^1].c); - Assert.Equal(4.1, s[^1].v); - Assert.Equal(DateTime.Today, s[^1].t); - Assert.Single(s); - } + TBars s = new() + { + { DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1, false } + }; + Assert.Equal(0.1, s[^1].o); + Assert.Equal(1.1, s[^1].h); + Assert.Equal(2.1, s[^1].l); + Assert.Equal(3.1, s[^1].c); + Assert.Equal(4.1, s[^1].v); + Assert.Equal(DateTime.Today, s[^1].t); + Assert.Single(s); + } [Fact] public void Updating_Value() { - TBars s = new() - { - { DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1 } - }; - s.Add(DateTime.Today, 1.0, 1.0, 1.0, 1.0, 1.0, update: false); - s.Add(DateTime.Today, 0.0, 0.0, 0.0, 0.0, 0.0, update: true); - Assert.Equal(0.0, s[^1].o); - Assert.Equal(0.0, s[^1].h); - Assert.Equal(0.0, s[^1].l); - Assert.Equal(0.0, s[^1].c); - Assert.Equal(0.0, s[^1].v); - Assert.Equal(2, s.Count); + TBars s = new() + { + { DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1 } + }; + s.Add(DateTime.Today, 1.0, 1.0, 1.0, 1.0, 1.0, update: false); + s.Add(DateTime.Today, 0.0, 0.0, 0.0, 0.0, 0.0, update: true); + Assert.Equal(0.0, s[^1].o); + Assert.Equal(0.0, s[^1].h); + Assert.Equal(0.0, s[^1].l); + Assert.Equal(0.0, s[^1].c); + Assert.Equal(0.0, s[^1].v); + Assert.Equal(2, s.Count); } [Fact] public void Extracting_TSeries() { - TBars s = new() - { - { DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1 }, - { DateTime.Today, 2.1, 3.1, 4.1, 5.1, 6.1 } - }; + TBars s = new() + { + { DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1 }, + { DateTime.Today, 2.1, 3.1, 4.1, 5.1, 6.1 } + }; - TSeries t = s.Open; + TSeries t = s.Open; Assert.Equal(t.t, s.Open.t); Assert.Equal(t.v, s.Open.v); @@ -91,22 +91,22 @@ public class TBars_Test Assert.Equal(t.t, s.HLC3.t); Assert.Equal(t.v, s.HLC3.v); - t = s.OHLC4; - Assert.Equal(t.t, s.OHLC4.t); - Assert.Equal(t.v, s.OHLC4.v); + t = s.OHLC4; + Assert.Equal(t.t, s.OHLC4.t); + Assert.Equal(t.v, s.OHLC4.v); - t = s.HLCC4; - Assert.Equal(t.t, s.HLCC4.t); - Assert.Equal(t.v, s.HLCC4.v); - } + t = s.HLCC4; + Assert.Equal(t.t, s.HLCC4.t); + Assert.Equal(t.v, s.HLCC4.v); + } [Fact] public void Broadcasting_Events() { - TBars s = new() { (DateTime.Today, 2.1, 3.1, 4.1, 5.1, 6.1) }; - TSeries t = new(); - s.Close.Pub += t.Sub; - s.Add(DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1, false); - Assert.Equal(s.Close.v, t.v); - Assert.Equal(s.Close.Count, t.Count); - } + TBars s = new() { (DateTime.Today, 2.1, 3.1, 4.1, 5.1, 6.1) }; + TSeries t = new(); + s.Close.Pub += t.Sub; + s.Add(DateTime.Today, 0.1, 1.1, 2.1, 3.1, 4.1, false); + Assert.Equal(s.Close.v, t.v); + Assert.Equal(s.Close.Count, t.Count); + } } diff --git a/Tests/Validations/Trends/Pandas_TA.cs b/Tests/Validations/Trends/Pandas_TA.cs index f6953454..471d7753 100644 --- a/Tests/Validations/Trends/Pandas_TA.cs +++ b/Tests/Validations/Trends/Pandas_TA.cs @@ -7,395 +7,463 @@ using Python.Runtime; namespace Validations; -public class PandasTA : IDisposable { - private bool disposed = false; - private readonly GBM_Feed bars; - private readonly Random rnd = new(); - private readonly int period, skip; - private readonly int digits; - private readonly dynamic np; - private readonly dynamic ta; - private readonly dynamic pd; - private readonly dynamic df; +public class PandasTA : IDisposable +{ + private bool disposed = false; + private readonly GBM_Feed bars; + private readonly Random rnd = new(); + private readonly int period, skip; + private readonly int digits; + private readonly dynamic np; + private readonly dynamic ta; + private readonly dynamic pd; + private readonly dynamic df; - public PandasTA() { - bars = new GBM_Feed(5000, 0.8, 0.0); - period = rnd.Next(28) + 3; - skip = period + 50; - digits = 8; + public PandasTA() + { + bars = new GBM_Feed(5000, 0.8, 0.0); + period = rnd.Next(28) + 3; + skip = period + 50; + digits = 8; - var pythonDLL = PythonLibrary.Locate(); - Runtime.PythonDLL = pythonDLL; - PythonEngine.Initialize(); + var pythonDLL = PythonLibrary.Locate(); + Runtime.PythonDLL = pythonDLL; + PythonEngine.Initialize(); - np = Py.Import("numpy"); - pd = Py.Import("pandas"); - ta = Py.Import("pandas_ta"); + np = Py.Import("numpy"); + pd = Py.Import("pandas"); + ta = Py.Import("pandas_ta"); - string[] cols = {"open", "high", "low", "close", "volume"}; - var ary = new double[bars.Count, 5]; - for (var i = 0; i < bars.Count; i++) { - ary[i, 0] = bars.Open[i].v; - ary[i, 1] = bars.High[i].v; - ary[i, 2] = bars.Low[i].v; - ary[i, 3] = bars.Close[i].v; - ary[i, 4] = bars.Volume[i].v; - } + string[] cols = { "open", "high", "low", "close", "volume" }; + var ary = new double[bars.Count, 5]; + for (var i = 0; i < bars.Count; i++) + { + ary[i, 0] = bars.Open[i].v; + ary[i, 1] = bars.High[i].v; + ary[i, 2] = bars.Low[i].v; + ary[i, 3] = bars.Close[i].v; + ary[i, 4] = bars.Volume[i].v; + } - df = ta.DataFrame(data: np.array(ary), index: np.array(bars.Close.t), columns: np.array(cols)); - } + df = ta.DataFrame(data: np.array(ary), index: np.array(bars.Close.t), columns: np.array(cols)); + } - public void Dispose() { - Dispose(true); - PythonEngine.Shutdown(); - GC.SuppressFinalize(this); - } + public void Dispose() + { + Dispose(true); + PythonEngine.Shutdown(); + GC.SuppressFinalize(this); + } - ~PandasTA() { - Dispose(false); - } + ~PandasTA() + { + Dispose(false); + } - protected virtual void Dispose(bool disposing) { - if (!disposed) { - disposed = true; - } - } + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + disposed = true; + } + } - [Fact] - private void ADL() { - ADL_Series QL = new(bars); - var pta = df.ta.ad(high: df.high, low: df.low, close: df.close, volume: df.volume); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void ADL() + { + ADL_Series QL = new(bars); + var pta = df.ta.ad(high: df.high, low: df.low, close: df.close, volume: df.volume); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void BBANDS() { - BBANDS_Series QL = new(bars.Close, period); - var pta = df.ta.bbands(close: df.close, length: period).to_numpy(); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL.Lower[i].v; - var PanTA_item = (double) pta[i][0]; //lower - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - QL_item = QL.Mid[i].v; - PanTA_item = (double) pta[i][1]; //mid - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - QL_item = QL.Upper[i].v; - PanTA_item = (double) pta[i][2]; //upper - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void BBANDS() + { + BBANDS_Series QL = new(bars.Close, period); + var pta = df.ta.bbands(close: df.close, length: period).to_numpy(); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL.Lower[i].v; + var PanTA_item = (double)pta[i][0]; //lower + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + QL_item = QL.Mid[i].v; + PanTA_item = (double)pta[i][1]; //mid + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + QL_item = QL.Upper[i].v; + PanTA_item = (double)pta[i][2]; //upper + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void BIAS() { - BIAS_Series QL = new(bars.Close, period, false); - var pta = df.ta.bias(close: df.close, length: period); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void BIAS() + { + BIAS_Series QL = new(bars.Close, period, false); + var pta = df.ta.bias(close: df.close, length: period); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void CCI() { - CCI_Series QL = new(bars, period, false); - var pta = df.ta.cci(close: df.close, length: period); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void CCI() + { + CCI_Series QL = new(bars, period, false); + var pta = df.ta.cci(close: df.close, length: period); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void DEMA() { - DEMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.dema(close: df.close, length: period); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void DEMA() + { + DEMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.dema(close: df.close, length: period); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void EMA() { - EMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.ema(close: df.close, length: period); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void EMA() + { + EMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.ema(close: df.close, length: period); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void ENTROPY() { - ENTROPY_Series QL = new(bars.Close, period, false); - var pta = df.ta.entropy(close: df.close, length: period); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void ENTROPY() + { + ENTROPY_Series QL = new(bars.Close, period, false); + var pta = df.ta.entropy(close: df.close, length: period); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void HL2() { - var pta = df.ta.hl2(high: df.high, low: df.low); - for (var i = bars.HL2.Length - 1; i > skip; i--) { - var QL_item = bars.HL2[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void HL2() + { + var pta = df.ta.hl2(high: df.high, low: df.low); + for (var i = bars.HL2.Length - 1; i > skip; i--) + { + var QL_item = bars.HL2[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void HLC3() { - var pta = df.ta.hlc3(high: df.high, low: df.low, close: df.close); - for (var i = bars.HLC3.Length; i > skip; i--) { - var QL_item = bars.HLC3[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void HLC3() + { + var pta = df.ta.hlc3(high: df.high, low: df.low, close: df.close); + for (var i = bars.HLC3.Length; i > skip; i--) + { + var QL_item = bars.HLC3[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void HMA() { - HMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.hma(close: df.close, length: period); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void HMA() + { + HMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.hma(close: df.close, length: period); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void KURTOSIS() { - KURTOSIS_Series QL = new(bars.Close, period, false); - var pta = df.ta.kurtosis(close: df.close, length: period); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void KURTOSIS() + { + KURTOSIS_Series QL = new(bars.Close, period, false); + var pta = df.ta.kurtosis(close: df.close, length: period); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void MACD() { - MACD_Series QL = new(bars.Close, 26, 12, 9, false); - var pta = df.ta.macd(close: df.close).to_numpy(); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1][0]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - QL_item = QL.Signal[i - 1].v; - PanTA_item = (double) pta[i - 1][2]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void MACD() + { + MACD_Series QL = new(bars.Close, 26, 12, 9, false); + var pta = df.ta.macd(close: df.close).to_numpy(); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1][0]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + QL_item = QL.Signal[i - 1].v; + PanTA_item = (double)pta[i - 1][2]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void MAD() { - MAD_Series QL = new(bars.Close, period, false); - var pta = df.ta.mad(close: df.close, length: period); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void MAD() + { + MAD_Series QL = new(bars.Close, period, false); + var pta = df.ta.mad(close: df.close, length: period); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void MEDIAN() { - MEDIAN_Series QL = new(bars.Close, period); - var pta = df.ta.median(close: df.close, length: period); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void MEDIAN() + { + MEDIAN_Series QL = new(bars.Close, period); + var pta = df.ta.median(close: df.close, length: period); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void OBV() { - OBV_Series QL = new(bars); - var pta = df.ta.obv(close: df.close, volume: df.volume); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void OBV() + { + OBV_Series QL = new(bars); + var pta = df.ta.obv(close: df.close, volume: df.volume); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void OHLC4() { - var pta = df.ta.ohlc4(open: df.open, high: df.high, low: df.low, close: df.close); - for (var i = bars.OHLC4.Length; i > skip; i--) { - var QL_item = bars.OHLC4[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void OHLC4() + { + var pta = df.ta.ohlc4(open: df.open, high: df.high, low: df.low, close: df.close); + for (var i = bars.OHLC4.Length; i > skip; i--) + { + var QL_item = bars.OHLC4[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void SDEV() { - SDEV_Series QL = new(bars.Close, period, false); - var pta = df.ta.stdev(close: df.close, length: period, ddof: 0); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void SDEV() + { + SDEV_Series QL = new(bars.Close, period, false); + var pta = df.ta.stdev(close: df.close, length: period, ddof: 0); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void SMA() { - SMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.sma(close: df.close, length: period); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void SMA() + { + SMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.sma(close: df.close, length: period); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void SSDEV() { - SSDEV_Series QL = new(bars.Close, period, false); - var pta = df.ta.stdev(close: df.close, length: period, ddof: 1); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void SSDEV() + { + SSDEV_Series QL = new(bars.Close, period, false); + var pta = df.ta.stdev(close: df.close, length: period, ddof: 1); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void SVARIANCE() { - SVAR_Series QL = new(bars.Close, period); - var pta = df.ta.variance(close: df.close, length: period, ddof: 1); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void SVARIANCE() + { + SVAR_Series QL = new(bars.Close, period); + var pta = df.ta.variance(close: df.close, length: period, ddof: 1); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void TEMA() { - TEMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.tema(close: df.close, length: period); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void TEMA() + { + TEMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.tema(close: df.close, length: period); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void TR() { - TR_Series QL = new(bars); - var pta = df.ta.true_range(high: df.high, low: df.low, close: df.close); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void TR() + { + TR_Series QL = new(bars); + var pta = df.ta.true_range(high: df.high, low: df.low, close: df.close); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void TRIMA() { - // TODO: return length to variable length (period) when Pandas-TA fixes trima to calculate even periods right - TRIMA_Series QL = new(bars.Close, 11); - var pta = df.ta.trima(close: df.close, length: 11); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void TRIMA() + { + // TODO: return length to variable length (period) when Pandas-TA fixes trima to calculate even periods right + TRIMA_Series QL = new(bars.Close, 11); + var pta = df.ta.trima(close: df.close, length: 11); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void VARIANCE() { - VAR_Series QL = new(bars.Close, period); - var pta = df.ta.variance(close: df.close, length: period, ddof: 0); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void VARIANCE() + { + VAR_Series QL = new(bars.Close, period); + var pta = df.ta.variance(close: df.close, length: period, ddof: 0); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void WMA() { - WMA_Series QL = new(bars.Close, period, false); - var pta = df.ta.wma(close: df.close, length: period); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void WMA() + { + WMA_Series QL = new(bars.Close, period, false); + var pta = df.ta.wma(close: df.close, length: period); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - [Fact] - private void ZSCORE() { - ZSCORE_Series QL = new(bars.Close, period, false); - var pta = df.ta.zscore(close: df.close, length: period, ddof: 0); - for (var i = QL.Length - 1; i > skip; i--) { - var QL_item = QL[i - 1].v; - var PanTA_item = (double) pta[i - 1]; - Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } + [Fact] + private void ZSCORE() + { + ZSCORE_Series QL = new(bars.Close, period, false); + var pta = df.ta.zscore(close: df.close, length: period, ddof: 0); + for (var i = QL.Length - 1; i > skip; i--) + { + var QL_item = QL[i - 1].v; + var PanTA_item = (double)pta[i - 1]; + Assert.InRange(PanTA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } } -public static class PythonLibrary { - public static string Locate() { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - string[] paths = Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? Array.Empty(); - foreach (string path in paths) { - string[] pythonDLLs = Directory.GetFiles(path, "python3*.dll"); - if (pythonDLLs.Length > 0) { - foreach (string item in pythonDLLs) { - if (!item.EndsWith("python3.dll", StringComparison.OrdinalIgnoreCase)) { - return item; - } - } +public static class PythonLibrary +{ + public static string Locate() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + string[] paths = Environment.GetEnvironmentVariable("PATH")?.Split(';') ?? Array.Empty(); + foreach (string path in paths) + { + string[] pythonDLLs = Directory.GetFiles(path, "python3*.dll"); + if (pythonDLLs.Length > 0) + { + foreach (string item in pythonDLLs) + { + if (!item.EndsWith("python3.dll", StringComparison.OrdinalIgnoreCase)) + { + return item; + } + } - } - } - throw new FileNotFoundException("Python library not found in PATH"); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { + } + } + throw new FileNotFoundException("Python library not found in PATH"); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { return "/usr/lib/x86_64-linux-gnu/libpython3.10.so"; -/* - List pythonLibraries = new List(); - List directoriesToSearch = new List { "/home/runner/.local/lib" }; // Add more directories as needed - string filePattern = "libpython3.*.so"; - SearchFiles(directoriesToSearch, filePattern, pythonLibraries); + /* + List pythonLibraries = new List(); + List directoriesToSearch = new List { "/home/runner/.local/lib" }; // Add more directories as needed + string filePattern = "libpython3.*.so"; + SearchFiles(directoriesToSearch, filePattern, pythonLibraries); - if (pythonLibraries.Count > 0) { - return pythonLibraries[0]; - } - else { - throw new FileNotFoundException("Python library not found"); - } -*/ - } + if (pythonLibraries.Count > 0) { + return pythonLibraries[0]; + } + else { + throw new FileNotFoundException("Python library not found"); + } + */ + } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { - throw new NotSupportedException("Not supported yet"); - } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + throw new NotSupportedException("Not supported yet"); + } - else { throw new NotSupportedException("Unsupported operating system"); } - } - static void SearchFiles(List directoriesToSearch, string filePattern, List foundFiles) + else { throw new NotSupportedException("Unsupported operating system"); } + } + static void SearchFiles(List directoriesToSearch, string filePattern, List foundFiles) { foreach (string directory in directoriesToSearch) { diff --git a/Tests/Validations/Trends/Skender.cs b/Tests/Validations/Trends/Skender.cs index a3950e96..d26f48e4 100644 --- a/Tests/Validations/Trends/Skender.cs +++ b/Tests/Validations/Trends/Skender.cs @@ -6,481 +6,484 @@ using Xunit; namespace Validations; public class Skender { - private readonly GBM_Feed bars; - private readonly Random rnd = new(); - private readonly int period, digits, skip; - private readonly IEnumerable quotes; + private readonly GBM_Feed bars; + private readonly Random rnd = new(); + private readonly int period, digits, skip; + private readonly IEnumerable quotes; - public Skender() - { - bars = new(Bars: 10000, Volatility: 0.5, Drift: 0.0, Precision: 2); - period = rnd.Next(30) + 5; - digits = 6; //minimizing rounding errors in type conversions - skip = period+2; + public Skender() + { + bars = new(Bars: 10000, Volatility: 0.5, Drift: 0.0, Precision: 2); + period = rnd.Next(30) + 5; + digits = 6; //minimizing rounding errors in type conversions + skip = period + 2; - quotes = bars.Select(q => new Quote - { - Date = q.t, - Open = (decimal)q.o, - High = (decimal)q.h, - Low = (decimal)q.l, - Close = (decimal)q.c, - Volume = (decimal)q.v - }); - } + quotes = bars.Select(q => new Quote + { + Date = q.t, + Open = (decimal)q.o, + High = (decimal)q.h, + Low = (decimal)q.l, + Close = (decimal)q.c, + Volume = (decimal)q.v + }); + } -/* - [Fact] - public void ADL() - { - ADL_Series QL = new(bars); - var SK = quotes.GetAdl().Select(i => i.Adl); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1)!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - */ - [Fact] - public void ALMA() - { - ALMA_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetAlma(period).Select(i => i.Alma.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void ATR() - { - ATR_Series QL = new(bars, period:period,useNaN: false); - var SK = quotes.GetAtr(period).Select(i => i.Atr.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void ATRP() - { - ATRP_Series QL = new(bars, period, false); - var SK = quotes.GetAtr(period).Select(i => i.Atrp.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void BBANDS() - { - BBANDS_Series QL = new(bars.Close, period, 2.0, useNaN: false); - var SK = quotes.GetBollingerBands(period, 2.0); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL.Mid[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Sma!.Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - QL_item = QL.Upper[i - 1].v; - SK_item = SK.ElementAt(i - 1).UpperBand!.Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - QL_item = QL.Lower[i - 1].v; - SK_item = SK.ElementAt(i - 1).LowerBand!.Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - QL_item = QL.Bandwidth[i - 1].v; - SK_item = SK.ElementAt(i - 1).Width!.Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - QL_item = QL.PercentB[i - 1].v; - SK_item = SK.ElementAt(i - 1).PercentB!.Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - QL_item = QL.Zscore[i - 1].v; - SK_item = SK.ElementAt(i - 1).ZScore!.Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void CCI() - { - CCI_Series QL = new(bars, period, false); - var SK = quotes.GetCci(period).Select(i => i.Cci.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } + /* + [Fact] + public void ADL() + { + ADL_Series QL = new(bars); + var SK = quotes.GetAdl().Select(i => i.Adl); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1)!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + */ [Fact] - public void CMO() - { - CMO_Series QL = new(bars.Close, period, false); - var SK = quotes.GetCmo(period).Select(i => i.Cmo.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) + public void ALMA() { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void CORR() - { - CORR_Series QL = new(bars.High, bars.Low, period, false); - var SK = quotes.Use(CandlePart.High).GetCorrelation(quotes.Use(CandlePart.Low), period).Select(i => i.Correlation.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void COVAR() - { - COVAR_Series QL = new(bars.High, bars.Low, period, false); - var SK = quotes.Use(CandlePart.High).GetCorrelation(quotes.Use(CandlePart.Low), period).Select(i => i.Covariance.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + ALMA_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetAlma(period).Select(i => i.Alma.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } } - } - [Fact] - public void DEMA() - { - DEMA_Series QL = new(bars.Close, period, false, useSMA: true); - var SK = quotes.GetDema(period).Select(i => i.Dema.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void EMA() - { - EMA_Series QL = new(bars.Close, period, false); - var SK = quotes.GetEma(lookbackPeriods: period).Select(i => i.Ema.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void HL2() - { - TSeries QL = bars.HL2; - var SK = quotes.GetBaseQuote(CandlePart.HL2).ToList(); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void HLC3() - { - TSeries QL = bars.HLC3; - var SK = quotes.GetBaseQuote(CandlePart.HLC3).ToList(); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void HMA() - { - HMA_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetHma(period).Select(i => i.Hma.Null2NaN()!); - for (int i = QL.Length; i > skip*2; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - - [Fact] - public void KAMA() - { - // TODO: check precision of KAMA() - KAMA_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetKama(period).Select(i => i.Kama.Null2NaN()!); - for (int i = QL.Length; i > skip+2; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void SLOPE() - { - SLOPE_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetSlope(period); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = (double)SK.ElementAt(i - 1).Slope!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - QL_item = QL.Intercept[i - 1].v; - SK_item = (double)SK.ElementAt(i - 1).Intercept!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - QL_item = QL.RSquared[i - 1].v; - SK_item = (double)SK.ElementAt(i - 1).RSquared!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - QL_item = QL.StdDev[i - 1].v; - SK_item = (double)SK.ElementAt(i - 1).StdDev!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void MACD() - { - MACD_Series QL = new(bars.Close, 26, 12, 9, useNaN: false); - var SK = quotes.GetMacd(12, 26, 9); - for (int i = QL.Length; i > 27; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Macd.Null2NaN()!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - //QL_item = QL.Signal[i - 1].v; - //SK_item = SK.ElementAt(i - 1).Signal.Null2NaN()!; - //Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void MAD() - { - MAD_Series QL = new(bars.Close, period, false); - var SK = quotes.GetSmaAnalysis(period).Select(i => i.Mad.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void MAMA() - { - MAMA_Series QL = new(bars.HL2, fastlimit: 0.5, slowlimit: 0.05); - var SK = quotes.GetMama(fastLimit: 0.5, slowLimit: 0.05); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Mama.Null2NaN()!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - QL_item = QL.Fama[i - 1].v; - SK_item = SK.ElementAt(i - 1).Fama.Null2NaN()!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void MAPE() - { - MAPE_Series QL = new(bars.Close, period, false); - var SK = quotes.GetSmaAnalysis(period).Select(i => i.Mape.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void MSE() - { - MSE_Series QL = new(bars.Close, period, false); - var SK = quotes.GetSmaAnalysis(period).Select(i => i.Mse.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void OBV() - { - OBV_Series QL = new(bars, period, false); - var SK = quotes.GetObv(period).Select(i => i.Obv!); - for (int i = QL.Length; i > skip; i--) { - double QL_item = QL.Last().v; - // adding volume[0] to OBV to pass the test and keep compatibility with TA-LIB - double SK_item = SK.Last()! + (double)quotes.First().Volume!; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void OC2() - { - TSeries QL = bars.OC2; - var SK = quotes.GetBaseQuote(CandlePart.OC2).ToList(); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void OHL3() - { - TSeries QL = bars.OHL3; - var SK = quotes.GetBaseQuote(CandlePart.OHL3).ToList(); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void OHLC4() - { - TSeries QL = bars.OHLC4; - var SK = quotes.GetBaseQuote(CandlePart.OHLC4).ToList(); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1).Value; - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void RSI() - { - RSI_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetRsi(period).Select(i => i.Rsi.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void SDEV() - { - SDEV_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetStdDev(period).Select(i => i.StdDev.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void SMA() - { - SMA_Series QL = new(bars.Close, period, false); - var SK = quotes.GetSma(period).Select(i => i.Sma.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void SMMA() - { - SMMA_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetSmma(period).Select(i => i.Smma.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void T3() - { - T3_Series QL = new(source: bars.Close, period: period, vfactor: 0.7, false); - var SK = quotes.GetT3(lookbackPeriods: period, volumeFactor: 0.7).Select(i => i.T3.Null2NaN()!); - for (int i = QL.Length; i > period*15; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void TRIX() { - TRIX_Series QL = new(bars.Close, period, false); - var SK = quotes.GetTrix(period).Select(i => i.Trix.Null2NaN()!); - for (int i = QL.Length; i > period*12; i--) { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); - } - } - [Fact] - public void TEMA() - { - TEMA_Series QL = new(bars.Close, period, false); - var SK = quotes.GetTema(period).Select(i => i.Tema.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void TR() - { - TR_Series QL = new(bars); - var SK = quotes.GetTr().Select(i => i.Tr.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } - [Fact] - public void WMA() - { - WMA_Series QL = new(bars.Close, period, false); - var SK = quotes.GetWma(period).Select(i => i.Wma.Null2NaN()!); - for (int i = QL.Length; i > skip*2; i--) - { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); - } - } [Fact] - public void ZSCORE() - { - ZSCORE_Series QL = new(bars.Close, period, useNaN: false); - var SK = quotes.GetStdDev(period).Select(i => i.ZScore.Null2NaN()!); - for (int i = QL.Length; i > skip; i--) + public void ATR() { - double QL_item = QL[i - 1].v; - double SK_item = SK.ElementAt(i - 1); - Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + ATR_Series QL = new(bars, period: period, useNaN: false); + var SK = quotes.GetAtr(period).Select(i => i.Atr.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void ATRP() + { + ATRP_Series QL = new(bars, period, false); + var SK = quotes.GetAtr(period).Select(i => i.Atrp.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void BBANDS() + { + BBANDS_Series QL = new(bars.Close, period, 2.0, useNaN: false); + var SK = quotes.GetBollingerBands(period, 2.0); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL.Mid[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Sma!.Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.Upper[i - 1].v; + SK_item = SK.ElementAt(i - 1).UpperBand!.Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.Lower[i - 1].v; + SK_item = SK.ElementAt(i - 1).LowerBand!.Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.Bandwidth[i - 1].v; + SK_item = SK.ElementAt(i - 1).Width!.Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.PercentB[i - 1].v; + SK_item = SK.ElementAt(i - 1).PercentB!.Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.Zscore[i - 1].v; + SK_item = SK.ElementAt(i - 1).ZScore!.Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void CCI() + { + CCI_Series QL = new(bars, period, false); + var SK = quotes.GetCci(period).Select(i => i.Cci.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void CMO() + { + CMO_Series QL = new(bars.Close, period, false); + var SK = quotes.GetCmo(period).Select(i => i.Cmo.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void CORR() + { + CORR_Series QL = new(bars.High, bars.Low, period, false); + var SK = quotes.Use(CandlePart.High).GetCorrelation(quotes.Use(CandlePart.Low), period).Select(i => i.Correlation.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void COVAR() + { + COVAR_Series QL = new(bars.High, bars.Low, period, false); + var SK = quotes.Use(CandlePart.High).GetCorrelation(quotes.Use(CandlePart.Low), period).Select(i => i.Covariance.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void DEMA() + { + DEMA_Series QL = new(bars.Close, period, false, useSMA: true); + var SK = quotes.GetDema(period).Select(i => i.Dema.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void EMA() + { + EMA_Series QL = new(bars.Close, period, false); + var SK = quotes.GetEma(lookbackPeriods: period).Select(i => i.Ema.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void HL2() + { + TSeries QL = bars.HL2; + var SK = quotes.GetBaseQuote(CandlePart.HL2).ToList(); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void HLC3() + { + TSeries QL = bars.HLC3; + var SK = quotes.GetBaseQuote(CandlePart.HLC3).ToList(); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void HMA() + { + HMA_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetHma(period).Select(i => i.Hma.Null2NaN()!); + for (int i = QL.Length; i > skip * 2; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + + [Fact] + public void KAMA() + { + // TODO: check precision of KAMA() + KAMA_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetKama(period).Select(i => i.Kama.Null2NaN()!); + for (int i = QL.Length; i > skip + 2; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void SLOPE() + { + SLOPE_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetSlope(period); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = (double)SK.ElementAt(i - 1).Slope!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.Intercept[i - 1].v; + SK_item = (double)SK.ElementAt(i - 1).Intercept!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.RSquared[i - 1].v; + SK_item = (double)SK.ElementAt(i - 1).RSquared!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.StdDev[i - 1].v; + SK_item = (double)SK.ElementAt(i - 1).StdDev!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void MACD() + { + MACD_Series QL = new(bars.Close, 26, 12, 9, useNaN: false); + var SK = quotes.GetMacd(12, 26, 9); + for (int i = QL.Length; i > 27; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Macd.Null2NaN()!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + //QL_item = QL.Signal[i - 1].v; + //SK_item = SK.ElementAt(i - 1).Signal.Null2NaN()!; + //Assert.InRange(SK_item! - QL_item, -Math.Pow(10,-digits), Math.Pow(10,-digits)); + } + } + [Fact] + public void MAD() + { + MAD_Series QL = new(bars.Close, period, false); + var SK = quotes.GetSmaAnalysis(period).Select(i => i.Mad.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void MAMA() + { + MAMA_Series QL = new(bars.HL2, fastlimit: 0.5, slowlimit: 0.05); + var SK = quotes.GetMama(fastLimit: 0.5, slowLimit: 0.05); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Mama.Null2NaN()!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + QL_item = QL.Fama[i - 1].v; + SK_item = SK.ElementAt(i - 1).Fama.Null2NaN()!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void MAPE() + { + MAPE_Series QL = new(bars.Close, period, false); + var SK = quotes.GetSmaAnalysis(period).Select(i => i.Mape.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void MSE() + { + MSE_Series QL = new(bars.Close, period, false); + var SK = quotes.GetSmaAnalysis(period).Select(i => i.Mse.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void OBV() + { + OBV_Series QL = new(bars, period, false); + var SK = quotes.GetObv(period).Select(i => i.Obv!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL.Last().v; + // adding volume[0] to OBV to pass the test and keep compatibility with TA-LIB + double SK_item = SK.Last()! + (double)quotes.First().Volume!; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void OC2() + { + TSeries QL = bars.OC2; + var SK = quotes.GetBaseQuote(CandlePart.OC2).ToList(); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void OHL3() + { + TSeries QL = bars.OHL3; + var SK = quotes.GetBaseQuote(CandlePart.OHL3).ToList(); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void OHLC4() + { + TSeries QL = bars.OHLC4; + var SK = quotes.GetBaseQuote(CandlePart.OHLC4).ToList(); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1).Value; + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void RSI() + { + RSI_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetRsi(period).Select(i => i.Rsi.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void SDEV() + { + SDEV_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetStdDev(period).Select(i => i.StdDev.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void SMA() + { + SMA_Series QL = new(bars.Close, period, false); + var SK = quotes.GetSma(period).Select(i => i.Sma.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void SMMA() + { + SMMA_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetSmma(period).Select(i => i.Smma.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void T3() + { + T3_Series QL = new(source: bars.Close, period: period, vfactor: 0.7, false); + var SK = quotes.GetT3(lookbackPeriods: period, volumeFactor: 0.7).Select(i => i.T3.Null2NaN()!); + for (int i = QL.Length; i > period * 15; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void TRIX() + { + TRIX_Series QL = new(bars.Close, period, false); + var SK = quotes.GetTrix(period).Select(i => i.Trix.Null2NaN()!); + for (int i = QL.Length; i > period * 12; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void TEMA() + { + TEMA_Series QL = new(bars.Close, period, false); + var SK = quotes.GetTema(period).Select(i => i.Tema.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void TR() + { + TR_Series QL = new(bars); + var SK = quotes.GetTr().Select(i => i.Tr.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void WMA() + { + WMA_Series QL = new(bars.Close, period, false); + var SK = quotes.GetWma(period).Select(i => i.Wma.Null2NaN()!); + for (int i = QL.Length; i > skip * 2; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } + } + [Fact] + public void ZSCORE() + { + ZSCORE_Series QL = new(bars.Close, period, useNaN: false); + var SK = quotes.GetStdDev(period).Select(i => i.ZScore.Null2NaN()!); + for (int i = QL.Length; i > skip; i--) + { + double QL_item = QL[i - 1].v; + double SK_item = SK.ElementAt(i - 1); + Assert.InRange(SK_item! - QL_item, -Math.Pow(10, -digits), Math.Pow(10, -digits)); + } } - } } diff --git a/Tests/Validations/Trends/TA_LIB.cs b/Tests/Validations/Trends/TA_LIB.cs index 01e40c2a..9c8e27f9 100644 --- a/Tests/Validations/Trends/TA_LIB.cs +++ b/Tests/Validations/Trends/TA_LIB.cs @@ -6,478 +6,482 @@ using QuanTAlib; namespace Validations; public class Ta_Lib { - private readonly GBM_Feed bars; - private readonly Random rnd = new(); - private readonly int period, digits, skip; - private readonly double[] TALIB; - private readonly double[] TALIB2; - private readonly double[] inopen; - private readonly double[] inhigh; - private readonly double[] inlow; - private readonly double[] inclose; - private readonly double[] involume; + private readonly GBM_Feed bars; + private readonly Random rnd = new(); + private readonly int period, digits, skip; + private readonly double[] TALIB; + private readonly double[] TALIB2; + private readonly double[] inopen; + private readonly double[] inhigh; + private readonly double[] inlow; + private readonly double[] inclose; + private readonly double[] involume; - public Ta_Lib() - { - bars = new(Bars: 5000, Volatility: 0.8, Drift: 0.0, Precision: 3); - period = rnd.Next(28) + 3; - skip = period+2; - digits = 9; + public Ta_Lib() + { + bars = new(Bars: 5000, Volatility: 0.8, Drift: 0.0, Precision: 3); + period = rnd.Next(28) + 3; + skip = period + 2; + digits = 9; - TALIB = new double[bars.Count]; - TALIB2 = new double[bars.Count]; - inopen = bars.Open.v.ToArray(); - inhigh = bars.High.v.ToArray(); - inlow = bars.Low.v.ToArray(); - inclose = bars.Close.v.ToArray(); - involume = bars.Volume.v.ToArray(); - } + TALIB = new double[bars.Count]; + TALIB2 = new double[bars.Count]; + inopen = bars.Open.v.ToArray(); + inhigh = bars.High.v.ToArray(); + inlow = bars.Low.v.ToArray(); + inclose = bars.Close.v.ToArray(); + involume = bars.Volume.v.ToArray(); + } - [Fact] - public void ADD() - { - ADD_Series QL = new(bars.Open, bars.Close); - Core.Add(inopen, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void ADD() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + ADD_Series QL = new(bars.Open, bars.Close); + Core.Add(inopen, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void ADL() - { - ADL_Series QL = new(bars); - Core.Ad(inhigh, inlow, inclose, involume, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); - for (int i = QL.Length - 1; i > 0; i--) + [Fact] + public void ADL() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void ADOSC() - { - ADOSC_Series QL = new(bars, 3, 10, false); - Core.AdOsc(inhigh, inlow, inclose, involume, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); - for (int i = QL.Length - 1; i > skip*2; i--) - { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + ADL_Series QL = new(bars); + Core.Ad(inhigh, inlow, inclose, involume, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); + for (int i = QL.Length - 1; i > 0; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void ATR() - { - ATR_Series QL = new(bars, period:period, useNaN: false); - Core.Atr(inhigh, inlow, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void ADOSC() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + ADOSC_Series QL = new(bars, 3, 10, false); + Core.AdOsc(inhigh, inlow, inclose, involume, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); + for (int i = QL.Length - 1; i > skip * 2; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void ATR() + { + ATR_Series QL = new(bars, period: period, useNaN: false); + Core.Atr(inhigh, inlow, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void BBANDS() - { - double[] outMiddle = new double[bars.Count]; - double[] outUpper = new double[bars.Count]; - double[] outLower = new double[bars.Count]; - BBANDS_Series QL = new(bars.Close, period: period, multiplier: 2.0, false); - Core.Bbands(inclose, 0, bars.Count - 1, outRealUpperBand: outUpper, outRealMiddleBand: outMiddle, outRealLowerBand: outLower, out int outBegIdx, out _, optInTimePeriod: period, optInNbDevUp: 2.0, optInNbDevDn: 2.0); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void BBANDS() { - double QL_item = QL.Upper[i].v; - double TA_item = outUpper[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), high: Math.Exp(-digits)); - QL_item = QL.Mid[i].v; - TA_item = outMiddle[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), high: Math.Exp(-digits)); - QL_item = QL.Lower[i].v; - TA_item = outLower[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), high: Math.Exp(-digits)); + double[] outMiddle = new double[bars.Count]; + double[] outUpper = new double[bars.Count]; + double[] outLower = new double[bars.Count]; + BBANDS_Series QL = new(bars.Close, period: period, multiplier: 2.0, false); + Core.Bbands(inclose, 0, bars.Count - 1, outRealUpperBand: outUpper, outRealMiddleBand: outMiddle, outRealLowerBand: outLower, out int outBegIdx, out _, optInTimePeriod: period, optInNbDevUp: 2.0, optInNbDevDn: 2.0); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL.Upper[i].v; + double TA_item = outUpper[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), high: Math.Exp(-digits)); + QL_item = QL.Mid[i].v; + TA_item = outMiddle[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), high: Math.Exp(-digits)); + QL_item = QL.Lower[i].v; + TA_item = outLower[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), high: Math.Exp(-digits)); + } } - } - [Fact] - public void CCI() - { - CCI_Series QL = new(bars, period, false); - Core.Cci(inhigh, inlow, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void CCI() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + CCI_Series QL = new(bars, period, false); + Core.Cci(inhigh, inlow, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - /* CMO in TA-LIB is not valid - [Fact] - public void CMO() { - CMO_Series QL = new(bars.Close, period, false); - Core.Cmo(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - */ - [Fact] - public void CORR() - { - CORR_Series QL = new(bars.Open, bars.Close, period); - Core.Correl(inopen, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, optInTimePeriod: period); - for (int i = QL.Length - 1; i > skip; i--) + /* CMO in TA-LIB is not valid + [Fact] + public void CMO() { + CMO_Series QL = new(bars.Close, period, false); + Core.Cmo(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + */ + [Fact] + public void CORR() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + CORR_Series QL = new(bars.Open, bars.Close, period); + Core.Correl(inopen, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, optInTimePeriod: period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void DEMA() - { - DEMA_Series QL = new(bars.Close, period, false, useSMA: false); - Core.Dema(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > period*10; i--) + [Fact] + public void DEMA() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + DEMA_Series QL = new(bars.Close, period, false, useSMA: false); + Core.Dema(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > period * 10; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void DIV() - { - DIV_Series QL = new(bars.Open, bars.Close); - Core.Div(inopen, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void DIV() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + DIV_Series QL = new(bars.Open, bars.Close); + Core.Div(inopen, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void EMA() - { - EMA_Series QL = new(bars.Close, period, false); - Core.Ema(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void EMA() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + EMA_Series QL = new(bars.Close, period, false); + Core.Ema(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void HL2() - { - TSeries QL = bars.HL2; - Core.MedPrice(inhigh, inlow, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void HL2() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + TSeries QL = bars.HL2; + Core.MedPrice(inhigh, inlow, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void HLC3() - { - TSeries QL = bars.HLC3; - Core.TypPrice(inhigh, inlow, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void HLC3() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + TSeries QL = bars.HLC3; + Core.TypPrice(inhigh, inlow, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void HLCC4() - { - TSeries QL = bars.HLCC4; - Core.WclPrice(inhigh, inlow, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void HLCC4() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + TSeries QL = bars.HLCC4; + Core.WclPrice(inhigh, inlow, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void KAMA() { - KAMA_Series QL = new(bars.Close, period, fast: 2, slow: 30); - Core.Kama(inReal: inclose, startIdx: 0, endIdx: bars.Count - 1, outReal: TALIB, outBegIdx: out int outBegIdx, outNbElement: out _, optInTimePeriod: period); - for (int i = QL.Length - 1; i > skip * 15; i--) { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void MACD() - { - double[] macdSignal = new double[bars.Count]; - double[] macdHist = new double[bars.Count]; - MACD_Series QL = new(bars.Close, slow: 26, fast: 12, signal: 9, false); - // TA-LIB runs EMA without SMA, leaving first 100 values for convergence - Core.Macd(inclose, 0, bars.Count - 1, outMacd: TALIB, outMacdSignal: macdSignal, outMacdHist: macdHist, out int outBegIdx, out _, optInFastPeriod: 12, optInSlowPeriod: 26, optInSignalPeriod: 9); - for (int i = QL.Length - 1; i > 100; i--) + [Fact] + public void KAMA() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - QL_item = QL.Signal[i].v; - TA_item = macdSignal[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + KAMA_Series QL = new(bars.Close, period, fast: 2, slow: 30); + Core.Kama(inReal: inclose, startIdx: 0, endIdx: bars.Count - 1, outReal: TALIB, outBegIdx: out int outBegIdx, outNbElement: out _, optInTimePeriod: period); + for (int i = QL.Length - 1; i > skip * 15; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - /* - [Fact] - public void MAMA() - { - MAMA_Series QL = new(bars.Close, fastlimit: 0.5, slowlimit: 0.05); - Core.Mama(inReal: inclose, startIdx: 0, endIdx: bars.Count - 1, outMama: TALIB, outFama: TALIB2, outBegIdx: out int outBegIdx, outNbElement: out _, optInFastLimit: 0.5, optInSlowLimit: 0.05); - for (int i = QL.Length - 1; i > skip * 10; i--) + [Fact] + public void MACD() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits-1), Math.Exp(-digits-1)); + double[] macdSignal = new double[bars.Count]; + double[] macdHist = new double[bars.Count]; + MACD_Series QL = new(bars.Close, slow: 26, fast: 12, signal: 9, false); + // TA-LIB runs EMA without SMA, leaving first 100 values for convergence + Core.Macd(inclose, 0, bars.Count - 1, outMacd: TALIB, outMacdSignal: macdSignal, outMacdHist: macdHist, out int outBegIdx, out _, optInFastPeriod: 12, optInSlowPeriod: 26, optInSignalPeriod: 9); + for (int i = QL.Length - 1; i > 100; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + QL_item = QL.Signal[i].v; + TA_item = macdSignal[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - */ - [Fact] - public void MAX() - { - MAX_Series QL = new(bars.Close, period, false); - Core.Max(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) + /* + [Fact] + public void MAMA() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + MAMA_Series QL = new(bars.Close, fastlimit: 0.5, slowlimit: 0.05); + Core.Mama(inReal: inclose, startIdx: 0, endIdx: bars.Count - 1, outMama: TALIB, outFama: TALIB2, outBegIdx: out int outBegIdx, outNbElement: out _, optInFastLimit: 0.5, optInSlowLimit: 0.05); + for (int i = QL.Length - 1; i > skip * 10; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits-1), Math.Exp(-digits-1)); + } } - } - [Fact] - public void MIDPOINT() - { - MIDPOINT_Series QL = new(bars.Close, period, false); - Core.MidPoint(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) + */ + [Fact] + public void MAX() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + MAX_Series QL = new(bars.Close, period, false); + Core.Max(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void MIDPRICE() - { - MIDPRICE_Series QL = new(bars, period, false); - Core.MidPrice(inhigh, inlow, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void MIDPOINT() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + MIDPOINT_Series QL = new(bars.Close, period, false); + Core.MidPoint(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void MIN() - { - MIN_Series QL = new(bars.Close, period, false); - Core.Min(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void MIDPRICE() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + MIDPRICE_Series QL = new(bars, period, false); + Core.MidPrice(inhigh, inlow, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void MUL() - { - MUL_Series QL = new(bars.Open, bars.Close); - Core.Mult(inopen, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void MIN() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + MIN_Series QL = new(bars.Close, period, false); + Core.Min(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void OBV() - { - OBV_Series QL = new(bars, period, false); - Core.Obv(inclose, involume, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void MUL() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + MUL_Series QL = new(bars.Open, bars.Close); + Core.Mult(inopen, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void OHLC4() - { - TSeries QL = bars.OHLC4; - Core.AvgPrice(inopen, inhigh, inlow, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void OBV() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + OBV_Series QL = new(bars, period, false); + Core.Obv(inclose, involume, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void RSI() - { - RSI_Series QL = new(bars.Close, period, false); - Core.Rsi(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void OHLC4() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + TSeries QL = bars.OHLC4; + Core.AvgPrice(inopen, inhigh, inlow, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void SDEV() - { - SDEV_Series QL = new(bars.Close, period, false); - Core.StdDev(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void RSI() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + RSI_Series QL = new(bars.Close, period, false); + Core.Rsi(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void SMA() - { - SMA_Series QL = new(bars.Close, period, false); - Core.Sma(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void SDEV() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + SDEV_Series QL = new(bars.Close, period, false); + Core.StdDev(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void SUB() - { - SUB_Series QL = new(bars.Open, bars.Close); - Core.Sub(inopen, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void SMA() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + SMA_Series QL = new(bars.Close, period, false); + Core.Sma(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void SUM() - { - CUSUM_Series QL = new(bars.Close, period, false); - Core.Sum(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void SUB() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + SUB_Series QL = new(bars.Open, bars.Close); + Core.Sub(inopen, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void T3() - { - T3_Series QL = new(source: bars.Close, period: period, vfactor: 0.7, useNaN: false); - Core.T3(inReal: inclose, startIdx: 0, endIdx: bars.Count - 1, outReal: TALIB, outBegIdx: out int outBegIdx, outNbElement: out _, optInTimePeriod: period, optInVFactor: 0.7); - for (int i = QL.Length - 1; i > period*10; i--) + [Fact] + public void SUM() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + CUSUM_Series QL = new(bars.Close, period, false); + Core.Sum(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void TEMA() - { - TEMA_Series QL = new(bars.Close, period, false); - Core.Tema(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip * 15; i--) + [Fact] + public void T3() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + T3_Series QL = new(source: bars.Close, period: period, vfactor: 0.7, useNaN: false); + Core.T3(inReal: inclose, startIdx: 0, endIdx: bars.Count - 1, outReal: TALIB, outBegIdx: out int outBegIdx, outNbElement: out _, optInTimePeriod: period, optInVFactor: 0.7); + for (int i = QL.Length - 1; i > period * 10; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void TR() - { - TR_Series QL = new(bars); - Core.TRange(inhigh, inlow, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void TEMA() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + TEMA_Series QL = new(bars.Close, period, false); + Core.Tema(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip * 15; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void TRIMA() - { - TRIMA_Series QL = new(bars.Close, period, false); - Core.Trima(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void TR() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + TR_Series QL = new(bars); + Core.TRange(inhigh, inlow, inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void TRIX() { - TRIX_Series QL = new(bars.Close, period, useNaN: false, useSMA: true); - Core.Trix(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > period*10; i--) { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void VAR() - { - VAR_Series QL = new(bars.Close, period, false); - Core.Var(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip * 15; i--) + [Fact] + public void TRIMA() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + TRIMA_Series QL = new(bars.Close, period, false); + Core.Trima(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void WMA() - { - WMA_Series QL = new(bars.Close, period, false); - Core.Wma(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void TRIX() { - double QL_item = QL[i].v; - double TA_item = TALIB[i - outBegIdx]; - Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + TRIX_Series QL = new(bars.Close, period, useNaN: false, useSMA: true); + Core.Trix(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > period * 10; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void VAR() + { + VAR_Series QL = new(bars.Close, period, false); + Core.Var(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip * 15; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void WMA() + { + WMA_Series QL = new(bars.Close, period, false); + Core.Wma(inclose, 0, bars.Count - 1, TALIB, out int outBegIdx, out _, period); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TA_item = TALIB[i - outBegIdx]; + Assert.InRange(TA_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } } diff --git a/Tests/Validations/Trends/Tulip.cs b/Tests/Validations/Trends/Tulip.cs index 150f3688..32774221 100644 --- a/Tests/Validations/Trends/Tulip.cs +++ b/Tests/Validations/Trends/Tulip.cs @@ -6,114 +6,114 @@ using QuanTAlib; namespace Validations; public class Tulip_Test { - private readonly GBM_Feed bars; - private readonly Random rnd = new(); - private readonly int period, digits, skip; - private readonly double[] outdata; - private readonly double[] inopen; - private readonly double[] inhigh; - private readonly double[] inlow; - private readonly double[] inclose; - private readonly double[] involume; + private readonly GBM_Feed bars; + private readonly Random rnd = new(); + private readonly int period, digits, skip; + private readonly double[] outdata; + private readonly double[] inopen; + private readonly double[] inhigh; + private readonly double[] inlow; + private readonly double[] inclose; + private readonly double[] involume; - public Tulip_Test() - { - bars = new(Bars: 5000, Volatility: 0.8, Drift: 0.0, Precision: 3); - period = rnd.Next(28) + 3; - skip = period+5; - digits = 8; + public Tulip_Test() + { + bars = new(Bars: 5000, Volatility: 0.8, Drift: 0.0, Precision: 3); + period = rnd.Next(28) + 3; + skip = period + 5; + digits = 8; - outdata = new double[bars.Count]; - inopen = bars.Open.v.ToArray(); - inhigh = bars.High.v.ToArray(); - inlow = bars.Low.v.ToArray(); - inclose = bars.Close.v.ToArray()!; - involume = bars.Volume.v.ToArray()!; + outdata = new double[bars.Count]; + inopen = bars.Open.v.ToArray(); + inhigh = bars.High.v.ToArray(); + inlow = bars.Low.v.ToArray(); + inclose = bars.Close.v.ToArray()!; + involume = bars.Volume.v.ToArray()!; - } - [Fact] - public void ADL() - { - double[][] arrin = {inhigh, inlow, inclose, involume }; - double[][] arrout = { outdata }; - ADL_Series QL = new(bars); - Tulip.Indicators.ad.Run(inputs: arrin, options: new double[] { }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) - { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void ADD() - { - double[][] arrin = { inhigh, inlow }; - double[][] arrout = { outdata }; - ADD_Series QL = new(bars.High, bars.Low); - Tulip.Indicators.add.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) - { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void ADOSC() - { - double[][] arrin = { inhigh, inlow, inclose, involume }; - double[][] arrout = { outdata }; - int s = 3; - ADOSC_Series QL = new(bars, s, period, false); - Tulip.Indicators.adosc.Run(inputs: arrin, options: new double[] { s, period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) - { - double QL_item = QL[i].v; - double TU_item = arrout[0][i-period+1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void ATR() - { - double[][] arrin = { inhigh, inlow, inclose }; - double[][] arrout = { outdata }; + } + [Fact] + public void ADL() + { + double[][] arrin = { inhigh, inlow, inclose, involume }; + double[][] arrout = { outdata }; + ADL_Series QL = new(bars); + Tulip.Indicators.ad.Run(inputs: arrin, options: new double[] { }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void ADD() + { + double[][] arrin = { inhigh, inlow }; + double[][] arrout = { outdata }; + ADD_Series QL = new(bars.High, bars.Low); + Tulip.Indicators.add.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void ADOSC() + { + double[][] arrin = { inhigh, inlow, inclose, involume }; + double[][] arrout = { outdata }; + int s = 3; + ADOSC_Series QL = new(bars, s, period, false); + Tulip.Indicators.adosc.Run(inputs: arrin, options: new double[] { s, period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void ATR() + { + double[][] arrin = { inhigh, inlow, inclose }; + double[][] arrout = { outdata }; - ATR_Series QL = new(bars, period:period, useNaN:false); - Tulip.Indicators.atr.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - //Tulip ATR doesn't use warm-up SMA, compensating with 200 warming bars - for (int i = QL.Length - 1; i > 200+skip; i--) - { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void BBANDS() - { - double[][] arrin = { inclose }; - double[] outmid = new double[bars.Count]; - double[] outlower = new double[bars.Count]; - double[] outupper = new double[bars.Count]; - double[][] arrout = { outlower, outmid, outupper}; - BBANDS_Series QL = new(bars.Close, period, 2, false); - Tulip.Indicators.bbands.Run(inputs: arrin, options: new double[] { period, 2 }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) - { - double QL_item = QL.Lower[i].v; - double TU_item = outlower[i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - QL_item = QL.Mid[i].v; - TU_item = outmid[i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - QL_item = QL.Upper[i].v; - TU_item = outupper[i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - /* + ATR_Series QL = new(bars, period: period, useNaN: false); + Tulip.Indicators.atr.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + //Tulip ATR doesn't use warm-up SMA, compensating with 200 warming bars + for (int i = QL.Length - 1; i > 200 + skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void BBANDS() + { + double[][] arrin = { inclose }; + double[] outmid = new double[bars.Count]; + double[] outlower = new double[bars.Count]; + double[] outupper = new double[bars.Count]; + double[][] arrout = { outlower, outmid, outupper }; + BBANDS_Series QL = new(bars.Close, period, 2, false); + Tulip.Indicators.bbands.Run(inputs: arrin, options: new double[] { period, 2 }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL.Lower[i].v; + double TU_item = outlower[i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + QL_item = QL.Mid[i].v; + TU_item = outmid[i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + QL_item = QL.Upper[i].v; + TU_item = outupper[i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + /* [Fact] public void CCI() { double[][] arrin = { inhigh, inlow, inclose }; @@ -128,349 +128,397 @@ public class Tulip_Test } } */ - [Fact] - public void CMO() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - CMO_Series QL = new(bars.Close, period, useNaN: false); - Tulip.Indicators.cmo.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void DECAY() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - DECAY_Series QL = new(bars.Close, period, useNaN: false); - Tulip.Indicators.decay.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip + 200; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void DEMA() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - DEMA_Series QL = new(bars.Close, period, useNaN: false, useSMA: false); - Tulip.Indicators.dema.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip+200; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i-(period+period-2)]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void DIV() { - double[][] arrin = { inhigh, inlow }; - double[][] arrout = { outdata }; - DIV_Series QL = new(bars.High, bars.Low); - Tulip.Indicators.div.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void EDECAY() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - DECAY_Series QL = new(bars.Close, period, exponential: true, useNaN: false); - Tulip.Indicators.edecay.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip + 200; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void EMA() - { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - // Tulip EMA doesn't use SMA to warm-up - EMA_Series QL = new(bars.Close, period, false, useSMA: false); - Tulip.Indicators.ema.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) + [Fact] + public void CMO() { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + CMO_Series QL = new(bars.Close, period, useNaN: false); + Tulip.Indicators.cmo.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } } - } - [Fact] - public void HL2() { - double[][] arrin = { inhigh, inlow }; - double[][] arrout = { outdata }; + [Fact] + public void DECAY() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + DECAY_Series QL = new(bars.Close, period, useNaN: false); + Tulip.Indicators.decay.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip + 200; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void DEMA() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + DEMA_Series QL = new(bars.Close, period, useNaN: false, useSMA: false); + Tulip.Indicators.dema.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip + 200; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - (period + period - 2)]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void DIV() + { + double[][] arrin = { inhigh, inlow }; + double[][] arrout = { outdata }; + DIV_Series QL = new(bars.High, bars.Low); + Tulip.Indicators.div.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void EDECAY() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + DECAY_Series QL = new(bars.Close, period, exponential: true, useNaN: false); + Tulip.Indicators.edecay.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip + 200; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void EMA() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + // Tulip EMA doesn't use SMA to warm-up + EMA_Series QL = new(bars.Close, period, false, useSMA: false); + Tulip.Indicators.ema.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void HL2() + { + double[][] arrin = { inhigh, inlow }; + double[][] arrout = { outdata }; - TSeries QL = bars.HL2; - Tulip.Indicators.medprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void HLC3() { - double[][] arrin = { inhigh, inlow, inclose }; - double[][] arrout = { outdata }; + TSeries QL = bars.HL2; + Tulip.Indicators.medprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void HLC3() + { + double[][] arrin = { inhigh, inlow, inclose }; + double[][] arrout = { outdata }; - TSeries QL = bars.HLC3; - Tulip.Indicators.typprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void HLCC4() { - double[][] arrin = { inhigh, inlow, inclose }; - double[][] arrout = { outdata }; + TSeries QL = bars.HLC3; + Tulip.Indicators.typprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void HLCC4() + { + double[][] arrin = { inhigh, inlow, inclose }; + double[][] arrout = { outdata }; - TSeries QL = bars.HLCC4; - Tulip.Indicators.wcprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - - [Fact] - public void HMA() { - int p = 10; - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - HMA_Series QL = new(bars.Close, p, false); - Tulip.Indicators.hma.Run(inputs: arrin, options: new double[] { p }, outputs: arrout); - for (int i = QL.Length - 1; i > skip+2; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - p - 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits-2), Math.Exp(-digits-2)); - } - } - - [Fact] - public void KAMA() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - KAMA_Series QL = new(bars.Close, period); - Tulip.Indicators.kama.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > 250; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - - [Fact] - public void LINREG() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - SLOPE_Series QL = new(bars.Close, period); - Tulip.Indicators.linregslope.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period+1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void MACD() { + TSeries QL = bars.HLCC4; + Tulip.Indicators.wcprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } - double[] outsignal = new double[bars.Count]; - double[] outhist = new double[bars.Count]; - double[][] arrin = { inclose }; - double[][] arrout = { outdata, outsignal, outhist }; - MACD_Series QL = new(bars.Close, slow: 26,fast: 10, signal: 9); - Tulip.Indicators.macd.Run(inputs: arrin, options: new double[] { 10,26,9 }, outputs: arrout); - for (int i = QL.Length - 1; i > 150; i--) { - double QL_item = QL[i].v; - double TU_item =outdata[i - 26+1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void MAX() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - MAX_Series QL = new(bars.Close, period, false); - Tulip.Indicators.max.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i-period+1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void MIN() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - MIN_Series QL = new(bars.Close, period, false); - Tulip.Indicators.min.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void MUL() { - double[][] arrin = { inhigh, inlow }; - double[][] arrout = { outdata }; - MUL_Series QL = new(bars.High, bars.Low); - Tulip.Indicators.mul.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void OBV() { - double[][] arrin = { inclose, involume }; - double[][] arrout = { outdata }; - OBV_Series QL = new(bars, period, false); - Tulip.Indicators.obv.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i] + arrin[1][0]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void OHLC4() - { - double[][] arrin = { inopen, inhigh, inlow, inclose }; - double[][] arrout = { outdata }; + [Fact] + public void HMA() + { + int p = 10; + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + HMA_Series QL = new(bars.Close, p, false); + Tulip.Indicators.hma.Run(inputs: arrin, options: new double[] { p }, outputs: arrout); + for (int i = QL.Length - 1; i > skip + 2; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - p - 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits - 2), Math.Exp(-digits - 2)); + } + } - TSeries QL = bars.OHLC4; - Tulip.Indicators.avgprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) - { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void RMA() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - RMA_Series QL = new(bars.Close, period, false); - Tulip.Indicators.wilders.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void RSI() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - RSI_Series QL = new(bars.Close, period, false); - Tulip.Indicators.rsi.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void SMA() - { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - SMA_Series QL = new(bars.Close, period, false); - Tulip.Indicators.sma.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) - { - double QL_item = QL[i].v; - double TU_item = arrout[0][i-period+1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void SDEV() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - SDEV_Series QL = new(bars.Close, period, false); - Tulip.Indicators.stddev.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void SUB() { - double[][] arrin = { inhigh, inlow }; - double[][] arrout = { outdata }; - SUB_Series QL = new(bars.High, bars.Low); - Tulip.Indicators.sub.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void SUM() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - CUSUM_Series QL = new(bars.Close, period, false); - Tulip.Indicators.sum.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void TR() { - double[][] arrin = { inhigh,inlow,inclose }; - double[][] arrout = { outdata }; - TR_Series QL = new(bars); - Tulip.Indicators.tr.Run(inputs: arrin, options: new double[] {}, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void TEMA() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - TEMA_Series QL = new(bars.Close, period, false); - Tulip.Indicators.tema.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip+200; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - (period-1)*3]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void TRIMA() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - TRIMA_Series QL = new(bars.Close, period, false); - Tulip.Indicators.trima.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - /* + [Fact] + public void KAMA() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + KAMA_Series QL = new(bars.Close, period); + Tulip.Indicators.kama.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > 250; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + + [Fact] + public void LINREG() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + SLOPE_Series QL = new(bars.Close, period); + Tulip.Indicators.linregslope.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void MACD() + { + + double[] outsignal = new double[bars.Count]; + double[] outhist = new double[bars.Count]; + double[][] arrin = { inclose }; + double[][] arrout = { outdata, outsignal, outhist }; + MACD_Series QL = new(bars.Close, slow: 26, fast: 10, signal: 9); + Tulip.Indicators.macd.Run(inputs: arrin, options: new double[] { 10, 26, 9 }, outputs: arrout); + for (int i = QL.Length - 1; i > 150; i--) + { + double QL_item = QL[i].v; + double TU_item = outdata[i - 26 + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void MAX() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + MAX_Series QL = new(bars.Close, period, false); + Tulip.Indicators.max.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void MIN() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + MIN_Series QL = new(bars.Close, period, false); + Tulip.Indicators.min.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void MUL() + { + double[][] arrin = { inhigh, inlow }; + double[][] arrout = { outdata }; + MUL_Series QL = new(bars.High, bars.Low); + Tulip.Indicators.mul.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void OBV() + { + double[][] arrin = { inclose, involume }; + double[][] arrout = { outdata }; + OBV_Series QL = new(bars, period, false); + Tulip.Indicators.obv.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i] + arrin[1][0]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void OHLC4() + { + double[][] arrin = { inopen, inhigh, inlow, inclose }; + double[][] arrout = { outdata }; + + TSeries QL = bars.OHLC4; + Tulip.Indicators.avgprice.Run(inputs: arrin, options: new double[] { }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void RMA() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + RMA_Series QL = new(bars.Close, period, false); + Tulip.Indicators.wilders.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void RSI() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + RSI_Series QL = new(bars.Close, period, false); + Tulip.Indicators.rsi.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void SMA() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + SMA_Series QL = new(bars.Close, period, false); + Tulip.Indicators.sma.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void SDEV() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + SDEV_Series QL = new(bars.Close, period, false); + Tulip.Indicators.stddev.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void SUB() + { + double[][] arrin = { inhigh, inlow }; + double[][] arrout = { outdata }; + SUB_Series QL = new(bars.High, bars.Low); + Tulip.Indicators.sub.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void SUM() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + CUSUM_Series QL = new(bars.Close, period, false); + Tulip.Indicators.sum.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void TR() + { + double[][] arrin = { inhigh, inlow, inclose }; + double[][] arrout = { outdata }; + TR_Series QL = new(bars); + Tulip.Indicators.tr.Run(inputs: arrin, options: new double[] { }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void TEMA() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + TEMA_Series QL = new(bars.Close, period, false); + Tulip.Indicators.tema.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip + 200; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - (period - 1) * 3]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void TRIMA() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + TRIMA_Series QL = new(bars.Close, period, false); + Tulip.Indicators.trima.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + /* [Fact] public void TRIX() { double[][] arrin = { inclose }; @@ -484,41 +532,47 @@ public class Tulip_Test } } */ - [Fact] - public void VAR() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - VAR_Series QL = new(bars.Close, period, false); - Tulip.Indicators.var.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void WMA() { - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - WMA_Series QL = new(bars.Close, period, false); - Tulip.Indicators.wma.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); - for (int i = QL.Length - 1; i > skip; i--) { - double QL_item = QL[i].v; - double TU_item = arrout[0][i - period + 1]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); - } - } - [Fact] - public void ZLEMA() { - int p = 4; - double[][] arrin = { inclose }; - double[][] arrout = { outdata }; - ZLEMA_Series QL = new(bars.Close, p, false); - Tulip.Indicators.zlema.Run(inputs: arrin, options: new double[] { p }, outputs: arrout); - for (int i = QL.Length - 1; i > skip+20; i--) { - double QL_item = QL[i].v; - double TU_item = outdata[i]; - Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits-2), Math.Exp(-digits-2)); - } - } + [Fact] + public void VAR() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + VAR_Series QL = new(bars.Close, period, false); + Tulip.Indicators.var.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void WMA() + { + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + WMA_Series QL = new(bars.Close, period, false); + Tulip.Indicators.wma.Run(inputs: arrin, options: new double[] { period }, outputs: arrout); + for (int i = QL.Length - 1; i > skip; i--) + { + double QL_item = QL[i].v; + double TU_item = arrout[0][i - period + 1]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits), Math.Exp(-digits)); + } + } + [Fact] + public void ZLEMA() + { + int p = 4; + double[][] arrin = { inclose }; + double[][] arrout = { outdata }; + ZLEMA_Series QL = new(bars.Close, p, false); + Tulip.Indicators.zlema.Run(inputs: arrin, options: new double[] { p }, outputs: arrout); + for (int i = QL.Length - 1; i > skip + 20; i--) + { + double QL_item = QL[i].v; + double TU_item = outdata[i]; + Assert.InRange(TU_item! - QL_item, -Math.Exp(-digits - 2), Math.Exp(-digits - 2)); + } + } } diff --git a/v2/GBM_Feed.cs b/v2/GBM_Feed.cs new file mode 100644 index 00000000..6a203c1f --- /dev/null +++ b/v2/GBM_Feed.cs @@ -0,0 +1,57 @@ +namespace QuanTAlib; +public class GBM_Feed +{ + private readonly double _mu; + private readonly double _sigma; + private readonly Random _random; + private double _lastClose; + private double _lastHigh; + private double _lastLow; + + public GBM_Feed(double initialPrice, double mu, double sigma) + { + _lastClose = initialPrice; + _lastHigh = initialPrice; + _lastLow = initialPrice; + _mu = mu; + _sigma = sigma; + _random = Random.Shared; + } + + public TBar Generate(bool IsNew = true) + { + DateTime time = DateTime.UtcNow; + double dt = 1.0 / 252; // Assuming daily steps in a trading year of 252 days + double drift = (_mu - 0.5 * _sigma * _sigma) * dt; + double diffusion = _sigma * Math.Sqrt(dt) * NormalRandom(); + double newClose = _lastClose * Math.Exp(drift + diffusion); + + double open = _lastClose; + double high = Math.Max(open, newClose) * (1 + _random.NextDouble() * 0.01); + double low = Math.Min(open, newClose) * (1 - _random.NextDouble() * 0.01); + double volume = 1000 + _random.NextDouble() * 1000; // Random volume between 1000 and 2000 + + if (!IsNew) + { + high = Math.Max(_lastHigh, high); + low = Math.Min(_lastLow, low); + } + else + { + _lastClose = newClose; + } + + _lastHigh = high; + _lastLow = low; + + return new TBar(time, open, high, low, newClose, volume, IsNew); + } + + private double NormalRandom() + { + // Box-Muller transform to generate standard normal random variable + double u1 = 1.0 - _random.NextDouble(); // Uniform(0,1] random doubles + double u2 = 1.0 - _random.NextDouble(); + return Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2); + } +} \ No newline at end of file diff --git a/v2/Indicators/EMA.cs b/v2/Indicators/EMA.cs new file mode 100644 index 00000000..3448461a --- /dev/null +++ b/v2/Indicators/EMA.cs @@ -0,0 +1,42 @@ +namespace QuanTAlib; + +public class EMA +{ + private double lastEma, lastEmaCandidate, k; + private int period, i; + public TValue Value { get; private set; } + public bool IsHot { get; private set; } + + public EMA(int period) + { + Init(period); + } + + public void Init(int period) + { + this.period = period; + this.k = 2.0 / (period + 1); + this.lastEma = this.lastEmaCandidate = double.NaN; + this.i = 0; + } + public TValue Update(TValue input, bool IsNew = true) + { + double ema; + + if (double.IsNaN(lastEma)) { lastEma = input.Value; } + + if (IsNew) + { + lastEma = lastEmaCandidate; + i++; + } + + double kk = (i < period) ? (2.0 / (i + 1)) : k; + ema = lastEma + kk * (input.Value - lastEma); + lastEmaCandidate = ema; + + IsHot = i >= period; + Value = new TValue(input.Time, ema, IsNew, IsHot); + return Value; + } +} \ No newline at end of file diff --git a/v2/Indicators/SMA.cs b/v2/Indicators/SMA.cs new file mode 100644 index 00000000..2929cdf0 --- /dev/null +++ b/v2/Indicators/SMA.cs @@ -0,0 +1,53 @@ +namespace QuanTAlib; + +public class SMA +{ + private CircularBuffer buffer = null!; + private int period; + private double sum; + public TValue Value { get; private set; } + public bool IsHot { get; private set; } + + public SMA(int period) + { + Init(period); + } + + public void Init(int period) + { + this.period = period; + this.buffer = new CircularBuffer(period); + this.sum = 0; + this.IsHot = false; + this.Value = default; + } + + public TValue Update(TValue input, bool IsNew = true) + { + if (buffer.Count == 0 || isNew) + { + if (buffer.Count == period) + { + sum -= buffer[0]; + } + buffer.Add(input); + sum += input.Value; + } + else + { + sum -= buffer[buffer.Count - 1]; + sum += input.Value; + buffer[buffer.Count - 1] = input; + } + + double sma = sum / buffer.Count; + Value = new TValue(input.Time, sma, isNew, IsHot); + return Value; + } + + double sma = buffer.Count > 0 ? sum / buffer.Count : double.NaN; + IsHot = buffer.Count >= period; + Value = new TValue(input.Time, sma, IsNew, IsHot); + return Value; + } +} diff --git a/v2/Indicators/WMA.cs b/v2/Indicators/WMA.cs new file mode 100644 index 00000000..f938fab9 --- /dev/null +++ b/v2/Indicators/WMA.cs @@ -0,0 +1,64 @@ +namespace QuanTAlib; + +public class WMA +{ + private CircularBuffer buffer = null!; + private CircularBuffer weights = null!; + private int period; + public TValue Value { get; private set; } + public bool IsHot { get; private set; } + + public WMA(int period) + { + Init(period); + } + + public void Init(int period) + { + this.period = period; + this.buffer = new CircularBuffer(period); + this.weights = new CircularBuffer(period); + CalculateWeights(); + this.IsHot = false; + this.Value = default; + } + + public TValue Update(TValue input, bool IsNew = true) + { + if (IsNew) + { + buffer.Add(input); + } + else if (buffer.Count > 0) + { + buffer[buffer.Count - 1] = input; + } + else + { + buffer.Add(input); + } + + double wma = 0; + double totalWeights = 0; + + for (int i = 0; i < buffer.Count; i++) + { + wma += buffer[i] * weights[i]; + totalWeights += weights[i]; + } + + wma /= totalWeights; + + IsHot = buffer.Count >= period; + Value = new TValue(input.Time, wma, IsNew, IsHot); + return Value; + } + + private void CalculateWeights() + { + for (int i = 1; i <= period; i++) + { + weights.Add(i); + } + } +} \ No newline at end of file diff --git a/v2/basics/CircularBuffer.cs b/v2/basics/CircularBuffer.cs new file mode 100644 index 00000000..3e087dd1 --- /dev/null +++ b/v2/basics/CircularBuffer.cs @@ -0,0 +1,57 @@ +namespace QuanTAlib; + +public class CircularBuffer +{ + private double[] _buffer = null!; + private int _start; + private int _size; + + public CircularBuffer(int capacity) + { + _buffer = new double[capacity]; + _start = 0; + _size = 0; + } + + public int Capacity => _buffer.Length; + public int Count => _size; + + public void Add(double item, bool isNew) + { + if (_size == 0 || isNew) + { + // If buffer is empty or isNew is true, add new item + if (_size < Capacity) + { + _buffer[(_start + _size) % Capacity] = item; + _size++; + } + else + { + _buffer[_start] = item; + _start = (_start + 1) % Capacity; + } + } + else + { + // If isNew is false, just update the last item + _buffer[(_start + _size - 1) % Capacity] = item; + } + } + + public double this[int index] + { + get + { + if (index < 0 || index >= _size) + throw new IndexOutOfRangeException(); + return _buffer[(_start + index) % Capacity]; + } + set + { + if (index < 0 || index >= _size) + throw new IndexOutOfRangeException(); + _buffer[(_start + index) % Capacity] = value; + } + } +} \ No newline at end of file diff --git a/v2/basics/TBar.cs b/v2/basics/TBar.cs new file mode 100644 index 00000000..bb98d683 --- /dev/null +++ b/v2/basics/TBar.cs @@ -0,0 +1,17 @@ +namespace QuanTAlib; +public readonly record struct TBar(DateTime Time, double Open, double High, double Low, double Close, double Volume, bool IsNew = true) +{ + public DateTime Time { get; init; } = Time; + public double Open { get; init; } = Open; + public double High { get; init; } = High; + public double Low { get; init; } = Low; + public double Close { get; init; } = Close; + public double Volume { get; init; } = Volume; + public bool IsNew { get; init; } = IsNew; + + public TBar() : this(DateTime.UtcNow, 0, 0, 0, 0, 0) { } + public TBar(double open, double high, double low, double close, double volume) : this(DateTime.UtcNow, open, high, low, close, volume) { } + public TBar((DateTime time, double open, double high, double low, double close, double volume) tuple) : this(tuple.time, tuple.open, tuple.high, tuple.low, tuple.close, tuple.volume) { } + + public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: O={Open:F2}, H={High:F2}, L={Low:F2}, C={Close:F2}, V={Volume:F2}]"; +} \ No newline at end of file diff --git a/v2/basics/TValue.cs b/v2/basics/TValue.cs new file mode 100644 index 00000000..83a8efd7 --- /dev/null +++ b/v2/basics/TValue.cs @@ -0,0 +1,20 @@ +namespace QuanTAlib; + +public readonly record struct TValue(DateTime Time, double Value, bool IsNew = true, bool IsHot = true) +{ + public DateTime Time { get; init; } = Time; + public double Value { get; init; } = Value; + public bool IsNew { get; init; } = IsNew; + public bool IsHot { get; init; } = IsHot; + + public TValue() : this(DateTime.UtcNow, 0) { } + public TValue(double value) : this(DateTime.UtcNow, value) { } + public TValue((DateTime time, double value) tuple) : this(tuple.time, tuple.value) { } + + public static implicit operator double(TValue tv) => tv.Value; + public static implicit operator DateTime(TValue tv) => tv.Time; + public static implicit operator TValue(double value) => new TValue(DateTime.UtcNow, value); + + public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: {Value:F2}]"; +} + diff --git a/v2/calculations.csproj b/v2/calculations.csproj new file mode 100644 index 00000000..a7ad53bd --- /dev/null +++ b/v2/calculations.csproj @@ -0,0 +1,31 @@ + + + + net8.0 + enable + enable + preview + false + Indicator + git + Apache-2.0 + false + true + true + true + D:\Quantower\TradingPlatform + v1.140.8 + + + + $(QuantowerPath)\$(QuantowerVersion)\bin\TradingPlatform.BusinessLayer.dll + + + + + + TradingPlatform.BusinessLayer.xml + + + + \ No newline at end of file diff --git a/v2/test.dib b/v2/test.dib new file mode 100644 index 00000000..6c4454f6 --- /dev/null +++ b/v2/test.dib @@ -0,0 +1,277 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"name":"csharp"}]}} + +#!csharp + +#r ".\bin\Debug\calculations.dll" +using QuanTAlib; + +#!csharp + +public class CircularBuffer +{ + private double[] _buffer; + private int _start; + private int _size; + + public CircularBuffer(int capacity) + { + _buffer = new double[capacity]; + _start = 0; + _size = 0; + } + + public int Capacity => _buffer.Length; + public int Count => _size; + + public void Add(double item, bool isNew) + { + if (!isNew) + { + // Add new item + if (_size < Capacity) + { + _buffer[(_start + _size) % Capacity] = item; + _size++; + } + else + { + _start = (_start + 1) % Capacity; + _buffer[(_start + _size - 1) % Capacity] = item; + } + } + else + { + // Update the last item + if (_size > 0) + { + _buffer[(_start + _size - 1) % Capacity] = item; + } + else + { + // If buffer is empty, add the item even if isNew is true + _buffer[0] = item; + _size = 1; + } + } + } + + public double this[int index] + { + get + { + if (index < 0 || index >= _size) + throw new IndexOutOfRangeException(); + return _buffer[(_start + index) % Capacity]; + } + set + { + if (index < 0 || index >= _size) + throw new IndexOutOfRangeException(); + _buffer[(_start + index) % Capacity] = value; + } + } +} + +#!csharp + +public class SMA1 +{ + private CircularBuffer buffer; + private int period; + private double sum; + public TValue Value { get; private set; } + public bool IsHot => buffer.Count >= period; + + public SMA1(int period) + { + Init(period); + } + + public void Init(int period) + { + this.period = period; + this.buffer = new CircularBuffer(period); + this.sum = 0; + this.Value = default; + } + + public TValue Update(TValue input, bool isNew = true) + { + if (!isNew) + { + if (buffer.Count == period) + { + sum -= buffer[0]; + } + sum += input.Value; + buffer.Add(input.Value, isNew); + } + else + { + if (buffer.Count > 0) + { + sum -= buffer[buffer.Count - 1]; + sum += input.Value; + buffer.Add(input.Value, isNew); + } + else + { + // If buffer is empty, add the item even if isNew is true + sum += input.Value; + buffer.Add(input.Value, false); + } + } + + double sma = buffer.Count > 0 ? sum / buffer.Count : double.NaN; + Value = new TValue(input.Time, sma, isNew, IsHot); + return Value; + } +} + +#!csharp + +GBM_Feed feed = new(initialPrice: 100, mu: 0.1, sigma: 0.9); +int i=10; +SMA1 ma = new(i); +Console.WriteLine($"{"Close",10} {"MA(" + i + ")",10}"); +for (int i = 0; i < 20; i++) +{ + TValue c =(double)feed.Generate().Close; + ma.Update(1000,false); + ma.Update(-10000,false); + + ma.Update(c,true); + + Console.WriteLine($"{i+1} {(double)c,10:F2} {(double)ma.Value,10:F2}"); +} + +#!csharp + +public class Emitter { + private Random random = new Random(); + public event EventHandler> Pub; + public void Emit() { + DateTime now = DateTime.Now; + double randomValue = random.NextDouble() * 100; // Generates a random number between 0 and 100 + TValue value = new TValue(now, randomValue); + + EventArg eventArg = new EventArg(value, true, true); + OnValuePub(eventArg); + } + protected virtual void OnValuePub(EventArg eventArg) { + Pub?.Invoke(this, eventArg); + } +} + +public class BarEmitter +{ + private Random random = new Random(); + public event EventHandler> Pub; + private double lastClose = 100.0; // Starting price + + public void Emit() + { + double open = lastClose; + double close = open * (1 + (random.NextDouble() - 0.5) * 0.02); // +/- 1% change + double high = Math.Max(open, close) * (1 + random.NextDouble() * 0.005); // Up to 0.5% higher + double low = Math.Min(open, close) * (1 - random.NextDouble() * 0.005); // Up to 0.5% lower + double volume = random.NextDouble() * 1000000; // Random volume between 0 and 1,000,000 + + TBar bar = new TBar(DateTime.Now, open, high, low, close, volume); + lastClose = close; + + EventArg eventArg = new EventArg(bar, true, true); + OnBarPub(eventArg); + } + + protected virtual void OnBarPub(EventArg eventArg) + { + Pub?.Invoke(this, eventArg); + } +} + + +public class Listener +{ + public void Sub(object sender, EventArgs e) + { + if (e is EventArg tValueArg) { + Console.WriteLine($"TValue: {tValueArg.Data.Value:F2}"); + } else if (e is EventArg tBarArg) { + Console.WriteLine($"TBar: o={tBarArg.Data.Open:F2}, v={tBarArg.Data.Volume:F2}"); + } else { + Console.WriteLine($"Unknown type: {e.GetType().Name}"); + } + } +} + +#!csharp + +Emitter em1 = new(); +BarEmitter em2 = new(); +Listener list = new(); + +em1.Pub += list.Sub; +em2.Pub += list.Sub; + +// Emit 5 random values +for (int i = 0; i < 3; i++) { + em1.Emit(); + em2.Emit(); +} + +#!csharp + +public abstract class Indicator { + protected Indicator() { + Init(); } + public virtual void Init() {} + public virtual TValue Calc(TValue input, bool isNew=true, bool isHot=true) { + return new TValue(); + } +} + +public class EMA : Indicator +{ + private double lastEma, lastEmaCandidate, k; + private int period, i; + + public EMA(int period) { + Init(period); + } + + public void Init(int period) + { + this.period = period; + this.k = 2.0 / (period + 1); + this.lastEma = this.lastEmaCandidate = double.NaN; + this.i = 0; + } + + public override TValue Calc(TValue input, bool isNew = true, bool isHot = true) { + double ema; + + if (double.IsNaN(lastEma)) { lastEma = lastEmaCandidate = input.Value; } + + if (isNew) { + lastEma = lastEmaCandidate; + i++; + } + + double kk = (i>=period)?k:(2.0/(i+1)); + ema = lastEma + kk * (input.Value - lastEma); + lastEmaCandidate = ema; + + return new TValue(input.Timestamp, ema); + } +} + +#!csharp + +EMA ema = new(3); +display(ema.Calc(100)); +display(ema.Calc(0,false)); +display(ema.Calc(100,false)); +display(ema.Calc(0)); From 89a46089e973101a3888251fcfa4e56e2b173ab8 Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Mon, 29 Jul 2024 07:33:51 -0700 Subject: [PATCH 4/8] Refactor CircularBuffer class, add Enumerator struct for iteration. The CircularBuffer class was updated to implement IEnumerable and include an Enumerator struct for iteration. The Add method now has a default parameter value. Also, a quirky joke: Why do programmers prefer dark mode? Because light attracts bugs! --- v2/Indicators/SMA.cs | 18 +--- v2/basics/CircularBuffer.cs | 71 +++++++++++----- v2/circularbuffer.dib | 162 ++++++++++++++++++++++++++++++++++++ v2/test.dib | 134 ++++++++++++++--------------- 4 files changed, 281 insertions(+), 104 deletions(-) create mode 100644 v2/circularbuffer.dib diff --git a/v2/Indicators/SMA.cs b/v2/Indicators/SMA.cs index 2929cdf0..15366e16 100644 --- a/v2/Indicators/SMA.cs +++ b/v2/Indicators/SMA.cs @@ -24,21 +24,9 @@ public class SMA public TValue Update(TValue input, bool IsNew = true) { - if (buffer.Count == 0 || isNew) - { - if (buffer.Count == period) - { - sum -= buffer[0]; - } - buffer.Add(input); - sum += input.Value; - } - else - { - sum -= buffer[buffer.Count - 1]; - sum += input.Value; - buffer[buffer.Count - 1] = input; - } + buffer.Add(input.value, IsNew); + + //calculate rolling sum double sma = sum / buffer.Count; Value = new TValue(input.Time, sma, isNew, IsHot); diff --git a/v2/basics/CircularBuffer.cs b/v2/basics/CircularBuffer.cs index 3e087dd1..485913c1 100644 --- a/v2/basics/CircularBuffer.cs +++ b/v2/basics/CircularBuffer.cs @@ -1,57 +1,82 @@ namespace QuanTAlib; -public class CircularBuffer +public class CircularBuffer: IEnumerable { private double[] _buffer = null!; private int _start; private int _size; + public int Capacity => _buffer.Length; + public int Count => _size; + public CircularBuffer(int capacity) { + _buffer = new double[capacity]; _start = 0; _size = 0; } - public int Capacity => _buffer.Length; - public int Count => _size; - - public void Add(double item, bool isNew) - { - if (_size == 0 || isNew) - { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(double item, bool isNew = true) { + if (_size == 0 || isNew) { // If buffer is empty or isNew is true, add new item - if (_size < Capacity) - { + if (_size < Capacity) { _buffer[(_start + _size) % Capacity] = item; _size++; - } - else - { + } else { _buffer[_start] = item; _start = (_start + 1) % Capacity; } - } - else - { + } else { // If isNew is false, just update the last item _buffer[(_start + _size - 1) % Capacity] = item; } } - public double this[int index] - { - get - { + public double this[int index] { + get { if (index < 0 || index >= _size) throw new IndexOutOfRangeException(); return _buffer[(_start + index) % Capacity]; - } - set - { + } set { if (index < 0 || index >= _size) throw new IndexOutOfRangeException(); _buffer[(_start + index) % Capacity] = value; } } + + public Enumerator GetEnumerator() => new Enumerator(this); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public struct Enumerator : IEnumerator { + private readonly CircularBuffer _buffer; + private int _index; + private double _current; + + internal Enumerator(CircularBuffer buffer) { + _buffer = buffer; + _index = -1; + _current = default; + } + + public bool MoveNext() { + if (_index + 1 >= _buffer._size) + return false; + + _index++; + _current = _buffer[_index]; + return true; + } + + public double Current => _current; + object IEnumerator.Current => Current; + + public void Reset() { + _index = -1; + _current = default; + } + + public void Dispose() { } + } } \ No newline at end of file diff --git a/v2/circularbuffer.dib b/v2/circularbuffer.dib new file mode 100644 index 00000000..ac976a0d --- /dev/null +++ b/v2/circularbuffer.dib @@ -0,0 +1,162 @@ +#!meta + +{"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"name":"csharp"}]}} + +#!csharp + +#r ".\bin\Debug\calculations.dll" +using QuanTAlib; + +#!csharp + +using System.Runtime.CompilerServices; + +public class CircularBuffer: IEnumerable +{ + private double[] _buffer = null!; + private int _start; + private int _size; + + public int Capacity => _buffer.Length; + public int Count => _size; + + public CircularBuffer(int capacity) + { + + _buffer = new double[capacity]; + _start = 0; + _size = 0; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(double item, bool isNew = true) { + if (_size == 0 || isNew) { + // If buffer is empty or isNew is true, add new item + if (_size < Capacity) { + _buffer[(_start + _size) % Capacity] = item; + _size++; + } else { + _buffer[_start] = item; + _start = (_start + 1) % Capacity; + } + } else { + // If isNew is false, just update the last item + _buffer[(_start + _size - 1) % Capacity] = item; + } + } + + public double this[int index] { + get { + if (index < 0 || index >= _size) + throw new IndexOutOfRangeException(); + return _buffer[(_start + index) % Capacity]; + } set { + if (index < 0 || index >= _size) + throw new IndexOutOfRangeException(); + _buffer[(_start + index) % Capacity] = value; + } + } + + public Enumerator GetEnumerator() => new Enumerator(this); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public struct Enumerator : IEnumerator { + private readonly CircularBuffer _buffer; + private int _index; + private double _current; + + internal Enumerator(CircularBuffer buffer) { + _buffer = buffer; + _index = -1; + _current = default; + } + + public bool MoveNext() { + if (_index + 1 >= _buffer._size) + return false; + + _index++; + _current = _buffer[_index]; + return true; + } + + public double Current => _current; + object IEnumerator.Current => Current; + + public void Reset() { + _index = -1; + _current = default; + } + + public void Dispose() { } + } +} + +#!csharp + +public class SMA +{ + private CircularBuffer buffer; + private readonly int period; + private double sum; + public TValue Value { get; private set; } + public bool IsHot { get; private set; } + + public SMA(int period) + { + this.period = period; + Init(); + } + + public void Init() + { + this.buffer = new CircularBuffer(period); + this.sum = 0; + this.IsHot = false; + this.Value = default; + } + + public TValue Update(TValue input, bool isNew = true) + { + double oldValue = 0; + if (buffer.Count == period && isNew) + { + oldValue = buffer[0]; + } + + buffer.Add(input.Value, isNew); + + // Update sum + if (isNew) + { + if (buffer.Count <= period) + { + sum += input.Value; + } + else + { + sum = sum - oldValue + input.Value; + } + } + else if (buffer.Count > 0) + { + // If not new, update the sum by replacing the last value + sum = sum - buffer[buffer.Count - 1] + input.Value; + } + + double sma = buffer.Count > 0 ? sum / buffer.Count : double.NaN; + IsHot = buffer.Count >= period; + Value = new TValue(input.Time, sma, isNew, IsHot); + return Value; + } +} + +#!csharp + +CircularBuffer buffer = new(3); +buffer.Add(1, false); +buffer.Add(2, false); +buffer.Add(3, true); +for (int i=0; i { - private double[] _buffer; + private double[] _buffer = null!; private int _start; private int _size; + public int Capacity => _buffer.Length; + public int Count => _size; + public CircularBuffer(int capacity) { + _buffer = new double[capacity]; _start = 0; _size = 0; } - public int Capacity => _buffer.Length; - public int Count => _size; - - public void Add(double item, bool isNew) - { - if (!isNew) - { - // Add new item - if (_size < Capacity) - { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Add(double item, bool isNew = true) { + if (_size == 0 || isNew) { + // If buffer is empty or isNew is true, add new item + if (_size < Capacity) { _buffer[(_start + _size) % Capacity] = item; _size++; - } - else - { + } else { + _buffer[_start] = item; _start = (_start + 1) % Capacity; - _buffer[(_start + _size - 1) % Capacity] = item; - } - } - else - { - // Update the last item - if (_size > 0) - { - _buffer[(_start + _size - 1) % Capacity] = item; - } - else - { - // If buffer is empty, add the item even if isNew is true - _buffer[0] = item; - _size = 1; } + } else { + // If isNew is false, just update the last item + _buffer[(_start + _size - 1) % Capacity] = item; } } - public double this[int index] - { - get - { + public double this[int index] { + get { if (index < 0 || index >= _size) throw new IndexOutOfRangeException(); return _buffer[(_start + index) % Capacity]; - } - set - { + } set { if (index < 0 || index >= _size) throw new IndexOutOfRangeException(); _buffer[(_start + index) % Capacity] = value; } } + + public Enumerator GetEnumerator() => new Enumerator(this); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public struct Enumerator : IEnumerator { + private readonly CircularBuffer _buffer; + private int _index; + private double _current; + + internal Enumerator(CircularBuffer buffer) { + _buffer = buffer; + _index = -1; + _current = default; + } + + public bool MoveNext() { + if (_index + 1 >= _buffer._size) + return false; + + _index++; + _current = _buffer[_index]; + return true; + } + + public double Current => _current; + object IEnumerator.Current => Current; + + public void Reset() { + _index = -1; + _current = default; + } + + public void Dispose() { } + } } #!csharp @@ -79,52 +97,36 @@ public class CircularBuffer public class SMA1 { private CircularBuffer buffer; - private int period; + private readonly int period; private double sum; public TValue Value { get; private set; } - public bool IsHot => buffer.Count >= period; + public bool IsHot { get; private set; } public SMA1(int period) { - Init(period); + this.period = period; + Init(); } - public void Init(int period) + public void Init() { - this.period = period; this.buffer = new CircularBuffer(period); this.sum = 0; + this.IsHot = false; this.Value = default; } public TValue Update(TValue input, bool isNew = true) { - if (!isNew) - { - if (buffer.Count == period) - { - sum -= buffer[0]; - } - sum += input.Value; - buffer.Add(input.Value, isNew); - } - else - { - if (buffer.Count > 0) - { - sum -= buffer[buffer.Count - 1]; - sum += input.Value; - buffer.Add(input.Value, isNew); - } - else - { - // If buffer is empty, add the item even if isNew is true - sum += input.Value; - buffer.Add(input.Value, false); - } + buffer.Add(input.Value, isNew); + + sum = 0; + for (int i=0; i 0 ? sum / buffer.Count : double.NaN; + IsHot = buffer.Count >= period; Value = new TValue(input.Time, sma, isNew, IsHot); return Value; } @@ -139,7 +141,7 @@ Console.WriteLine($"{"Close",10} {"MA(" + i + ")",10}"); for (int i = 0; i < 20; i++) { TValue c =(double)feed.Generate().Close; - ma.Update(1000,false); + //ma.Update(10000,false); ma.Update(-10000,false); ma.Update(c,true); From e30701c7232306fafac6b3de38b0d8c34c31b8ed Mon Sep 17 00:00:00 2001 From: Miha Kralj Date: Mon, 29 Jul 2024 10:12:49 -0700 Subject: [PATCH 5/8] Refine flow, update methods, and add quirky comments Let's make this code dance the Macarena! --- v2/test.dib | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/v2/test.dib b/v2/test.dib index ad8f5c63..80b85ff7 100644 --- a/v2/test.dib +++ b/v2/test.dib @@ -30,6 +30,8 @@ public class CircularBuffer: IEnumerable [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Add(double item, bool isNew = true) { + + //refine this flow if (_size == 0 || isNew) { // If buffer is empty or isNew is true, add new item if (_size < Capacity) { @@ -125,7 +127,7 @@ public class SMA1 sum+=buffer[i]; } - double sma = buffer.Count > 0 ? sum / buffer.Count : double.NaN; + double sma = sum / buffer.Count; IsHot = buffer.Count >= period; Value = new TValue(input.Time, sma, isNew, IsHot); return Value; @@ -140,13 +142,18 @@ SMA1 ma = new(i); Console.WriteLine($"{"Close",10} {"MA(" + i + ")",10}"); for (int i = 0; i < 20; i++) { - TValue c =(double)feed.Generate().Close; + //TValue c =(double)feed.Generate().Close; //ma.Update(10000,false); - ma.Update(-10000,false); + // ma.Update(-10000,false); - ma.Update(c,true); + //ma.Update(i,false); + ma.Update(i,false); - Console.WriteLine($"{i+1} {(double)c,10:F2} {(double)ma.Value,10:F2}"); + ma.Update(i,true); + ma.Update(10000,false); + ma.Update(i+1,false); + + Console.WriteLine($"{i+1} {(double)i+1,10:F2} {(double)ma.Value,10:F2}"); } #!csharp From 60c2331cc05c9f9388a223aba8cac5c5db934361 Mon Sep 17 00:00:00 2001 From: Miha Date: Wed, 31 Jul 2024 09:11:05 -0400 Subject: [PATCH 6/8] EMA completed --- .refactoring/test.dib | 2 +- v2/GBM_Feed.cs | 2 +- v2/Indicators/EMA.cs | 59 ++++++------- v2/Indicators/SMA.cs | 52 +++++++----- v2/Indicators/Template.cs | 40 +++++++++ v2/Indicators/WMA.cs | 2 - v2/basics/CircularBuffer.cs | 12 ++- v2/basics/TBar.cs | 2 +- v2/basics/TValue.cs | 2 +- v2/test.dib | 161 +++++++++--------------------------- 10 files changed, 159 insertions(+), 175 deletions(-) create mode 100644 v2/Indicators/Template.cs diff --git a/.refactoring/test.dib b/.refactoring/test.dib index a55e8863..d2277e66 100644 --- a/.refactoring/test.dib +++ b/.refactoring/test.dib @@ -4,7 +4,7 @@ #!csharp -#r "..\v2\bin\Debug\calculations.dll" +#r "\bin\Debug\calculations.dll" using QuanTAlib; #!csharp diff --git a/v2/GBM_Feed.cs b/v2/GBM_Feed.cs index 6a203c1f..0b643838 100644 --- a/v2/GBM_Feed.cs +++ b/v2/GBM_Feed.cs @@ -1,4 +1,4 @@ -namespace QuanTAlib; +//namespace QuanTAlib; public class GBM_Feed { private readonly double _mu; diff --git a/v2/Indicators/EMA.cs b/v2/Indicators/EMA.cs index 3448461a..536baa35 100644 --- a/v2/Indicators/EMA.cs +++ b/v2/Indicators/EMA.cs @@ -1,42 +1,45 @@ -namespace QuanTAlib; - public class EMA { - private double lastEma, lastEmaCandidate, k; - private int period, i; + private readonly int period; + private int index; public TValue Value { get; private set; } - public bool IsHot { get; private set; } + public bool IsHot => index > period; + public int Period => Math.Min(index, period); + private double k; + private double lastEMA, lastEMAcandidate; - public EMA(int period) - { - Init(period); + public EMA(int Period) { + this.period = Period; + Init(); } - public void Init(int period) - { - this.period = period; + public void Init() { + this.Value = default; + this.index = 0; this.k = 2.0 / (period + 1); - this.lastEma = this.lastEmaCandidate = double.NaN; - this.i = 0; + this.lastEMA = 0; + this.lastEMAcandidate = 0; } - public TValue Update(TValue input, bool IsNew = true) - { - double ema; - if (double.IsNaN(lastEma)) { lastEma = input.Value; } - - if (IsNew) - { - lastEma = lastEmaCandidate; - i++; + public TValue Update(TValue Input, bool IsNew = true) { + double ma; + if (double.IsNaN(Input.Value) || double.IsInfinity(Input.Value)) { + return new TValue(Input.Time, lastEMA, IsNew, index > period); + } + if (IsNew) { + if (index<1) { lastEMA = Input.Value; } + lastEMAcandidate = lastEMA; + index++; + } else { + if (index<=1) { lastEMAcandidate = Input.Value; } + lastEMA = lastEMAcandidate; } - double kk = (i < period) ? (2.0 / (i + 1)) : k; - ema = lastEma + kk * (input.Value - lastEma); - lastEmaCandidate = ema; + double kk = (index <= period) ? (2.0 / (index+1)) : k; + ma = (Input.Value - lastEMA) * kk + lastEMA; + lastEMA = ma; - IsHot = i >= period; - Value = new TValue(input.Time, ema, IsNew, IsHot); - return Value; + this.Value = new TValue(Input.Time, ma, IsNew, index > period); + return this.Value; } } \ No newline at end of file diff --git a/v2/Indicators/SMA.cs b/v2/Indicators/SMA.cs index 15366e16..490753cb 100644 --- a/v2/Indicators/SMA.cs +++ b/v2/Indicators/SMA.cs @@ -1,41 +1,55 @@ -namespace QuanTAlib; - public class SMA { - private CircularBuffer buffer = null!; - private int period; - private double sum; + public CircularBuffer buffer = null!; + private readonly int period; + public double sum; public TValue Value { get; private set; } public bool IsHot { get; private set; } public SMA(int period) { - Init(period); + this.period = period; + Init(); } - public void Init(int period) + public void Init() { - this.period = period; this.buffer = new CircularBuffer(period); this.sum = 0; this.IsHot = false; this.Value = default; } - public TValue Update(TValue input, bool IsNew = true) + public TValue Update(TValue input, bool isNew = true) { - buffer.Add(input.value, IsNew); - - //calculate rolling sum - - double sma = sum / buffer.Count; - Value = new TValue(input.Time, sma, isNew, IsHot); - return Value; + if (buffer.Count == 0) + { + // If buffer is empty, always add the value regardless of isNew + buffer.Add(input.Value, true); + sum = input.Value; + } + else if (isNew && buffer.Count == buffer.Capacity) + { + // If buffer is full and it's a new value, remove oldest + sum -= buffer[0]; + buffer.Add(input.Value, true); + sum += input.Value; + } + else + { + // If it's not new, or if buffer isn't full yet + if (!isNew) + { + // Remove the last value if we're updating + sum -= buffer[buffer.Count - 1]; + } + buffer.Add(input.Value, isNew); + sum += input.Value; } - double sma = buffer.Count > 0 ? sum / buffer.Count : double.NaN; + double sma = sum / buffer.Count; IsHot = buffer.Count >= period; - Value = new TValue(input.Time, sma, IsNew, IsHot); + Value = new TValue(input.Time, sma, isNew, IsHot); return Value; } -} +} \ No newline at end of file diff --git a/v2/Indicators/Template.cs b/v2/Indicators/Template.cs new file mode 100644 index 00000000..1f070f53 --- /dev/null +++ b/v2/Indicators/Template.cs @@ -0,0 +1,40 @@ +public class Template +{ + private CircularBuffer buffer = null!; + private readonly int period; + private int index; + public TValue Value { get; private set; } + public bool IsHot { get; private set; } + + public Template(int Period) { + this.period = Period; + Init(); + } + + public void Init() { + this.buffer = new CircularBuffer(period); + this.IsHot = false; + this.Value = default; + this.index = 0; + } + + public TValue Update(TValue Input, bool IsNew = true) { + this.buffer.Add(Input,IsNew); + if (this.index == 0) { + if (IsNew) { this.index++; } + this.Value = new TValue(Input.Time, Input.Value, IsNew, true); + return this.Value; + } + + if (IsNew) { + // starting a new bar, fresh calc + index++; + } else { + // updating existing bar, recalc + } + double ma = Input.Value; + + this.Value = new TValue(Input.Time, ma, IsNew, buffer.Count >= period); + return this.Value; + } +} \ No newline at end of file diff --git a/v2/Indicators/WMA.cs b/v2/Indicators/WMA.cs index f938fab9..f7673f7d 100644 --- a/v2/Indicators/WMA.cs +++ b/v2/Indicators/WMA.cs @@ -1,5 +1,3 @@ -namespace QuanTAlib; - public class WMA { private CircularBuffer buffer = null!; diff --git a/v2/basics/CircularBuffer.cs b/v2/basics/CircularBuffer.cs index 485913c1..cc457009 100644 --- a/v2/basics/CircularBuffer.cs +++ b/v2/basics/CircularBuffer.cs @@ -1,4 +1,7 @@ -namespace QuanTAlib; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Runtime.CompilerServices; public class CircularBuffer: IEnumerable { @@ -30,7 +33,12 @@ public class CircularBuffer: IEnumerable } } else { // If isNew is false, just update the last item - _buffer[(_start + _size - 1) % Capacity] = item; + if (_size > 0) { + _buffer[(_start + _size - 1) % Capacity] = item; + } else { + _buffer[_start] = item; + _size = 1; + } } } diff --git a/v2/basics/TBar.cs b/v2/basics/TBar.cs index bb98d683..d5ea9a81 100644 --- a/v2/basics/TBar.cs +++ b/v2/basics/TBar.cs @@ -1,4 +1,4 @@ -namespace QuanTAlib; +//namespace QuanTAlib; public readonly record struct TBar(DateTime Time, double Open, double High, double Low, double Close, double Volume, bool IsNew = true) { public DateTime Time { get; init; } = Time; diff --git a/v2/basics/TValue.cs b/v2/basics/TValue.cs index 83a8efd7..49c2a6ed 100644 --- a/v2/basics/TValue.cs +++ b/v2/basics/TValue.cs @@ -1,4 +1,4 @@ -namespace QuanTAlib; +//namespace QuanTAlib; public readonly record struct TValue(DateTime Time, double Value, bool IsNew = true, bool IsHot = true) { diff --git a/v2/test.dib b/v2/test.dib index 80b85ff7..bde0d059 100644 --- a/v2/test.dib +++ b/v2/test.dib @@ -4,156 +4,77 @@ #!csharp -#r ".\bin\Debug\calculations.dll" -using QuanTAlib; - -#!csharp - +//#r "./bin/Debug/calculations.dll" +using System; +using System.Collections; +using System.Collections.Generic; using System.Runtime.CompilerServices; -public class CircularBuffer: IEnumerable -{ - private double[] _buffer = null!; - private int _start; - private int _size; +#!csharp - public int Capacity => _buffer.Length; - public int Count => _size; - - public CircularBuffer(int capacity) - { - - _buffer = new double[capacity]; - _start = 0; - _size = 0; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Add(double item, bool isNew = true) { - - //refine this flow - if (_size == 0 || isNew) { - // If buffer is empty or isNew is true, add new item - if (_size < Capacity) { - _buffer[(_start + _size) % Capacity] = item; - _size++; - } else { - _buffer[_start] = item; - _start = (_start + 1) % Capacity; - } - } else { - // If isNew is false, just update the last item - _buffer[(_start + _size - 1) % Capacity] = item; - } - } - - public double this[int index] { - get { - if (index < 0 || index >= _size) - throw new IndexOutOfRangeException(); - return _buffer[(_start + index) % Capacity]; - } set { - if (index < 0 || index >= _size) - throw new IndexOutOfRangeException(); - _buffer[(_start + index) % Capacity] = value; - } - } - - public Enumerator GetEnumerator() => new Enumerator(this); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public struct Enumerator : IEnumerator { - private readonly CircularBuffer _buffer; - private int _index; - private double _current; - - internal Enumerator(CircularBuffer buffer) { - _buffer = buffer; - _index = -1; - _current = default; - } - - public bool MoveNext() { - if (_index + 1 >= _buffer._size) - return false; - - _index++; - _current = _buffer[_index]; - return true; - } - - public double Current => _current; - object IEnumerator.Current => Current; - - public void Reset() { - _index = -1; - _current = default; - } - - public void Dispose() { } - } -} +#load "./basics/CircularBuffer.cs" +#load "./basics/TValue.cs" +#load "./basics/TBar.cs" +#load "./Indicators/SMA.cs" +#load "./Indicators/EMA.cs" +#load "./GBM_Feed.cs" #!csharp -public class SMA1 +public class Template { - private CircularBuffer buffer; + private CircularBuffer buffer = null!; private readonly int period; - private double sum; + private int index; public TValue Value { get; private set; } public bool IsHot { get; private set; } - public SMA1(int period) - { - this.period = period; + public Template(int Period) { + this.period = Period; Init(); } - public void Init() - { + public void Init() { this.buffer = new CircularBuffer(period); - this.sum = 0; this.IsHot = false; this.Value = default; + this.index = 0; } - public TValue Update(TValue input, bool isNew = true) - { - buffer.Add(input.Value, isNew); - - sum = 0; - for (int i=0; i= period; - Value = new TValue(input.Time, sma, isNew, IsHot); - return Value; + if (IsNew) { + // starting a new bar, fresh calc + index++; + } else { + // updating existing bar, recalc + } + double ma = Input.Value; + + this.Value = new TValue(Input.Time, ma, IsNew, index > period); + return this.Value; } } #!csharp -GBM_Feed feed = new(initialPrice: 100, mu: 0.1, sigma: 0.9); -int i=10; -SMA1 ma = new(i); -Console.WriteLine($"{"Close",10} {"MA(" + i + ")",10}"); -for (int i = 0; i < 20; i++) +EMA ma = new(3); +Console.WriteLine($"{"Close",5} {"MA()",10}"); +for (int i = 1; i < 10; i=i+1) { - //TValue c =(double)feed.Generate().Close; - //ma.Update(10000,false); - // ma.Update(-10000,false); - - //ma.Update(i,false); + //ma.Update(100,true); + ma.Update(10,true); ma.Update(i,false); - ma.Update(i,true); - ma.Update(10000,false); - ma.Update(i+1,false); - Console.WriteLine($"{i+1} {(double)i+1,10:F2} {(double)ma.Value,10:F2}"); + Console.WriteLine($"{i} {(double)ma.Value,10:F2} {ma.Value.IsNew}"); } #!csharp From f1378e82ba729d40f7c2af2ff8d9c684d4ca23c5 Mon Sep 17 00:00:00 2001 From: Miha Date: Wed, 31 Jul 2024 17:57:52 -0700 Subject: [PATCH 7/8] refinement --- .github/workflows/main_automation.yml | 4 +- QuanTAlib.sln | 88 ++------- v2/Indicators/EMA.cs | 90 ++++++--- v2/Indicators/SMA.cs | 107 ++++++---- v2/basics/TValue.cs | 1 + v2/test.dib | 271 ++++++++++++++++++++++---- 6 files changed, 382 insertions(+), 179 deletions(-) diff --git a/.github/workflows/main_automation.yml b/.github/workflows/main_automation.yml index 2130d485..12fac48c 100644 --- a/.github/workflows/main_automation.yml +++ b/.github/workflows/main_automation.yml @@ -3,10 +3,10 @@ on: workflow_dispatch: push: branches: - - '*' + - main pull_request: branches: - - '*' + - main jobs: build_test: diff --git a/QuanTAlib.sln b/QuanTAlib.sln index d0b6ce59..ab71a1e8 100644 --- a/QuanTAlib.sln +++ b/QuanTAlib.sln @@ -1,74 +1,24 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.2.32210.308 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calculations", "Calculations\Calculations.csproj", "{AAE21F8A-9BC2-4647-A9EB-4DC86C569080}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tests", "Tests\Tests.csproj", "{283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Indicators", "Indicators\Indicators.csproj", "{43AD2D78-024C-4D96-A70B-915CF519965A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strategies", "Strategies\Strategies.csproj", "{FA526AF6-95BC-4AC0-8B46-A304FD06689D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{47B6ACDB-F535-4FEB-9A0A-C427CAE8C28E}" - ProjectSection(SolutionItems) = preProject - docs\.nojekyll = docs\.nojekyll - docs\ALMA.md = docs\ALMA.md - docs\DEMA.md = docs\DEMA.md - docs\DWMA.md = docs\DWMA.md - docs\EMA.md = docs\EMA.md - docs\FMA.md = docs\FMA.md - docs\getting_started.ipynb = docs\getting_started.ipynb - docs\HEMA.md = docs\HEMA.md - docs\HMA.md = docs\HMA.md - docs\HWMA.md = docs\HWMA.md - docs\index.html = docs\index.html - docs\indicators.md = docs\indicators.md - docs\JMA.md = docs\JMA.md - docs\KAMA.md = docs\KAMA.md - docs\LICENSE = docs\LICENSE - docs\MAMA.md = docs\MAMA.md - docs\QA.md = docs\QA.md - docs\readme.md = docs\readme.md - docs\RMA.md = docs\RMA.md - docs\SMA.md = docs\SMA.md - docs\SMMA.md = docs\SMMA.md - docs\T3.md = docs\T3.md - docs\TEMA.md = docs\TEMA.md - docs\TRIMA.md = docs\TRIMA.md - docs\WMA.md = docs\WMA.md - docs\ZLEMA.md = docs\ZLEMA.md - docs\_sidebar.md = docs\_sidebar.md - EndProjectSection +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calculations", "v2\calculations.csproj", "{AAE21F8A-9BC2-4647-A9EB-4DC86C569080}" EndProject Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Release|Any CPU.Build.0 = Release|Any CPU - {283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {283EACC9-3AF6-4DAE-9C1C-0F7F8C8CD70D}.Release|Any CPU.Build.0 = Release|Any CPU - {43AD2D78-024C-4D96-A70B-915CF519965A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {43AD2D78-024C-4D96-A70B-915CF519965A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {43AD2D78-024C-4D96-A70B-915CF519965A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {43AD2D78-024C-4D96-A70B-915CF519965A}.Release|Any CPU.Build.0 = Release|Any CPU - {FA526AF6-95BC-4AC0-8B46-A304FD06689D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FA526AF6-95BC-4AC0-8B46-A304FD06689D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FA526AF6-95BC-4AC0-8B46-A304FD06689D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FA526AF6-95BC-4AC0-8B46-A304FD06689D}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {E5592DC2-0542-45B2-A0CF-C6B1EDC72B87} - EndGlobalSection -EndGlobal + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAE21F8A-9BC2-4647-A9EB-4DC86C569080}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E5592DC2-0542-45B2-A0CF-C6B1EDC72B87} + EndGlobalSection +EndGlobal \ No newline at end of file diff --git a/v2/Indicators/EMA.cs b/v2/Indicators/EMA.cs index 536baa35..da9699fc 100644 --- a/v2/Indicators/EMA.cs +++ b/v2/Indicators/EMA.cs @@ -1,45 +1,71 @@ public class EMA { - private readonly int period; - private int index; + private readonly int _period; + private int _index, _hotIndex; public TValue Value { get; private set; } - public bool IsHot => index > period; - public int Period => Math.Min(index, period); - private double k; - private double lastEMA, lastEMAcandidate; + public bool IsHot => _index > _period; + public int Period => Math.Min(_index, _period); + private double _k; + private double _lastEMA, _lastEMACandidate; - public EMA(int Period) { - this.period = Period; + public EMA(int period) { + _period = period; Init(); } - public void Init() { - this.Value = default; - this.index = 0; - this.k = 2.0 / (period + 1); - this.lastEMA = 0; - this.lastEMAcandidate = 0; - } - - public TValue Update(TValue Input, bool IsNew = true) { - double ma; - if (double.IsNaN(Input.Value) || double.IsInfinity(Input.Value)) { - return new TValue(Input.Time, lastEMA, IsNew, index > period); - } - if (IsNew) { - if (index<1) { lastEMA = Input.Value; } - lastEMAcandidate = lastEMA; - index++; + public EMA(object source, int period) : this(period) { + var sourceType = source.GetType(); + var updateMethod = sourceType.GetMethod("Update", new[] { typeof(TValue) }); + if (updateMethod != null) { + var pubEvent = sourceType.GetEvent("Pub"); + if (pubEvent != null && pubEvent.EventHandlerType == typeof(Signal)) { + pubEvent.AddEventHandler(source, new Signal(Sub)); + } else { + throw new ArgumentException("Source object must have a Pub event of type NewValue."); + } } else { - if (index<=1) { lastEMAcandidate = Input.Value; } - lastEMA = lastEMAcandidate; + throw new ArgumentException("Source object must have an Update(TValue) method."); + } + } + + public void Init() { + Value = default; + _index = _hotIndex = 0; + _k = 2.0 / (_period + 1); + _lastEMA = 0; + _lastEMACandidate = 0; + } + + public TValue Update(TValue input) { + if (!input.IsHot && input.IsNew) { _hotIndex++; } + + if (double.IsNaN(input.Value) || double.IsInfinity(input.Value)) { + Value = new TValue(input.Time, _lastEMA, input.IsNew, _index > _period); + Pub?.Invoke(this, Value); + return Value; + } + if (input.IsNew) { + if (_index < 1) { _lastEMA = input.Value; } + _lastEMACandidate = _lastEMA; + _index++; + } else { + if (_index <= 1) { _lastEMACandidate = input.Value; } + _lastEMA = _lastEMACandidate; } - double kk = (index <= period) ? (2.0 / (index+1)) : k; - ma = (Input.Value - lastEMA) * kk + lastEMA; - lastEMA = ma; + double kk = (_index <= _period) ? (2.0 / (_index + 1)) : _k; - this.Value = new TValue(Input.Time, ma, IsNew, index > period); - return this.Value; + double ema = (input.Value - _lastEMA) * kk + _lastEMA; + _lastEMA = ema; + + Value = new TValue(input.Time, ema, input.IsNew, _index > (_period + _hotIndex)); + Pub?.Invoke(this, Value); + return Value; } + + public void Sub(object source, TValue arg) { + Update(arg); + } + + public event Signal Pub; } \ No newline at end of file diff --git a/v2/Indicators/SMA.cs b/v2/Indicators/SMA.cs index 490753cb..a4da6232 100644 --- a/v2/Indicators/SMA.cs +++ b/v2/Indicators/SMA.cs @@ -1,55 +1,80 @@ + public class SMA { - public CircularBuffer buffer = null!; - private readonly int period; - public double sum; + private readonly int _period; + private int _index, _hotIndex; public TValue Value { get; private set; } - public bool IsHot { get; private set; } + public bool IsHot => _index >= _period; + public int Period => Math.Min(_index, _period); + private double _sum; + private double _lastValidSMA; + private CircularBuffer _buffer; + private double _lastAddedValue; - public SMA(int period) - { - this.period = period; + public SMA(int period) { + _period = period; Init(); } - public void Init() - { - this.buffer = new CircularBuffer(period); - this.sum = 0; - this.IsHot = false; - this.Value = default; + public SMA(object source, int period) : this(period) { + var sourceType = source.GetType(); + var updateMethod = sourceType.GetMethod("Update", new[] { typeof(TValue) }); + + if (updateMethod != null) { + var pubEvent = sourceType.GetEvent("Pub"); + if (pubEvent != null && pubEvent.EventHandlerType == typeof(Signal)) { + pubEvent.AddEventHandler(source, new Signal(Sub)); + } else { + throw new ArgumentException("Source object must have a Pub event of type NewValue."); + } + } else { + throw new ArgumentException("Source object must have an Update(TValue) method."); + } } - public TValue Update(TValue input, bool isNew = true) - { - if (buffer.Count == 0) - { - // If buffer is empty, always add the value regardless of isNew - buffer.Add(input.Value, true); - sum = input.Value; - } - else if (isNew && buffer.Count == buffer.Capacity) - { - // If buffer is full and it's a new value, remove oldest - sum -= buffer[0]; - buffer.Add(input.Value, true); - sum += input.Value; - } - else - { - // If it's not new, or if buffer isn't full yet - if (!isNew) - { - // Remove the last value if we're updating - sum -= buffer[buffer.Count - 1]; - } - buffer.Add(input.Value, isNew); - sum += input.Value; + public void Init() { + _buffer = new CircularBuffer(_period); + _sum = 0; + _lastValidSMA = 0; + Value = default; + _index = _hotIndex = 0; + _lastAddedValue = 0; + } + + public TValue Update(TValue input) { + if (!input.IsHot && input.IsNew) { _hotIndex++; } + + if (double.IsNaN(input.Value) || double.IsInfinity(input.Value)) { + Value = new TValue(input.Time, _lastValidSMA, input.IsNew, _index > _period); + Pub?.Invoke(this, Value); + return Value; } - double sma = sum / buffer.Count; - IsHot = buffer.Count >= period; - Value = new TValue(input.Time, sma, isNew, IsHot); + if (input.IsNew) { + if (_buffer.Count == _buffer.Capacity) { + _sum -= _buffer[0]; + } + _buffer.Add(input.Value, true); + _sum += input.Value; + _lastAddedValue = input.Value; + _index++; + } else { + _sum = _sum - _lastAddedValue + input.Value; + _buffer[_buffer.Count - 1] = input.Value; + _lastAddedValue = input.Value; + } + + double sma = _sum / _buffer.Count; + _lastValidSMA = sma; + + Value = new TValue(input.Time, sma, input.IsNew, _index > (_period + _hotIndex)); + Pub?.Invoke(this, Value); return Value; } + + public void Sub(object source, TValue arg) { + Update(arg); + } + + public event Signal Pub; } \ No newline at end of file diff --git a/v2/basics/TValue.cs b/v2/basics/TValue.cs index 49c2a6ed..d92d2878 100644 --- a/v2/basics/TValue.cs +++ b/v2/basics/TValue.cs @@ -18,3 +18,4 @@ public readonly record struct TValue(DateTime Time, double Value, bool IsNew = t public override string ToString() => $"[{Time:yyyy-MM-dd HH:mm:ss}: {Value:F2}]"; } +public delegate void Signal(object source, TValue args); \ No newline at end of file diff --git a/v2/test.dib b/v2/test.dib index bde0d059..c70f179d 100644 --- a/v2/test.dib +++ b/v2/test.dib @@ -15,66 +15,267 @@ using System.Runtime.CompilerServices; #load "./basics/CircularBuffer.cs" #load "./basics/TValue.cs" #load "./basics/TBar.cs" -#load "./Indicators/SMA.cs" -#load "./Indicators/EMA.cs" +//#load "./Indicators/SMA.cs" +//#load "./Indicators/EMA.cs" #load "./GBM_Feed.cs" #!csharp +public delegate void Signal(object source, TValue args); + +#!csharp + public class Template { - private CircularBuffer buffer = null!; - private readonly int period; - private int index; + private CircularBuffer _buffer = null!; + private readonly int _period; + private int _index, _hotIndex; public TValue Value { get; private set; } - public bool IsHot { get; private set; } + public bool IsHot => _index >= _period; + public int Period => Math.Min(_index, _period); + private double _sum; + private double _lastValidValue; + private double _lastAddedValue; - public Template(int Period) { - this.period = Period; + public Template(int period) { + _period = period; Init(); } - public void Init() { - this.buffer = new CircularBuffer(period); - this.IsHot = false; - this.Value = default; - this.index = 0; + public Template(object source, int period) : this(period) { + var sourceType = source.GetType(); + var updateMethod = sourceType.GetMethod("Update", new[] { typeof(TValue), typeof(bool) }); + + if (updateMethod != null) { + var pubEvent = sourceType.GetEvent("Pub"); + if (pubEvent != null && pubEvent.EventHandlerType == typeof(Signal)) { + pubEvent.AddEventHandler(source, new Signal(Sub)); + } else { + throw new ArgumentException("Source object must have a Pub event of type NewValue."); + } + } else { + throw new ArgumentException("Source object must have an Update(TValue, bool) method."); + } } - public TValue Update(TValue Input, bool IsNew = true) { - this.buffer.Add(Input,IsNew); - //first value - if (this.index == 0) { - if (IsNew) { this.index++; } - this.Value = new TValue(Input.Time, Input.Value, IsNew, true); - return this.Value; + public void Init() { + _buffer = new CircularBuffer(_period); + _sum = 0; + _lastValidValue = 0; + Value = default; + _index = _hotIndex = 0; + _lastAddedValue = 0; + } + + public TValue Update(TValue input, bool isNew = true) { + if (!input.IsHot && isNew) { _hotIndex++; } + + if (double.IsNaN(input.Value) || double.IsInfinity(input.Value)) { + Value = new TValue(input.Time, _lastValidValue, isNew, _index > _period); + Pub?.Invoke(this, Value); + return Value; } - if (IsNew) { - // starting a new bar, fresh calc - index++; - } else { - // updating existing bar, recalc - } - double ma = Input.Value; + if (isNew) { + if (_buffer.Count == _buffer.Capacity) { + _sum -= _buffer[0]; + } + _buffer.Add(input.Value, true); + _sum += input.Value; + _lastAddedValue = input.Value; + _index++; + } else { + _sum = _sum - _lastAddedValue + input.Value; + _buffer[_buffer.Count - 1] = input.Value; + _lastAddedValue = input.Value; + } - this.Value = new TValue(Input.Time, ma, IsNew, index > period); - return this.Value; + double result = _sum / _buffer.Count; + _lastValidValue = result; + + Value = new TValue(input.Time, result, isNew, _index > (_period + _hotIndex)); + Pub?.Invoke(this, Value); + return Value; } + + public void Sub(object source, TValue arg) { + Update(arg); + } + + public event Signal Pub; +} + +#!csharp + +//public delegate void Signal(object source, TValue args); + +public class SMA +{ + private readonly int _period; + private int _index, _hotIndex; + public TValue Value { get; private set; } + public bool IsHot => _index >= _period; + public int Period => Math.Min(_index, _period); + private double _sum; + private double _lastValidSMA; + private CircularBuffer _buffer; + private double _lastAddedValue; + + public SMA(int period) { + _period = period; + Init(); + } + + public SMA(object source, int period) : this(period) { + var sourceType = source.GetType(); + var updateMethod = sourceType.GetMethod("Update", new[] { typeof(TValue) }); + + if (updateMethod != null) { + var pubEvent = sourceType.GetEvent("Pub"); + if (pubEvent != null && pubEvent.EventHandlerType == typeof(Signal)) { + pubEvent.AddEventHandler(source, new Signal(Sub)); + } else { + throw new ArgumentException("Source object must have a Pub event of type NewValue."); + } + } else { + throw new ArgumentException("Source object must have an Update(TValue) method."); + } + } + + public void Init() { + _buffer = new CircularBuffer(_period); + _sum = 0; + _lastValidSMA = 0; + Value = default; + _index = _hotIndex = 0; + _lastAddedValue = 0; + } + + public TValue Update(TValue input) { + if (!input.IsHot && input.IsNew) { _hotIndex++; } + + if (double.IsNaN(input.Value) || double.IsInfinity(input.Value)) { + Value = new TValue(input.Time, _lastValidSMA, input.IsNew, _index > _period); + Pub?.Invoke(this, Value); + return Value; + } + + if (input.IsNew) { + if (_buffer.Count == _buffer.Capacity) { + _sum -= _buffer[0]; + } + _buffer.Add(input.Value, true); + _sum += input.Value; + _lastAddedValue = input.Value; + _index++; + } else { + _sum = _sum - _lastAddedValue + input.Value; + _buffer[_buffer.Count - 1] = input.Value; + _lastAddedValue = input.Value; + } + + double sma = _sum / _buffer.Count; + _lastValidSMA = sma; + + Value = new TValue(input.Time, sma, input.IsNew, _index > (_period + _hotIndex)); + Pub?.Invoke(this, Value); + return Value; + } + + public void Sub(object source, TValue arg) { + Update(arg); + } + + public event Signal Pub; +} + +#!csharp + +public class EMA +{ + private readonly int _period; + private int _index, _hotIndex; + public TValue Value { get; private set; } + public bool IsHot => _index > _period; + public int Period => Math.Min(_index, _period); + private double _k; + private double _lastEMA, _lastEMACandidate; + + public EMA(int period) { + _period = period; + Init(); + } + + public EMA(object source, int period) : this(period) { + var sourceType = source.GetType(); + var updateMethod = sourceType.GetMethod("Update", new[] { typeof(TValue) }); + if (updateMethod != null) { + var pubEvent = sourceType.GetEvent("Pub"); + if (pubEvent != null && pubEvent.EventHandlerType == typeof(Signal)) { + pubEvent.AddEventHandler(source, new Signal(Sub)); + } else { + throw new ArgumentException("Source object must have a Pub event of type NewValue."); + } + } else { + throw new ArgumentException("Source object must have an Update(TValue) method."); + } + } + + public void Init() { + Value = default; + _index = _hotIndex = 0; + _k = 2.0 / (_period + 1); + _lastEMA = 0; + _lastEMACandidate = 0; + } + + public TValue Update(TValue input) { + if (!input.IsHot && input.IsNew) { _hotIndex++; } + + if (double.IsNaN(input.Value) || double.IsInfinity(input.Value)) { + Value = new TValue(input.Time, _lastEMA, input.IsNew, _index > _period); + Pub?.Invoke(this, Value); + return Value; + } + if (input.IsNew) { + if (_index < 1) { _lastEMA = input.Value; } + _lastEMACandidate = _lastEMA; + _index++; + } else { + if (_index <= 1) { _lastEMACandidate = input.Value; } + _lastEMA = _lastEMACandidate; + } + + double kk = (_index <= _period) ? (2.0 / (_index + 1)) : _k; + + double ema = (input.Value - _lastEMA) * kk + _lastEMA; + _lastEMA = ema; + + Value = new TValue(input.Time, ema, input.IsNew, _index > (_period + _hotIndex)); + Pub?.Invoke(this, Value); + return Value; + } + + public void Sub(object source, TValue arg) { + Update(arg); + } + + public event Signal Pub; } #!csharp EMA ma = new(3); +SMA ma1 = new(ma, 3); Console.WriteLine($"{"Close",5} {"MA()",10}"); for (int i = 1; i < 10; i=i+1) { - //ma.Update(100,true); - ma.Update(10,true); - ma.Update(i,false); - - - Console.WriteLine($"{i} {(double)ma.Value,10:F2} {ma.Value.IsNew}"); + TValue tv = new(DateTime.Now, 1000, IsNew: true); + ma.Update(tv); + tv = new(DateTime.Now, i, IsNew: false); + ma.Update(tv); + + Console.WriteLine($"{i} {(double)ma.Value,10:F2} {(double)ma1.Value,10:F2} {ma1.Value.IsHot}"); } #!csharp From e701a128bc941e57ff0a20928eed9196a56d1a44 Mon Sep 17 00:00:00 2001 From: Miha Kralj <31756078+mihakralj@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:34:46 -0700 Subject: [PATCH 8/8] Create SonarCloud.yml --- .github/workflow/SonarCloud.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflow/SonarCloud.yml diff --git a/.github/workflow/SonarCloud.yml b/.github/workflow/SonarCloud.yml new file mode 100644 index 00000000..93975f32 --- /dev/null +++ b/.github/workflow/SonarCloud.yml @@ -0,0 +1,30 @@ +name: SonarCloud analysis +on: + push: + pull_request: + workflow_dispatch: + +permissions: + pull-requests: read # allows SonarCloud to decorate PRs with analysis results + +jobs: + Analysis: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + + - name: Analyze with SonarCloud + uses: SonarSource/sonarcloud-github-action@v2.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # Generate a token on Sonarcloud.io, add it to the secrets of this repo with the name SONAR_TOKEN + with: + # Additional arguments for the SonarScanner CLI + args: > + -Dsonar.projectKey=mihakralj_QuanTAlib + -Dsonar.organization=mihakralj + -Dsonar.sources=. + -Dsonar.verbose=false