From 4edcb31a7baff9a19fcb1a2d2c780364b7277d77 Mon Sep 17 00:00:00 2001 From: David Brazda Date: Tue, 25 Apr 2023 17:31:04 +0200 Subject: [PATCH] ready2live test --- v2realbot/ENTRY_backtest_strategyVykladaci.py | 56 +++++++++++++----- v2realbot/__pycache__/config.cpython-310.pyc | Bin 2779 -> 2779 bytes .../__pycache__/backtester.cpython-310.pyc | Bin 18755 -> 19082 bytes v2realbot/backtesting/backtester.py | 24 +++++++- v2realbot/config.py | 6 +- .../strategy/StrategyOrderLimitVykladaci.py | 29 ++++++++- ...trategyOrderLimitVykladaci.cpython-310.pyc | Bin 7296 -> 8094 bytes 7 files changed, 90 insertions(+), 25 deletions(-) diff --git a/v2realbot/ENTRY_backtest_strategyVykladaci.py b/v2realbot/ENTRY_backtest_strategyVykladaci.py index de54ef1..6e06116 100644 --- a/v2realbot/ENTRY_backtest_strategyVykladaci.py +++ b/v2realbot/ENTRY_backtest_strategyVykladaci.py @@ -33,10 +33,12 @@ Do budoucna vice ridit nakupy pri klesani - napr. vyložení jen 2-3 pozic a dal # """ stratvars = AttributeDict(maxpozic = 400, + def_mode_from = 200, chunk = 10, MA = 2, Trend = 2, profit = 0.02, + def_profit = 0.01, lastbuyindex=-6, pendingbuys={}, limitka = None, @@ -93,6 +95,27 @@ def next(data, state: StrategyState): #ic(state.vars) #ic(data) + # + def is_defensive_mode(): + akt_pozic = int(state.positions) + max_pozic = int(state.vars.maxpozic) + def_mode_from = safe_get(state.vars, "def_mode_from") + if def_mode_from == None: def_mode_from = max_pozic/2 + if akt_pozic >= int(def_mode_from): + state.ilog(e=f"DEFENSIVE mode ACTIVE {state.vars.def_mode_from=}", msg=state.positions) + return True + else: + state.ilog(e=f"STANDARD mode ACTIVE {state.vars.def_mode_from=}", msg=state.positions) + return False + + def get_limitka_price(): + def_profit = safe_get(state.vars, "def_profit") + if def_profit == None: def_profit = state.vars.profit + if is_defensive_mode(): + return price2dec(float(state.avgp)+float(def_profit)) + else: + return price2dec(float(state.avgp)+float(state.vars.profit)) + #mozna presunout o level vys def vyloz(): ##prvni se vyklada na aktualni cenu, další jdou podle krivky, nula v krivce zvyšuje množství pro následující iteraci @@ -104,25 +127,22 @@ def next(data, state: StrategyState): vykladka = state.vars.vykladka #kolik muzu max vylozit kolikmuzu = int((int(state.vars.maxpozic) - int(state.positions))/int(state.vars.chunk)) - akt_pozic = int((int(state.positions))/int(state.vars.chunk)) #20 - max_pozic = int((int(state.vars.maxpozic))/int(state.vars.chunk)) #40 + akt_pozic = int(state.positions) + max_pozic = int(state.vars.maxpozic) #mame polovinu a vic vylozeno, pouzivame defenzicni krivku - if (akt_pozic >= max_pozic/2): - state.ilog(e="DEF: Mame pul a vic vylozeno, pouzivame defenzivni krivku", akt_pozic=akt_pozic, max_pozic=max_pozic, curve_def=curve_def) + if is_defensive_mode(): + state.ilog(e="DEF: Pouzivame defenzivni krivku", akt_pozic=akt_pozic, max_pozic=max_pozic, curve_def=curve_def) curve = curve_def #zaroven docasne menime ticks2reset na defenzivni 0.06 state.vars.ticks2reset = 0.06 state.ilog(e="DEF: Menime tick2reset na 0.06", ticks2reset=state.vars.ticks2reset, ticks2reset_backup=state.vars.ticks2reset_backup) - defense = True else: - defense = False #vracime zpet, pokud bylo zmeneno if state.vars.ticks2reset != state.vars.ticks2reset_backup: state.vars.ticks2reset = state.vars.ticks2reset_backup state.ilog(e="DEF: Menime tick2reset zpet na"+str(state.vars.ticks2reset), ticks2reset=state.vars.ticks2reset, ticks2reset_backup=state.vars.ticks2reset_backup) - if kolikmuzu < vykladka: vykladka = kolikmuzu if len(curve) < vykladka: @@ -137,9 +157,11 @@ def next(data, state: StrategyState): ##VAR - na zaklade conf. muzeme jako prvni posilat MARKET order if safe_get(state.vars, "first_buy_market") == True: #pri defenzivnim rezimu pouzivame vzdy LIMIT order - if defense: + if is_defensive_mode(): + state.ilog(e="DEF mode on, odesilame jako prvni limitku") state.buy_l(price=price, size=qty) else: + state.ilog(e="Posilame jako prvni MARKET order") state.buy(size=qty) else: state.buy_l(price=price, size=qty) @@ -277,7 +299,7 @@ def next(data, state: StrategyState): #neni limitka, ale mela by byt - vytváříme ji if int(state.positions) > 0 and state.vars.limitka is None: state.ilog(e="Limitka neni, ale mela by být.", msg=f"{state.positions=}") - price=price2dec(float(state.avgp)+state.vars.profit) + price=get_limitka_price() state.vars.limitka = asyncio.run(state.interface.sell_l(price=price, size=int(state.positions))) state.vars.limitka_price = price if state.vars.limitka == -1: @@ -287,12 +309,13 @@ def next(data, state: StrategyState): else: state.ilog(e="Vytvořena nová limitka", limitka=str(state.vars.limitka), limtka_price=state.vars.limitka_price, qty=state.positions) - elif state.vars.limitka is not None and int(state.positions) > 0 and (int(state.positions) != int(limitka_qty)): + #existuje a nesedi mnozstvi nebo cena + elif state.vars.limitka is not None and int(state.positions) > 0 and ((int(state.positions) != int(limitka_qty)) or float(state.vars.limitka_price) != float(get_limitka_price())): #limitka existuje, ale spatne mnostvi - updatujeme - state.ilog(e=f"Limitka existuje, ale spatne mnozstvi - updatujeme", msg=f"{state.positions=} {limitka_qty=}", pos=state.positions, limitka_qty=limitka_qty) + state.ilog(e=f"Limitka existuje, ale spatne mnozstvi nebo CENA - updatujeme", msg=f"{state.positions=} {limitka_qty=} {state.vars.limitka_price=}", nastavenacena=state.vars.limitka_price, spravna_cena=get_limitka_price(), pos=state.positions, limitka_qty=limitka_qty) #snad to nespadne, kdyztak pridat exception handling puvodni = state.vars.limitka - state.vars.limitka = asyncio.run(state.interface.repl(price=state.vars.limitka_price, orderid=state.vars.limitka, size=int(state.positions))) + state.vars.limitka = asyncio.run(state.interface.repl(price=get_limitka_price(), orderid=state.vars.limitka, size=int(state.positions))) if state.vars.limitka == -1: state.ilog(e="Replace limitky neprobehl, vracime puvodni", msg=f"{state.vars.limitka=}", puvodni=puvodni) @@ -334,7 +357,7 @@ def next(data, state: StrategyState): #HLAVNI ITERACNI LOG JESTE PRED AKCI - obsahuje aktualni hodnoty vetsiny parametru lp = state.interface.get_last_price(symbol=state.symbol) - state.ilog(e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),2)} profit:{round(float(state.profit),2)} Trades:{len(state.tradeList)}", last_price=lp, stratvars=state.vars) + state.ilog(e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),2)} profit:{round(float(state.profit),2)} Trades:{len(state.tradeList)} DEF:{str(is_defensive_mode())}", last_price=lp, stratvars=state.vars) #maxSlopeMA = -0.03 #SLOPE ANGLE PROTECTIONs @@ -362,10 +385,11 @@ def next(data, state: StrategyState): if state.vars.jevylozeno == 0: print("Neni vylozeno, muzeme testovat nakup") + #pokud je defenziva, buy triggeruje defenzivni def_trend + #TBD + + if isfalling(state.indicators.ema,state.vars.Trend) and slope > minimum_slope: - print("BUY MARKET") - #zatim vykladame full - #positions = int(int(state.vars.maxpozic)/int(state.vars.chunk)) vyloz() ## testuje aktualni cenu od nejvyssi visici limitky diff --git a/v2realbot/__pycache__/config.cpython-310.pyc b/v2realbot/__pycache__/config.cpython-310.pyc index 8a9750e399396c9009c25826d2ceac96315aa091..31817bd9ae204cc6d702ae6d4cdbf6a3eb35f4d6 100644 GIT binary patch delta 192 zcmW-byA8rH00r$}aQGM!lJNht1QS4m1X7TYUP1K&5)C6zkOi^=D`XZbS{gQ>#BkkR zbYK;GN!Y-!lVOFwvPL+MOwcXpX YWAESIGLPk}m@jl+s(8KG=X*DGz9pwFC;$Ke delta 193 zcmW-bJrcn{07iFZ2}u?~692!K&}yPJBQs{cM)QRkjUy=J0t%(Z6>^-k8aGf{UiV)0 zil7MeS!+ia?vI%Jn?76=>PJEQDbYx6R<9Dxnpg|g=wyPMWCsxgbQz+T%2OW$Mi{b-k*Q-UO#XGs9%k%gUNL#W@b=lb@!K+E Y@84dY$X8h=`O+Rp6|c5QzN==XFB_08*#H0l diff --git a/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc b/v2realbot/backtesting/__pycache__/backtester.cpython-310.pyc index cbb92f7931e4ac508e044512040bc6ff8cc12f20..c64b33f3dece88777fe68672289e34aeb0f83180 100644 GIT binary patch delta 1225 zcmah{ZA@EL7{2ehx3{G$?QI8irKJ=Y3T}}PX9Qv=p)IV)$0jvy=;+v9*tp$*wP$my z=YrZUpb5#saWO_nitdN;V`}aXH8Jsr|J3ZKCPtI}GBF{>MYF&2^sbVy#CT5LbKd8D z-sd?dC+FLBc=HCh7pkkBk{E|S9>VW#eD2=xK$d^xX$uAz=yvMRt1=|}HWI^5olGu3 zTsnD~|KXWy;>?xh%#QL?l3{0dx>xkAvnnJ3$xwuL56^4uaGl@OdOct7(&zcMcEI}u za~iIB4-L?2-6OQ$@HTHpYb|qKhivU`Us0*q<-FuQ06zYqx2tQj?0j)^zE7N}P@7(# zA)N^vM%4oq^)j^p8AUL|+~tebHqWb6LvH~dMkKH74R zze&~1M87y|V1@WCNZt9bH#B4iP{9*0Q}_NvR>r6=(PT7JBN0{EpZ zFaoq(J5FQol1(CTy@gNEcm)S7JV_H39J26>bg+UOXlJPlUlPi&m7Nq>Bkk0OaEduG zy+(`(J)$S^6jMsW%Q(FScpA?X#f#`|aj5`^ID@j7h_iSW&utK#Lme|>KVR7|;5;ti zqL>#qY?xhh8BLaDj;W<#%aK}+>i;?}|F>oHs#JQGj$jTi6$l;0DZE@DTLfRmD@Acl zqou;1dWgUJ5I=-@OCQ5m%K8;pA+rP-8bwC|RAWCKD~IOA<^F z3sy%Ei`KuZnvmMB<#WzRWyI%li|41W<|3!3&ty#fP{QV~*&)k4N3#pFbF?qtz&q5C z9L+a!j~Z@@=0gLi^k8yiBpn+cizidb@v-#4^C!|H$qEzUA-CV>aB2L`;04$=$A(e>zw`Hxl>PzF C<7&kK delta 957 zcmbu7T}V_x6vywG-MxEV*4@49$F?7qsQ8gqDne=a)vBv$iCC1H*~cmCTAQw;_Axhw zUD5)1t*O2`_5v&pMhqjik3D+B6pDJR%&CWC^Sj^6a2gLY{@NV zkKAHbH0_jhE3?uJ;o~d~iC5CiBAP_BP#4O4O3fpYoBXb;+WB$==n7wPm8#E~O}B@g z)JxMfr+~cTh3bwX53|of#Iv!1ILY4Ff?rlkA(P)zD?E!y_Q+zmMhtGI7R@(Aguj!2 zSG~}|6?bh`L0A?8sZjt4E!`3`&%tOf?{x=Z%2;%-nd=nD1qHH6Hkg5|%IRdvOkChF zIlu;TN(zx7k0>HnOi8!xuX|0l8_S+5fIh?O4H7`3-M<8I!}uAflwrxp&tD_3YWNBt znn2}mO1?p{@pLN!lyhHM$eAmiAVbI`H2TqM43|~QljZRWJd^<4OOx4Qk@;x3wg-Ed z4SQ#a?x#Vm3Hz8i-i${Qfcz0X&8#u?O%tp1_j}1QAc+X%Pq4;~6}Q z8V-qlZoz`*XYG0*MGP}JNt_hO3n`@hAIO{;Dc(Xu7{=i!p{>}1BT)s(}Jlc^t*)-9Dx z84s%ZEqtNo8Tn0&gS$Tf1dMI9Zh&39p>7^pjrVm&0eX04!za+Vt8olEjcbjMfOiE4 zA(y`hzU5DvqH;YE&%A8ZHa7#GYl*>jo)vlrzxd}+d4}DQMz&;H3w(htcfjFsD|RO@ SYCR8MjoYmU0KV{*w)h_|*A=k< diff --git a/v2realbot/backtesting/backtester.py b/v2realbot/backtesting/backtester.py index 9852c0a..7f5cd8b 100644 --- a/v2realbot/backtesting/backtester.py +++ b/v2realbot/backtesting/backtester.py @@ -251,7 +251,11 @@ class Backtester: #(1679081919.381649, 27.88) #ic(i) fill_time = i[0] - fill_price = i[1] + + #v BT nikdy nekoupime za lepsi cenu nez LIMIT price (TBD mozna zmenit na parametr) + fill_price = o.limit_price + #fill_price = i[1] + print("FILL LIMIT BUY at", fill_time, datetime.fromtimestamp(fill_time).astimezone(zoneNY), "at",i[1]) if BT_FILL_LOG_SURROUNDING_TRADES != 0: #TODO loguru @@ -281,7 +285,13 @@ class Backtester: #(1679081919.381649, 27.88) #ic(i) fill_time = i[0] - fill_price = i[1] + + #support pomaleho plneni + #v BT nikdy neprodame za lepsi cenu nez LIMIT price (TBD mozna zmenit na parametr) + fill_price = o.limit_price + + + #fill_price = i[1] print("FILL LIMIT SELL at", fill_time, datetime.fromtimestamp(fill_time).astimezone(zoneNY), "at",i[1]) if BT_FILL_LOG_SURROUNDING_TRADES != 0: #TODO loguru @@ -572,6 +582,7 @@ class Backtester: res.append(o) return res + ##toto bude predelano na display_results(ve variantach) umozni zobrazit result jak BT tak LIVE def display_backtest_result(self, state): """ Displays backtest results chart, trades and orders with option to save the result as static HTML. @@ -755,6 +766,13 @@ class Backtester: Open orders:''' + str(len(self.open_orders))) textik7 = html.Div(''' Trades:''' + str(len(self.trades))) + textik8 = html.Div(''' + Profit:''' + str(state.profit)) + textik9 = html.Div(f"{BT_FILL_CONS_TRADES_REQUIRED=}") + textik10 = html.Div(f"{BT_FILL_LOG_SURROUNDING_TRADES=}") + textik11 = html.Div(f"{BT_FILL_CONDITION_BUY_LIMIT=}") + textik12 = html.Div(f"{BT_FILL_CONDITION_SELL_LIMIT=}") + orders_title = dcc.Markdown('## Open orders') trades_title = dcc.Markdown('## Trades') ## Define the graph @@ -867,7 +885,7 @@ class Backtester: return 'saved' ## Customize your layout - app.layout = dbc.Container([mytitle,button,saved, textik1, textik2, textik3, textik35, textik4, textik5, textik55, textik6,textik7, mygraph, trades_title, trades_table, orders_title, open_orders_table]) + app.layout = dbc.Container([mytitle,button,saved, textik1, textik2, textik3, textik35, textik4, textik5, textik55, textik6,textik7, textik8, textik9, textik10, textik11, textik12, mygraph, trades_title, trades_table, orders_title, open_orders_table]) port = 9050 print("Backtest FINSIHED"+str(self.backtest_end-self.backtest_start)) diff --git a/v2realbot/config.py b/v2realbot/config.py index 6e15f25..a272a83 100644 --- a/v2realbot/config.py +++ b/v2realbot/config.py @@ -9,14 +9,14 @@ QUIET_MODE = False #N - N consecutive trades required #not impl.yet #minimum is 1, na alpace live to vetsinou vychazi 7-8 u BAC, je to hodne podobne tomu, nez je cena překonaná pul centu. tzn. 7-8 a nebo FillCondition.SLOW -BT_FILL_CONS_TRADES_REQUIRED = 3 +BT_FILL_CONS_TRADES_REQUIRED = 2 #during bt trade execution logs X-surrounding trades of the one that triggers the fill BT_FILL_LOG_SURROUNDING_TRADES = 10 #fill condition for limit order in bt # fast - price has to be equal or bigger <= # slow - price has to be bigger < -BT_FILL_CONDITION_BUY_LIMIT = FillCondition.FAST -BT_FILL_CONDITION_SELL_LIMIT = FillCondition.FAST +BT_FILL_CONDITION_BUY_LIMIT = FillCondition.SLOW +BT_FILL_CONDITION_SELL_LIMIT = FillCondition.SLOW #backend counter of api requests COUNT_API_REQUESTS = False #stratvars that cannot be changed in gui diff --git a/v2realbot/strategy/StrategyOrderLimitVykladaci.py b/v2realbot/strategy/StrategyOrderLimitVykladaci.py index 53bac50..add4877 100644 --- a/v2realbot/strategy/StrategyOrderLimitVykladaci.py +++ b/v2realbot/strategy/StrategyOrderLimitVykladaci.py @@ -1,5 +1,5 @@ from v2realbot.strategy.base import Strategy -from v2realbot.utils.utils import parse_alpaca_timestamp, ltp, AttributeDict,trunc,price2dec, zoneNY, print, json_serial +from v2realbot.utils.utils import parse_alpaca_timestamp, ltp, AttributeDict,trunc,price2dec, zoneNY, print, json_serial, safe_get from v2realbot.utils.tlog import tlog, tlog_exception from v2realbot.enums.enums import Mode, Order, Account from alpaca.trading.models import TradeUpdate @@ -37,7 +37,8 @@ class StrategyOrderLimitVykladaci(Strategy): self.state.positions = data.position_qty if self.state.vars.limitka is None: self.state.avgp = float(data.price) - price=price2dec(float(o.filled_avg_price)+self.state.vars.profit) + #price=price2dec(float(o.filled_avg_price)+self.state.vars.profit) + price = await self.get_limitka_price() self.state.vars.limitka = await self.interface.sell_l(price=price, size=o.filled_qty) #obcas live vrati "held for orders", odchytime chybu a limitku nevytvarime - spravi to dalsi notifikace nebo konzolidace if self.state.vars.limitka == -1: @@ -49,7 +50,8 @@ class StrategyOrderLimitVykladaci(Strategy): else: #avgp, pos self.state.avgp, self.state.positions = self.state.interface.pos() - cena = price2dec(float(self.state.avgp) + float(self.state.vars.profit)) + #cena = price2dec(float(self.state.avgp) + float(self.state.vars.profit)) + cena = await self.get_limitka_price() try: puvodni = self.state.vars.limitka self.state.vars.limitka = await self.interface.repl(price=cena,orderid=self.state.vars.limitka,size=int(self.state.positions)) @@ -175,3 +177,24 @@ class StrategyOrderLimitVykladaci(Strategy): self.state.vars.jevylozeno = 0 print("cancel pending buys end") self.state.ilog(e="Dokončeno zruseni vsech pb", pb=self.state.vars.pendingbuys) + + #kopie funkci co jsou v next jen async, nejak vymyslet, aby byly jen jedny + async def is_defensive_mode(self): + akt_pozic = int(self.state.positions) + max_pozic = int(self.state.vars.maxpozic) + def_mode_from = safe_get(self.state.vars, "def_mode_from") + if def_mode_from == None: def_mode_from = max_pozic/2 + if akt_pozic >= int(def_mode_from): + self.state.ilog(e=f"DEFENSIVE MODE active {self.state.vars.def_mode_from=}", msg=self.state.positions) + return True + else: + self.state.ilog(e=f"STANDARD MODE active {self.state.vars.def_mode_from=}", msg=self.state.positions) + return False + + async def get_limitka_price(self): + def_profit = safe_get(self.state.vars, "def_profit") + if def_profit == None: def_profit = self.state.vars.profit + if await self.is_defensive_mode(): + return price2dec(float(self.state.avgp)+float(def_profit)) + else: + return price2dec(float(self.state.avgp)+float(self.state.vars.profit)) \ No newline at end of file diff --git a/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc b/v2realbot/strategy/__pycache__/StrategyOrderLimitVykladaci.cpython-310.pyc index 9a896e19bac7986a587ca98f08119ddeb659c111..ad85ab9631a72c11d2c29ac62c25732761758d6a 100644 GIT binary patch delta 3036 zcmb7G-E$jP72mrb(#o>dvaDFLWm~din+nGU#~*3ZmL#q1G@*5jon+|JBG$TVSJJLl zy}LHCkZTub=)g?crgzFr=@8OBzyk~~nPC_n7+Rhfc!A;RzL4R8nZhvq13YlgT`P%Q zctA7y?b&nA{W!mK?zy{v_U?JZOC&lZ{`=^`B}^yp7z5}JcP8fc+_)J>5|*&ybh-&M z;TonP-c_f|O`1u!+w68zW=iNZr^ij3X(30P6K=2B>t@W1c#k?+x6kYovhMVo{d~@t zlXC~m0io+~2HhcZ=+K@w^V}YHzF2UF&0!>6l5i(Z+>&s@MhA*Hg7r12XnYDAE+VF{ z1;h&M&7ES5#*x{CjYWM=q^pJO@tdL~EzEPp~jP^M)vN`I&9qbKMU z^&0vs{gL`(kMpl$Kl_!uoBQ z?nnA%@&o!yZ5rk2-?fRWd7QW%0V>ucn4mgvukfMtvfQ4GPuY89O0{VTzj!sXtBL7{ zTWVVqjL;xbL=k#7GQ;QpQ)EKP2P5xB2ep&$pq)A$iB9+B1EZxKAlw7Xwj$M0oUY05 zAX1`EbP$cv??qdY)0}KFLp$~N+Tu_2*<`2%(;UD&{f9mnKPyClbF?S+a{jmzSe^m+ zQt0zmYCaf1U0MFAeFLB&eOle&diN7%_G<*O!EVtb*5GvZ&R$!)T-~* zndW#F_L+va+=fq}s!0!B?RXBQ==VFOi?A@$Hn>wjV53a6wl^EBo0m)J$a?hO9fhx7 z01u_%`Aj8t!(mFL9*|x!tn6W%#TIXt?M6`X>cuE2ay^*>Bk~UCF57jBMH^e&93O4$o~A!HCfelx z#h69&G~G4cR$S=HB(H(h zN5sc8E|nbOf*yq_IyO;<5B;N5saJyS`*P|!3MpNsuk=iyr|5Tkh9Bck4d*!rk7O8w zh)ZxO!h}GW?pik+Ub9jb{4%Zl>Q=qR^mWH8*Z78*x^5Al)VOO%6Y#(cQG=Er92R^I z%pQlyq8jcTmL-IPsri+rO~N_~i>d9rM{lH`MQ887pDv^LY0!%Ec#cMUUs&4W?@gt! za*od^Y%`A>Kzb7F>8+Y$Vav4(4ZDsj^-aEGpZH)80G^`n_l|{}h{6%&l=`1!kZl+w z>g<~!MFB+On;?lOMXfa874lAQ0_*?)MMW))K8ypjoPyk8)gDbIfjQp3|J6+SFpFpC z$JxU(GEe`RwY^70FDed}V~S(fnOgQ5J50y6-2jPv85R=KAIb4014yFWd zgDEw8hkOg1k8K4??j!nf-}SvR;$r&;6$Jm^^1uHUhc{3V_IddL?Y3p5s`58EfPRju z3PuMqzd95w)nqc*lChkFs|382160$OXvV) zqyw}jwe@?|&;U8+!`tSfyv#a)_^<(U-UK~@H&Nvkz8CTx0CD&pw(O!Do~$>)bgX=@ zbKNKW?{{*L_YIUnMigbykQ{O`@R=KvZ)2&bEOW@x;r_8bgAZid%Egt%RdIHhUbY;^ zT6b)ct01KcF@EQl+EIrQ=jWE1yM4y1u6@~;GWr{!0f-? ziCCTtBlZ%hB=R*$R|c}#Q@mEw Pyjr1Rb<`;fL}~Ru6rs1f delta 2263 zcmZ`(OKclO7~b*j`u$ix;%Dr{X_LlH8k#m~n({~=^aUlP4Joj+SR3yqwX^GW$Lo*; zha#mC7m8Y@TsV}LLnTDvk_(3-Bu*T925E0_qP-v?BrbrMf7WSAB_ieXKmYg7|K7PZ z_5Nho42Aq0{Zqa;q5e5|BixOCxG^+-1#v2;@_N7s%0VL}huB%r!$w4o7?La*Q8~(V zq8>Bia-7L-Jz;doT}Dz)va?4|8EHAqWUro)Gt{R~&l)*7$8>(Z+sMoL4quQ9ln>}V zMz1{Fi@1}V8dO79IW?rA4PGv)-dU~`ehA5yknNqduwrS8t0gaqAel7LE%Y^sx!#Kw z)r=-BxYVqgyDG|2wU69!WrH7Z0w;G-4?FA}3B_{cch~nqhWa9#{66xCUq`#hCE+#n z7x`AWJKMITHrhalYol6Mo2#L660jDirBtrP;qR<8WilIxaak)z&#qdxbEa1L6t{ua zdCC@QJ#C(Hz2u(QgFYh<#8Ffv|A<57q8hs9hT3O2=DTpa?+$mt<#=S6WPTxTb_3A} zP6qi6#Jme@N=K7g7pYXJ?Pv>I8fzz6ahFh>eD2;)MvS58JncO!fkN_snKg* z#J$ck5IC#>C23`NPO{?Ty=V2ca7saO~ivIS*% zv4K4xN()t8*VHoAIKFLn1Dgv~i~Qm5eR~Q__=efE1*|o6o3GX_+{Ii}OjWae$5$#^ z!>XF~k_Qh`J>CN(GCOf9T3xX{jn=ZM)~lEmHUw4-h6xUg#nPyN!mca|BfskaO7jIo zz6td3zpU-1tJ+QK(ZD7O?sd6G;ND&`5w4?W$=&eKR_lKXkD+lAj125~+&9~6>a3!M z?E#7Ak7wcfn*h+%)&#j7Nswklzw-DNBk%#rmiq7!usjNI7=TUm7)UPy90!1>1HS?g z1(*hSm4eM*wpLmF_#Eh1 z$cNJY&}n*p+-&kd8t8qZqm;8FY$xU#DqV?LP1jG6SoGdhKU@=Re!jKJinB#3m36Fx z9yzmn%8|bb&rG>muUgjT-_bebwEQf2BR+%zG=og9Wj zY_FlLG|aVX1s^3p$8&QvU|2(-VmrL3S!G>mTAe-50$rz25}XvlKfX*s7Ms;I4Lh5} z5+B~OT_>UGeFhR^@Hh@8F0vsYY zll{(;9ELO7kJ$efh+AO97H1hG_{N-Ju7bqsVda`|gjwQu09F8?870vvbUPzjZLYJ1 z{~ZtpAJ;Z#Q9HWp`t? z11Ty->3^B}c~Q{C5G90v E0bT9+j{pDw