From bf7c1773bd10c88880305b9fd5b563c8d0f29695 Mon Sep 17 00:00:00 2001 From: David Brazda Date: Thu, 25 May 2023 20:17:56 +0200 Subject: [PATCH] refactor RSI SELL --- v2realbot/ENTRY_Vykladaci_RSI_MYSELL.py | 115 +++--- v2realbot/__pycache__/config.cpython-310.pyc | Bin 2847 -> 2847 bytes .../common/__pycache__/model.cpython-310.pyc | Bin 6494 -> 6498 bytes v2realbot/common/model.py | 2 +- v2realbot/config.py | 2 +- v2realbot/controller/services.py | 15 +- .../__pycache__/aggregator.cpython-310.pyc | Bin 6561 -> 6953 bytes v2realbot/loader/aggregator.py | 125 +++++-- v2realbot/loader/aggregator_old.py | 341 ++++++++++++++++++ v2realbot/static/js/archivechart.js | 214 ++++++----- v2realbot/static/js/livewebsocket.js | 89 +++-- v2realbot/static/js/utils.js | 3 + .../strategy/__pycache__/base.cpython-310.pyc | Bin 13978 -> 14184 bytes v2realbot/strategy/base.py | 53 ++- .../utils/__pycache__/utils.cpython-310.pyc | Bin 9144 -> 9396 bytes v2realbot/utils/utils.py | 8 + 16 files changed, 736 insertions(+), 231 deletions(-) create mode 100644 v2realbot/loader/aggregator_old.py diff --git a/v2realbot/ENTRY_Vykladaci_RSI_MYSELL.py b/v2realbot/ENTRY_Vykladaci_RSI_MYSELL.py index 8e91b18..4e8e2ac 100644 --- a/v2realbot/ENTRY_Vykladaci_RSI_MYSELL.py +++ b/v2realbot/ENTRY_Vykladaci_RSI_MYSELL.py @@ -5,7 +5,7 @@ from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import Strat from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide, OrderType from v2realbot.indicators.indicators import ema from v2realbot.indicators.oscillators import rsi -from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, print, safe_get, get_tick +from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, print, safe_get, get_tick, round2five from datetime import datetime #from icecream import install, ic #from rich import print @@ -175,6 +175,10 @@ def next(data, state: StrategyState): akt_pozic = int(state.positions) max_pozic = int(state.vars.maxpozic) + if akt_pozic >= max_pozic: + state.ilog(e="MAX pozic reached, cannot vyklad") + return + #mame polovinu a vic vylozeno, pouzivame defenzicni krivku if is_defensive_mode(): state.ilog(e="DEF: Pouzivame defenzivni krivku", akt_pozic=akt_pozic, max_pozic=max_pozic, curve_def=curve_def) @@ -243,31 +247,38 @@ def next(data, state: StrategyState): #na urovni CBARU mame zajisteno, ze update prichazi pri zmene ceny #v kazde iteraci testujeme sell - #pri confirmed tesutjeme i buy + #pri potvrzenem baru muzeme provest kroky per hlavni BAR + #potvrzeni neprinasi nikdy zadna nova data, ale pouze potvrzeni. state.ilog(e="-----") eval_sell() - conf_bar = data['confirmed'] - #for CBAR TICK and VOLUME change info - #price change vs Volume - tick_price = data['close'] - tick_volume = data['volume'] - state.vars.last_tick_volume - - #pouze potvrzovací BAR CBARu, mozna id confirmed = 1, pak ignorovat - if tick_volume == 0: + if conf_bar == 1: + #delej veci per standardni bar + state.ilog(e="BAR potvrzeny") + else: pass + #delej veci tick-based - ##naplneni indikatoru vnitrniho CBAR tick price - ##pozor CBAR identifikatory jsou ukladane do historie az pro konfirmnuty bar - try: - state.indicators.tick_price[-1] = tick_price - state.indicators.tick_volume[-1] = tick_volume - except: - pass + #CBAR INDICATOR pro tick price a deltu VOLUME + tick_price = round2five(data['close']) + tick_delta_volume = data['volume'] - state.vars.last_tick_volume + + if conf_bar == 0: + try: + #pokud v potvrzovacím baru nebyly zmeny, nechavam puvodni hodnoty + # if tick_delta_volume == 0: + # state.indicators.tick_price[-1] = state.indicators.tick_price[-2] + # state.indicators.tick_volume[-1] = state.indicators.tick_volume[-2] + # else: - state.ilog(e=f"TICK PRICE {tick_price} VOLUME {tick_volume} {conf_bar=}", last_price=state.vars.last_tick_price, last_volume=state.vars.last_tick_volume) + #docasne dame pryc volume deltu a davame absolutni cislo + state.cbar_indicators.tick_price[-1] = tick_price + state.cbar_indicators.tick_volume[-1] = tick_delta_volume + except: + pass + state.ilog(e=f"TICK PRICE {tick_price} VOLUME {tick_delta_volume} {conf_bar=}", last_price=state.vars.last_tick_price, last_volume=state.vars.last_tick_volume) state.vars.last_tick_price = tick_price state.vars.last_tick_volume = data['volume'] @@ -278,6 +289,18 @@ def next(data, state: StrategyState): + #TEST BUY SIGNALu z cbartick_price - 3klesave za sebou + buy_tp = isfalling(state.cbar_indicators.tick_price,state.vars.Trend) + state.ilog(e=f"TICK SIGNAL ISFALLING {buy_tp}", last_tp=state.cbar_indicators.tick_price[-6:], trend=state.vars.Trend) + + #IVWAP - PRUBEZNY persistovany VWAP + # try: + # #naplneni cbar tick indikatoru s prubeznou vwap + # state.cbar_indicators.ivwap[-1]=data['vwap'] + # except: + # pass + + # if data['confirmed'] == 0: # state.ilog(e="CBAR unconfirmed - returned", msg=str(data)) # #TBD zde muzeme i nakupovat @@ -287,7 +310,7 @@ def next(data, state: StrategyState): # else: # state.ilog(e="CBAR confirmed - continue", msg=str(data)) - #EMA INDICATOR - + #BAR EMA INDICATOR - #plnime MAcko - nyni posilame jen N poslednich hodnot #zaroven osetrujeme pripady, kdy je malo dat a ukladame nulu try: @@ -296,27 +319,31 @@ def next(data, state: StrategyState): source = state.bars.close[-ma:] #state.bars.vwap ema_value = ema(source, ma) state.indicators.ema[-1]=trunc(ema_value[-1],3) + state.ilog(e=f"EMA {state.indicators.ema[-1]}", ema_last=state.indicators.ema[-6:]) except Exception as e: state.ilog(e="EMA nechavame 0", message=str(e)+format_exc()) #state.indicators.ema[-1]=(0) - #RSI14 INDICATOR + #CBAR RSI14 INDICATOR try: ##mame v atributech nastaveni? rsi_dont_buy_above = safe_get(state.vars, "rsi_dont_buy_above",50) rsi_buy_signal_conf = safe_get(state.vars, "rsi_buy_signal_below",40) rsi_buy_signal = False rsi_dont_buy = False - rsi_length = 2 - source = state.bars.close #[-rsi_length:] #state.bars.vwap + rsi_length = 14 + + #source = state.bars.close #[-rsi_length:] #state.bars.vwap + #jako zdroj je prubezna CBAR tickprice + source = state.cbar_indicators.tick_price rsi_res = rsi(source, rsi_length) rsi_value = trunc(rsi_res[-1],3) - state.indicators.RSI14[-1]=rsi_value + state.cbar_indicators.RSI14[-1]=rsi_value rsi_dont_buy = rsi_value > rsi_dont_buy_above rsi_buy_signal = rsi_value < rsi_buy_signal_conf - state.ilog(e=f"RSI{rsi_value} {rsi_length=} {rsi_dont_buy=} {rsi_buy_signal=}", rsi_indicator=state.indicators.RSI14[-5:]) + state.ilog(e=f"CBARRSI{rsi_value} {rsi_length=} {rsi_dont_buy=} {rsi_buy_signal=}", rsi_indicator=state.cbar_indicators.RSI14[-5:]) except Exception as e: - state.ilog(e=f"RSI {rsi_length=} nechavame 0", message=str(e)+format_exc()) + state.ilog(e=f"CBARRSI {rsi_length=} nechavame 0", message=str(e)+format_exc()) #state.indicators.RSI14.append(0) @@ -417,9 +444,8 @@ def next(data, state: StrategyState): #TODO: zvazit jestli nechat i pri otevrenych pozicich, zatim nechavame #TODO int(int(state.oa.poz)/int(state.variables.chunk)) > X - #TODO predelat mechanismus ticků (zrelativizovat), aby byl pouzitelny na tituly s ruznou cenou #TODO spoustet 1x za X iteraci nebo cas - if state.vars.jevylozeno == 1: + if state.vars.jevylozeno == 1 and len(state.vars.pendingbuys)>0: #pokud mame vylozeno a cena je vetsi nez tick2reset if len(state.vars.pendingbuys)>0: maxprice = max(state.vars.pendingbuys.values()) @@ -443,34 +469,6 @@ def next(data, state: StrategyState): state.vars.jevylozeno = 0 state.ilog(e="PB se vyklepaly nastavujeme: neni vylozeno", jevylozeno=state.vars.jevylozeno) - #TODO toto dodelat konzolidaci a mozna lock na limitku a pendingbuys a jevylozeno ?? - - #kdykoliv se muze notifikace ztratit - # - pendingbuys - vsechny open orders buy - # - limitka - open order sell - - - - - - - #pokud je vylozeno a mame pozice a neexistuje limitka - pak ji vytvorim - # if int(state.oe.poz)>0 and state.oe.limitka == 0: - # #pro jistotu updatujeme pozice - # state.oe.avgp, state.oe.poz = state.oe.pos() - # if int(state.oe.poz) > 0: - # cena = round(float(state.oe.avgp) + float(state.oe.stratvars["profit"]),2) - # print("BUGF: limitka neni vytvarime, a to za cenu",cena,"mnozstvi",state.oe.poz) - # print("aktuzalni ltp",ltp.price[state.oe.symbol]) - - # try: - # state.oe.limitka = state.oe.sell_noasync(cena, state.oe.poz) - # print("vytvorena limitka", state.oe.limitka) - # except Exception as e: - # print("Neslo vytvorit profitku. Problem,ale jedeme dal",str(e)) - # pass - # ##raise Exception(e) - print(10*"*","NEXT STOP",10*"*") def init(state: StrategyState): @@ -481,12 +479,13 @@ def init(state: StrategyState): state.vars.last_tick_price = 0 state.vars.last_tick_volume = 0 state.vars.next_new = 0 - state.indicators['tick_price'] = [] - state.indicators['tick_volume'] = [] + #state.cbar_indicators['ivwap'] = [] + state.cbar_indicators['tick_price'] = [] + state.cbar_indicators['tick_volume'] = [] state.indicators['ema'] = [] state.indicators['slope'] = [] state.indicators['slopeMA'] = [] - state.indicators['RSI14'] = [] + state.cbar_indicators['RSI14'] = [] #static indicators - those not series based state.statinds['angle'] = dict(minimum_slope=state.vars["minimum_slope"]) state.vars["ticks2reset_backup"] = state.vars.ticks2reset diff --git a/v2realbot/__pycache__/config.cpython-310.pyc b/v2realbot/__pycache__/config.cpython-310.pyc index 85a2755785563bb224b385570e8e182a7a979b3c..25a29de25fd011267eb734888e8a389a50d67e7a 100644 GIT binary patch delta 195 zcmW-axedZV00i%_jj_$)7aK7DQwt&>Lj+5@2NDS-5I_UafF)XRN*$Cz4V>6uq?u%^ zyvo%@DPNeC+Y8?hwZA&>*+f7WZS9;DI)8?A5S;{DFNs*Y^llP~C)R>KgS%jGYalh& zrZT3CW$b8+p|+1UBgS_dT_(txqF{y|yXZ5=fCWk|&qIvZ!qX#fBK delta 195 zcmW-axedZV00i&whmFsGKOTemYeD3QVBsK TG<8MyAtARFA|_A%T`v#S7|N1uq~`i6{K94~Q3pc;1!zvfsJmByFhbkQJ@UlAo&RY3Gx?jgaWWdPa=O7lGp@PFN+T=lI%9Tm z_;z0G|V@O9qP1Ks7znurB@oiDPrB-UD<9cO1odq!Zm`YIEXb*3@SUSx~x!k!nI zH)1fKzd6StpWmheVK`aby&NRfc*ccc(WH-i~2^h9|@3v!g&c5 z=u{X80it;Z1L%)zg*8LzI`_>`Ln-`iscTKJAd+HPwrq6Y-jUzVx}Rb>h6*qGQFkQJ zx5Mh3`4-aIY8SX4Vg873Z;XbrO?0dKF?JVoM^~8mnD&HKTwK1n@X#7j9ohyg#{5Kc z4?27$?65yitqPN{W->=6<0$7_H`X(~i|%O8ge2w0;qrs`H+jXL?uS9L%i4Qf+yiZn zN&wIOJXUFHdbvPe_!r#d5$47fH>L`hfMwyvjr5{H)SnEZ%_&%oXBfD>?)W7w$9hpu zZCSyoYTV?#Xdlk|g6Jl9Q>xrFrSPKRHV{Is-581}(hB!!DxjPRn8BJeuJZ@XPkF|T z<4P+^94=VTxS0=a*uty19}zJEYMT{z6w;ihBW@o|sDfy}3Yr%Crx43@N3ml~Ox#fL-fiHnqxb#o5xgC8TOgrFNiRWo-;CnUiYx*g zN+@%mN;Izc;&5)#F3rubmu1Y<^FVQKh~k`oCyb}pIR?}~)vwlB&r-6&w(MA7ZIobD z*yNn4bFVOzOp~OJK9;BD(Rj8J+NDVvSn7GAr-cSx6aPenyO2c(|1S9A@Q$K!OiU{6 z^d+8UwAHZOO|EMCyE<=Zeu|W3iXNC49vDr02(L7qb7VpP(v0lHKaxpjW{Tse6a^yt zh)fYF64?(Tzi{#+^-I5fx$)m`zIa$Z=#jQOKN%tCv_LgNjx@N_A zNNBeFW}1Y z6n{vw4S6MzpNSbfp~dh*iD{;m0cS)UJ*6Me4O;6l!;-%yDzghznQ^w=Ziz!+MVZJ> z;fi}uUf6R~KO_?J)AZ!!YkQJ>^c8Z8Tv(~C)bOs`FB9Wylgj@b`IEL}iwCxNaZbLM zIUv6qcgDhiq#*M%@|W>HIe6LO6k=>0|L%gsIP~#yOZ9XguRh;c*F+)EHzJ#?XLvZ7 z@zP^Wb0ZoUG?W({jJM{}pzwu~C6*B++=Z&qZgd-7*ROchnwW!XU{u?kHwBeB&^s%B zDH4`>MhP$;1HFB=d9UbDm==+pim5keF@xO%811c$X6d$W11m>fPu@mJ(T=Hz^X{FCbFYHUgh;{G+=6Z9Hn4@?F?WRDD(WmzFw)lyeaGb4lC}#{V?i9I z0_oWjkAeISVbLZPK`S~h5LFv`nM`kl)&R8ki>@*wvTJJu?O&;#NiYgpJbnaif*0iV zY-ty`!lxLw09LF(F)&&U;Cl;X#VYyT3eVR`aTA_;$`T|kO(2{6B=_pH8d?LNlIO%I mna4l|BmOy2tGGB@L`Tl#=L<9$%JGIg;S9@0{*eAus_=gZ5?=NI delta 2462 zcmah~-D@0G6u)0PJ3HNMCY$}vSDQ9Xx7wPtwh{f8sQ)*HUTyee@Guw-3`< z{i$8Eo<_%E?Zyu1`PlK+MM22>G$0{u(zXdtSQ zkmV17BLs3ypbNb~5Wvopp27TPSO_3(eZfiRLD!}-HbTNkie$-(XtaVYZEJ@%N1%WY zgQ#>u-cLx{oRYwXXuC|2@SZ0@EO0uoX_Re1BN`njJ}%?yM0K!aSPiIqJL~FML)1jb z=Fws>aH~Vdp?lw9_e>vpDM&S$LxW)`<_9^@wEP!kylH_XB?Z|3?9b`ZU3*$4JxJ3s zIZxz3NW3hr;GL5KXeK0rRP!=1M;P()Qou88D01+CbzY`EcEA?As)b&`E9@>+Nyr%W z8Dx+4q+nAp@iP52Zy{_cVuGU(ir*||(*;UPL$^kZJOg@zRGanIpcyq>_90Ey^d~J{ zBZn;6USzi~&*h0sWLrI94#JFWCBSY#xV}SrCnxhGL>Bt#M=#)#+9;Rm zzHD^ktCf~g8SuJ-RRc_R4>p`XN7pXb}e#Ha&n7c^N>AUVB{g1m}|Kc7To5Fy+ zR`$C*SY7Li)2B{eR5`ScsTn;#G;?avk(K3TZ%tK7WpxTPYIchpLGO6Qzg}(xUZ9p_ z84Vg=17WvqE6$l6r%CT-u_7#hJr0G{G#c{~a1XgCd6(c!DNq0iGO9(dU@M z6Wj(_0i;QhU``}X9qNb#bz?TQsKpXI#rJc;ESBMpu(Y3CnmUiwh->wFO&tYRT|x4o zSi>FBegCjD6UJj1Jr;o!9W}GbE0n^ezE{7NP7L;00_k3$Ak*GD6z$Z(tR8xwlD&hIk>f4R(%5a+Wk%$l$8yYBJy<3q+f#LdZIi4btR3 z=-^X8s#XUsiw#Oi1?(k(Szxc4IVjqy0-<}m!Wz3yrBU%4!O|N(ATx~;QL3-qH1&4* z+ImnFy<@?am>rXoyQ!v%)j&M=!g+(D@H?aaZ}RkZeyybYuq^*mWG zd7fe@T0#P!KBBInk~adSY+Pa&R0+uz6x{*OE7g`7jZ(Q*g8-%iLrg(9OR5ZH0&Bq_ zHWBodh)6|L8PFP|QlHN*09>8y*_rET{SFY=qiKstvyRe7@Ixu$S9o~m=yL+HshYl& zd&a;R0g-`G0`osG9)2Q>OfvY!OiF6*5(BB^<{}n(vdnhC8|lHTo^3(K-T^xa+#_(S zRn#NQJrr?QU2AC#?gF_3XaPXaJ`w5D*xe-f8l>Sc8l>Ue+4_V0^h0Q?8<_4fNGpZe zMXdsWKL%>mK)=TU9DrhO2f#dKE0h*TAe(-waFs6Wj|vmi*IyJ46>uPodRsIT_lW+h KaEuS7a{mDop8L4~ diff --git a/v2realbot/loader/aggregator.py b/v2realbot/loader/aggregator.py index 07b229c..c15dbf9 100644 --- a/v2realbot/loader/aggregator.py +++ b/v2realbot/loader/aggregator.py @@ -23,6 +23,8 @@ class TradeAggregator: mintick: int = 0, exthours: bool = False): """ + UPDATED VERSION - vrací více záznamů + Create trade agregator. Instance accepts trades one by one and process them and returns output type Trade - return trade one by one (no change) Bar - return finished bar in given timeframe @@ -62,6 +64,8 @@ class TradeAggregator: #instance variable to hold last trade price self.last_price = 0 self.barindex = 1 + self.diff_price = True + self.preconfBar = {} async def ingest_trade(self, indata, symbol): """ @@ -72,7 +76,7 @@ class TradeAggregator: data = unpackb(indata) #last item signal - if data == "last": return data + if data == "last": return [data] #print(data) ##implementing fitlers - zatim natvrdo a jen tyto: size: 1, cond in [O,C,4] opening,closed a derivately priced, @@ -82,15 +86,15 @@ class TradeAggregator: ## přidán W - average price trade, U - Extended hours - sold out of sequence try: for i in data['c']: - if i in ('C','O','4','B','7','V','P','W','U'): return 0 + if i in ('C','O','4','B','7','V','P','W','U'): return [] except KeyError: pass - #EXPERIMENT zkusime vyhodit vsechny pod 50 #puv if int(data['s']) == 1: return 0 + #EXPERIMENT zkusime vyhodit vsechny pod 50 #puv if int(data['s']) == 1: return [] #zatim nechavame - výsledek je naprosto stejný jako v tradingview - if int(data['s']) < self.minsize: return 0 + if int(data['s']) < self.minsize: return [] #{'t': 1678982075.242897, 'x': 'D', 'p': 29.1333, 's': 18000, 'c': [' ', '7', 'V'], 'i': 79372107591749, 'z': 'A', 'u': 'incorrect'} - if 'u' in data: return 0 + if 'u' in data: return [] #pokud projde TRADE s cenou 0.33% rozdilna oproti predchozi, pak vyhazujeme v ramci cisteni dat (cca 10ticku na 30USD) pct_off = 0.33 @@ -106,7 +110,7 @@ class TradeAggregator: if float(data['p']) > float(ltp.price[symbol]) + (float(data['p'])/100*pct_off) or float(data['p']) < float(ltp.price[symbol])-(float(data['p'])/100*pct_off): print("ZLO", data,ltp.price[symbol]) #nechavame zlo zatim projit - ##return 0 + ##return [] # with open("cache/wrongtrades.txt", 'a') as fp: # fp.write(str(data) + 'predchozi:'+str(ltp.price[symbol])+'\n') @@ -128,7 +132,7 @@ class TradeAggregator: if not is_open_hours(datetime.fromtimestamp(data['t'])) and self.exthours is False: #print("AGG: trade not in open hours skipping", datetime.fromtimestamp(data['t']).astimezone(zoneNY)) - return 0 + return [] #tady bude vzdycky posledni cena a posledni cas if self.update_ltp: @@ -137,7 +141,7 @@ class TradeAggregator: #if data['p'] < self.last_price - 0.02: print("zlo:",data) - if self.rectype == RecordType.TRADE: return data + if self.rectype == RecordType.TRADE: return [data] #print("agr přišel trade", datetime.fromtimestamp(data['t']),data) @@ -167,9 +171,45 @@ class TradeAggregator: else: self.newBar['confirmed'] = 1 self.newBar['vwap'] = self.vwaphelper / self.newBar['volume'] - #updatujeme čas - obsahuje datum tradu, který confirm triggeroval - self.newBar['updated'] = data['t'] + + #HACK pro update casu, který confirm triggeroval + #u CBARu v confirmnutem muze byt + # 1) no trades (pak potvrzujeme predchozi) + # 2) trades with same price , ktere zaroven timto flushujeme (v tomto pripade je cas updatu cas predchoziho tradu) + + # variantu vyse pozname podle nastavene self.diff_price = True (mame trady a i ulozeny cas) + if self.rectype == RecordType.CBAR: + #UPDATE ať confirmace nenese zadna data, vsechny zmenena data jsou vyflusnute predtim + #pokud byly nejake trady + if self.diff_price is False: + #self.newBar['updated'] = self.lasttimestamp + + #TODO tady bychom nejdriv vyflushnuly nekonfirmovany bar s trady + #a nasladne poslali prazdny confirmacni bar + self.preconfBar = deepcopy(self.newBar) + self.preconfBar['updated'] = self.lasttimestamp + self.preconfBar['confirmed'] = 0 + #pridat do promenne + + #else: + #NASTY HACK pro GUI + #zkousime potvrzeni baru dat o chlup mensi cas nez cas noveho baru, ktery jde hned za nim + #gui neumi zobrazit duplicity a v RT grafu nejde upravovat zpetne + #zarovname na cas baru podle timeframu(např. 5, 10, 15 ...) (ROUND) + if self.align: + t = datetime.fromtimestamp(data['t']) + t = t - timedelta(seconds=t.second % self.timeframe,microseconds=t.microsecond) + #nebo pouzijeme datum tradu zaokrouhlene na vteriny (RANDOM) + else: + #ulozime si jeho timestamp (odtum pocitame timeframe) + t = datetime.fromtimestamp(int(data['t'])) + + #self.newBar['updated'] = float(data['t']) - 0.001 + self.newBar['updated'] = datetime.timestamp(t) - 0.000001 + #PRO standardní BAR nechavame puvodni + else: + self.newBar['updated'] = data['t'] #ulozime datum akt.tradu pro mintick self.lastBarConfirmed = True #ukládám si předchozí (confirmed)bar k vrácení @@ -199,9 +239,9 @@ class TradeAggregator: #je cena stejna od predchoziho tradu? pro nepotvrzeny cbar vracime jen pri zmene ceny if self.last_price == data['p']: - diff_price = False + self.diff_price = False else: - diff_price = True + self.diff_price = True self.last_price = data['p'] #spočteme vwap - potřebujeme předchozí hodnoty @@ -216,6 +256,7 @@ class TradeAggregator: self.newBar['hlcc4'] = round((self.newBar['high']+self.newBar['low']+self.newBar['close']+self.newBar['close'])/4,3) #predchozi bar byl v jine vterine, tzn. ukladame do noveho (aktualniho) pocatecni hodnoty + #NEW BAR POPULATION if (issamebar == False): #zaciname novy bar @@ -249,14 +290,30 @@ class TradeAggregator: #je tu maly bug pro CBAR - kdy prvni trade, který potvrzuje predchozi bar #odesle potvrzeni predchoziho baru a nikoliv open stávajícího, ten posle až druhý trade #což asi nevadí + #OPRAVENO - #pokud je pripraveny, vracíme předchozí confirmed bar + #pokud je pripraveny, vracíme předchozí confirmed bar PLUS NOVY, který ho triggeroval. pokud bylo + # pred confirmem nejake trady beze zmeny ceny flushujeme je take (preconfBar) + #predchozi bar muze obsahovat zmenena data if len(self.returnBar) > 0: - self.tmp = self.returnBar - self.returnBar = {} - #print(self.tmp) - return self.tmp + return_set = [] + #pridame preconfirm bar pokud je + if len(self.preconfBar)>0: + return_set.append(self.preconfBar) + self.preconfBar = {} + #pridame confirmation bar + return_set.append(self.returnBar) + #self.tmp = self.returnBar + self.returnBar = [] + #doplnime prubezny vwap + self.newBar['vwap'] = self.vwaphelper / self.newBar['volume'] + return_set.append(self.newBar) + #TODO pridat sem podporu pro mintick jako nize, tzn. pokud je v ochrannem okne, tak novy bar nevracet + #zatim je novy bar odesilan nehlede na mintick + #return_set = [self.tmp, self.newBar] + + return return_set #pro cont bar posilame ihned (TBD vwap a min bar tick value) if self.rectype == RecordType.CBAR: @@ -267,7 +324,7 @@ class TradeAggregator: #pocatek noveho baru + Xs musi byt vetsi nez aktualni trade if (self.newBar['time'] + timedelta(seconds=self.mintick)) > datetime.fromtimestamp(data['t']): #print("waiting for mintick") - return 0 + return [] else: self.lastBarConfirmed = False @@ -276,12 +333,12 @@ class TradeAggregator: #print(self.newBar) #pro (nepotvrzeny) cbar vracime jen pri zmene ceny - if diff_price is True: - return self.newBar + if self.diff_price is True: + return [self.newBar] else: - return 0 + return [] else: - return 0 + return [] class TradeAggregator2Queue(TradeAggregator): @@ -297,15 +354,17 @@ class TradeAggregator2Queue(TradeAggregator): async def ingest_trade(self, data): #print("ingest ve threadu:",current_thread().name) res = await super().ingest_trade(data, self.symbol) - if res != 0: + + #if len(res) > 0: + for obj in res: #print(res) #pri rychlem plneni vetsiho dictionary se prepisovali - vyreseno kopií - if isinstance(res, dict): - copy = res.copy() + if isinstance(obj, dict): + copy = obj.copy() else: - copy = res + copy = obj self.queue.put(copy) - res = {} + res = [] #print("po insertu",res) class TradeAggregator2List(TradeAggregator): @@ -324,17 +383,17 @@ class TradeAggregator2List(TradeAggregator): #print("ted vstoupil do tradeagg2list ingestu") res1 = await super().ingest_trade(data, self.symbol) #print("ted je po zpracovani", res1) - if res1 != 0: + for obj in res1: #pri rychlem plneni vetsiho dictionary se prepisovali - vyreseno kopií - if isinstance(res1, dict): - copy = res1.copy() + if isinstance(obj, dict): + copy = obj.copy() else: - copy = res1 - if res1 == 'last': return 0 + copy = obj + if obj == 'last': return [] self.btdata.append((copy['t'],copy['p'])) # with open(self.debugfile, "a") as output: # output.write(str(copy['t']) + ' ' + str(datetime.fromtimestamp(copy['t']).astimezone(zoneNY)) + ' ' + str(copy['p']) + '\n') - res1 = {} + res1 = [] #print("po insertu",res) diff --git a/v2realbot/loader/aggregator_old.py b/v2realbot/loader/aggregator_old.py new file mode 100644 index 0000000..07b229c --- /dev/null +++ b/v2realbot/loader/aggregator_old.py @@ -0,0 +1,341 @@ +""" + Aggregator mdoule containing main aggregator logic for TRADES, BARS and CBAR +""" +from v2realbot.enums.enums import RecordType, StartBarAlign +from datetime import datetime, timedelta +from v2realbot.utils.utils import parse_alpaca_timestamp, ltp, Queue,is_open_hours,zoneNY +from queue import Queue +from rich import print +from v2realbot.enums.enums import Mode +import threading +from copy import deepcopy +from msgpack import unpackb +import os +from config import DATA_DIR + +class TradeAggregator: + def __init__(self, + rectype: RecordType = RecordType.BAR, + timeframe: int = 5, + minsize: int = 100, + update_ltp: bool = False, + align: StartBarAlign = StartBarAlign.ROUND, + mintick: int = 0, + exthours: bool = False): + """ + Create trade agregator. Instance accepts trades one by one and process them and returns output type + Trade - return trade one by one (no change) + Bar - return finished bar in given timeframe + CBar - returns continuous bar, finished bar is marked by confirmed status + Args: + timeframe (number): Resolution of bar in seconds + update_ltp (bool): Whether to update global variable with price (usually only one instance does that) + align: Defines alignement of first bar. ROUND - according to timeframe( 5,10,15 - for 5s timeframe), RANDOM - according to timestamp of first trade + mintick: Applies for CBAR. Minimální mezera po potvrzeni baru a aktualizaci dalsiho nepotvrzeneho (např. pro 15s, muzeme chtit prvni tick po 5s). po teto dobe realtime. + """ + self.rectype: RecordType = rectype + self.timeframe = timeframe + self.minsize = minsize + self.update_ltp = update_ltp + self.exthours = exthours + + if mintick >= timeframe: + print("Mintick musi byt mensi nez timeframe") + raise Exception + + self.mintick = mintick + #class variables = starters + self.iterace = 1 + self.lasttimestamp = 0 + #inicalizace pro prvni agregaci + self.newBar = dict(high=0, low=999999, volume = 0, trades = 0, confirmed = 0, vwap = 0, close=0, index = 1, updated = 0) + self.bar_start = 0 + self.align = align + self.tm: datetime = None + self.firstpass = True + self.vwaphelper = 0 + self.returnBar = {} + self.lastBarConfirmed = False + #min trade size + self.minsize = minsize + + #instance variable to hold last trade price + self.last_price = 0 + self.barindex = 1 + + async def ingest_trade(self, indata, symbol): + """ + Aggregator logic for trade record + Args: + indata (dict): online or offline record + """ + data = unpackb(indata) + + #last item signal + if data == "last": return data + + #print(data) + ##implementing fitlers - zatim natvrdo a jen tyto: size: 1, cond in [O,C,4] opening,closed a derivately priced, + ## 22.3. - dal jsem pryc i contingency trades [' ', '7', 'V'] - nasel jsem obchod o 30c mimo + ## dán pryč P - prior reference time + 25centu mimo, {'t': '2023-04-12T19:45:08.63257344Z', 'x': 'D', 'p': 28.68, 's': 1000, 'c': [' ', 'P'], 'i': 71693108525109, 'z': 'A'}, + ## Q - jsou v pohode, oteviraci trady, ale O jsou jejich duplikaty + ## přidán W - average price trade, U - Extended hours - sold out of sequence + try: + for i in data['c']: + if i in ('C','O','4','B','7','V','P','W','U'): return 0 + except KeyError: + pass + + #EXPERIMENT zkusime vyhodit vsechny pod 50 #puv if int(data['s']) == 1: return 0 + #zatim nechavame - výsledek je naprosto stejný jako v tradingview + if int(data['s']) < self.minsize: return 0 + #{'t': 1678982075.242897, 'x': 'D', 'p': 29.1333, 's': 18000, 'c': [' ', '7', 'V'], 'i': 79372107591749, 'z': 'A', 'u': 'incorrect'} + if 'u' in data: return 0 + + #pokud projde TRADE s cenou 0.33% rozdilna oproti predchozi, pak vyhazujeme v ramci cisteni dat (cca 10ticku na 30USD) + pct_off = 0.33 + ##ic(ltp.price) + ##ic(ltp.price[symbol]) + + try: + ltp.price[symbol] + except KeyError: + ltp.price[symbol]=data['p'] + + + if float(data['p']) > float(ltp.price[symbol]) + (float(data['p'])/100*pct_off) or float(data['p']) < float(ltp.price[symbol])-(float(data['p'])/100*pct_off): + print("ZLO", data,ltp.price[symbol]) + #nechavame zlo zatim projit + ##return 0 + # with open("cache/wrongtrades.txt", 'a') as fp: + # fp.write(str(data) + 'predchozi:'+str(ltp.price[symbol])+'\n') + + #timestampy jsou v UTC + #TIMESTAMP format is different for online and offline trade streams + #offline trade + #{'t': '2023-02-17T14:30:00.16111744Z', 'x': 'J', 'p': 35.14, 's': 20, 'c': [' ', 'F', 'I'], 'i': 52983525027938, 'z': 'A'} + #websocket trade + #{'T': 't', 'S': 'MSFT', 'i': 372, 'x': 'V', 'p': 264.58, 's': 25, 'c': ['@', 'I'], 'z': 'C', 't': Timestamp(seconds=1678973696, nanoseconds=67312449), 'r': Timestamp(seconds=1678973696, nanoseconds=72865209)} + #parse alpaca timestamp + + # tzn. na offline mohu pouzit >>> datetime.fromisoformat(d).timestamp() 1676644200.161117 + #orizne sice nanosekundy ale to nevadi + #print("tady", self.mode, data['t']) + # if self.mode == Mode.BT: + # data['t'] = datetime.fromisoformat(str(data['t'])).timestamp() + # else: + data['t'] = parse_alpaca_timestamp(data['t']) + + if not is_open_hours(datetime.fromtimestamp(data['t'])) and self.exthours is False: + #print("AGG: trade not in open hours skipping", datetime.fromtimestamp(data['t']).astimezone(zoneNY)) + return 0 + + #tady bude vzdycky posledni cena a posledni cas + if self.update_ltp: + ltp.price[symbol] = data['p'] + ltp.time[symbol] = data['t'] + + #if data['p'] < self.last_price - 0.02: print("zlo:",data) + + if self.rectype == RecordType.TRADE: return data + + #print("agr přišel trade", datetime.fromtimestamp(data['t']),data) + + #OPIC pokud bude vadit, ze prvni bar neni kompletni - pak zapnout tuto opicarnu + #kddyz jde o prvni iteraci a pozadujeme align, cekame na kulaty cas (pro 5s 0,5,10..) + # if self.lasttimestamp ==0 and self.align: + # if self.firstpass: + # self.tm = datetime.fromtimestamp(data['t']) + # self.tm += timedelta(seconds=self.timeframe) + # self.tm = self.tm - timedelta(seconds=self.tm.second % self.timeframe,microseconds=self.tm.microsecond) + # self.firstpass = False + # print("trade: ",datetime.fromtimestamp(data['t'])) + # print("required",self.tm) + # if self.tm > datetime.fromtimestamp(data['t']): + # return + # else: pass + + #print("barstart",datetime.fromtimestamp(self.bar_start)) + #print("oriznute data z tradu", datetime.fromtimestamp(int(data['t']))) + #print("timeframe",self.timeframe) + if int(data['t']) - self.bar_start < self.timeframe: + issamebar = True + else: + issamebar = False + ##flush předchozí bar a incializace (krom prvni iterace) + if self.lasttimestamp ==0: pass + else: + self.newBar['confirmed'] = 1 + self.newBar['vwap'] = self.vwaphelper / self.newBar['volume'] + #updatujeme čas - obsahuje datum tradu, který confirm triggeroval + self.newBar['updated'] = data['t'] + + #ulozime datum akt.tradu pro mintick + self.lastBarConfirmed = True + #ukládám si předchozí (confirmed)bar k vrácení + self.returnBar = self.newBar + #print(self.returnBar) + + #inicializuji pro nový bar + self.vwaphelper = 0 + + # return self.newBar + ##flush CONFIRMED bar to queue + #self.q.put(self.newBar) + ##TODO pridat prubezne odesilani pokud je pozadovano + self.barindex +=1 + self.newBar = { + "close": 0, + "high": 0, + "low": 99999999, + "volume": 0, + "trades": 0, + "hlcc4": 0, + "confirmed": 0, + "updated": 0, + "vwap": 0, + "index": self.barindex + } + + #je cena stejna od predchoziho tradu? pro nepotvrzeny cbar vracime jen pri zmene ceny + if self.last_price == data['p']: + diff_price = False + else: + diff_price = True + self.last_price = data['p'] + + #spočteme vwap - potřebujeme předchozí hodnoty + self.vwaphelper += (data['p'] * data['s']) + self.newBar['updated'] = data['t'] + self.newBar['close'] = data['p'] + self.newBar['high'] = max(self.newBar['high'],data['p']) + self.newBar['low'] = min(self.newBar['low'],data['p']) + self.newBar['volume'] = self.newBar['volume'] + data['s'] + self.newBar['trades'] = self.newBar['trades'] + 1 + #pohrat si s timto round + self.newBar['hlcc4'] = round((self.newBar['high']+self.newBar['low']+self.newBar['close']+self.newBar['close'])/4,3) + + #predchozi bar byl v jine vterine, tzn. ukladame do noveho (aktualniho) pocatecni hodnoty + if (issamebar == False): + #zaciname novy bar + + self.newBar['open'] = data['p'] + + #zarovname time prvniho baru podle timeframu kam patří (např. 5, 10, 15 ...) (ROUND) + if self.align: + t = datetime.fromtimestamp(data['t']) + t = t - timedelta(seconds=t.second % self.timeframe,microseconds=t.microsecond) + self.bar_start = datetime.timestamp(t) + #nebo pouzijeme datum tradu zaokrouhlene na vteriny (RANDOM) + else: + #ulozime si jeho timestamp (odtum pocitame timeframe) + t = datetime.fromtimestamp(int(data['t'])) + #timestamp + self.bar_start = int(data['t']) + + + + + self.newBar['time'] = t + self.newBar['resolution'] = self.timeframe + self.newBar['confirmed'] = 0 + + + #uložíme do předchozí hodnoty (poznáme tak open a close) + self.lasttimestamp = data['t'] + self.iterace += 1 + # print(self.iterace, data) + + #je tu maly bug pro CBAR - kdy prvni trade, který potvrzuje predchozi bar + #odesle potvrzeni predchoziho baru a nikoliv open stávajícího, ten posle až druhý trade + #což asi nevadí + + + #pokud je pripraveny, vracíme předchozí confirmed bar + if len(self.returnBar) > 0: + self.tmp = self.returnBar + self.returnBar = {} + #print(self.tmp) + return self.tmp + + #pro cont bar posilame ihned (TBD vwap a min bar tick value) + if self.rectype == RecordType.CBAR: + + #pokud je mintick nastavený a předchozí bar byl potvrzený + if self.mintick != 0 and self.lastBarConfirmed: + #d zacatku noveho baru musi ubehnout x sekund nez posilame updazte + #pocatek noveho baru + Xs musi byt vetsi nez aktualni trade + if (self.newBar['time'] + timedelta(seconds=self.mintick)) > datetime.fromtimestamp(data['t']): + #print("waiting for mintick") + return 0 + else: + self.lastBarConfirmed = False + + #doplnime prubezny vwap + self.newBar['vwap'] = self.vwaphelper / self.newBar['volume'] + #print(self.newBar) + + #pro (nepotvrzeny) cbar vracime jen pri zmene ceny + if diff_price is True: + return self.newBar + else: + return 0 + else: + return 0 + + +class TradeAggregator2Queue(TradeAggregator): + """ + Child of TradeAggregator - sends items to given queue + In the future others will be added - TradeAggToTxT etc. + """ + def __init__(self, symbol: str, queue: Queue, rectype: RecordType = RecordType.BAR, timeframe: int = 5, minsize: int = 100, update_ltp: bool = False, align: StartBarAlign = StartBarAlign.ROUND, mintick: int = 0, exthours: bool = False): + super().__init__(rectype=rectype, timeframe=timeframe, minsize=minsize, update_ltp=update_ltp, align=align, mintick=mintick, exthours=exthours) + self.queue = queue + self.symbol = symbol + + async def ingest_trade(self, data): + #print("ingest ve threadu:",current_thread().name) + res = await super().ingest_trade(data, self.symbol) + if res != 0: + #print(res) + #pri rychlem plneni vetsiho dictionary se prepisovali - vyreseno kopií + if isinstance(res, dict): + copy = res.copy() + else: + copy = res + self.queue.put(copy) + res = {} + #print("po insertu",res) + +class TradeAggregator2List(TradeAggregator): + """" + stores records to the list + """ + def __init__(self, symbol: str, btdata: list, rectype: RecordType = RecordType.BAR, timeframe: int = 5, minsize: int = 100, update_ltp: bool = False, align: StartBarAlign = StartBarAlign.ROUND, mintick: int = 0, exthours: bool = False): + super().__init__(rectype=rectype, timeframe=timeframe, minsize=minsize, update_ltp=update_ltp, align=align, mintick=mintick, exthours=exthours) + self.btdata = btdata + self.symbol = symbol + # self.debugfile = DATA_DIR + "/BACprices.txt" + # if os.path.exists(self.debugfile): + # os.remove(self.debugfile) + + async def ingest_trade(self, data): + #print("ted vstoupil do tradeagg2list ingestu") + res1 = await super().ingest_trade(data, self.symbol) + #print("ted je po zpracovani", res1) + if res1 != 0: + #pri rychlem plneni vetsiho dictionary se prepisovali - vyreseno kopií + if isinstance(res1, dict): + copy = res1.copy() + else: + copy = res1 + if res1 == 'last': return 0 + self.btdata.append((copy['t'],copy['p'])) + # with open(self.debugfile, "a") as output: + # output.write(str(copy['t']) + ' ' + str(datetime.fromtimestamp(copy['t']).astimezone(zoneNY)) + ' ' + str(copy['p']) + '\n') + res1 = {} + #print("po insertu",res) + + + diff --git a/v2realbot/static/js/archivechart.js b/v2realbot/static/js/archivechart.js index 24a1872..39ee07c 100644 --- a/v2realbot/static/js/archivechart.js +++ b/v2realbot/static/js/archivechart.js @@ -127,8 +127,6 @@ function transform_data(data) { //pro jistotu jeste seradime podle casu //v BT se muze predbehnout a lightweight to pak nezobrazi - const sorter = (a, b) => a.time > b.time ? 1 : -1; - markers.sort(sorter) markers_line.sort(sorter) avgp_buy_line.sort(sorter) @@ -287,118 +285,142 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { //vybereme barvu pro kazdy identifikator //zjistime typ idenitfikatoru - zatim right vs left function display_indicators(data) { - console.log("indikatory", JSON.stringify(data.indicators,null,2)) + //console.log("indikatory", JSON.stringify(data.indicators,null,2)) //podobne v livewebsokcets.js - dat do jedne funkce if (data.hasOwnProperty("indicators")) { // console.log("jsme uvnitr indikatoru") - var indicators = data.indicators - //if there are indicators it means there must be at least two keys (time which is always present) - if (Object.keys(indicators).length > 1) { - for (const [key, value] of Object.entries(indicators)) { - if (key !== "time") { - //initialize indicator and store reference to array - var obj = {name: key, series: null} - - //start - //console.log(key) - //get configuation of indicator to display - conf = get_ind_config(key) - //INIT INDICATOR BASED on CONFIGURATION + //vraci se pole indicatoru, kazdy se svoji casovou osou (time) - nyni standard indikatory a cbar indikatory + var indicatorList = data.indicators - //MOVE TO UTILS ro reuse?? - if (conf && conf.display) { + indicatorList.forEach((indicators, index, array) => { - //tranform data do správného formátru - items = [] - //var last = null - value.forEach((element, index, array) => { - item = {} - //debug - //TOTO odstranit po identifikovani chyby - //if (indicators.time[index] !== undefined) { - //{console.log("problem",key,last)} - item["time"] = indicators.time[index] - item["value"] = element - //console.log("objekt indicatoru",item) - items.push(item) + //var indicators = data.indicators + //if there are indicators it means there must be at least two keys (time which is always present) + if (Object.keys(indicators).length > 1) { + for (const [key, value] of Object.entries(indicators)) { + if (key !== "time") { + //initialize indicator and store reference to array + var obj = {name: key, series: null} + + //start + //console.log(key) + //get configuation of indicator to display + conf = get_ind_config(key) + + //INIT INDICATOR BASED on CONFIGURATION + + //DO BUDOUCNA zde udelat sorter a pripadny handling duplicit jako + //funkci do ktere muzu zavolat vse co pujde jako data do chartu + + //MOVE TO UTILS ro reuse?? + if (conf && conf.display) { + + //tranform data do správného formátru + items = [] + //var last = null + var last_time = 0 + var time = 0 + value.forEach((element, index, array) => { + item = {} //debug - //last = item - // } - // else - // { - // console.log("chybejici cas", key) - // } - }); + //TOTO odstranit po identifikovani chyby + //if (indicators.time[index] !== undefined) { + //{console.log("problem",key,last)} + time = indicators.time[index] + if (time==last_time) { + //console.log(key, "problem v case - pousunuto o 0.001",time, last_time, element) + time += 0.000001 + } + item["time"] = time + item["value"] = element - if (conf.embed) { + last_time = time - if (conf.histogram) { + if ((element == null) || (indicators.time[index] == null)) { + console.log("probelem u indikatoru",key, "nekonzistence", element, indicators.time[index]) + } + + //console.log("objekt indicatoru",item) + items.push(item) + //debug + //last = item + // } + // else + // { + // console.log("chybejici cas", key) + // } + }); + + if (conf.embed) { + + if (conf.histogram) { + + obj.series = chart.addHistogramSeries({ + title: (conf.titlevisible?conf.name:""), + color: colors.shift(), + priceFormat: {type: 'volume'}, + priceScaleId: conf.priceScaleId, + lastValueVisible: conf.lastValueVisible, + priceScaleId: conf.priceScaleId}); + + obj.series.priceScale().applyOptions({ + // set the positioning of the volume series + scaleMargins: { + top: 0.7, // highest point of the series will be 70% away from the top + bottom: 0, + }, + }); + + } + else { + + obj.series = chart.addLineSeries({ + color: colors.shift(), + priceScaleId: conf.priceScaleId, + title: (conf.titlevisible?conf.name:""), + lineWidth: 1 + }); + + //toto nejak vymyslet konfiguracne, additional threshold lines + if (key == "slopeMA") { + //natvrdo nakreslime lajnu pro min angle + //TODO predelat na configuracne + const minSlopeLineOptopns = { + price: data.statinds.angle.minimum_slope, + color: '#b67de8', + lineWidth: 1, + lineStyle: 2, // LineStyle.Dotted + axisLabelVisible: true, + title: "max:", + }; + + const minSlopeLine = obj.series.createPriceLine(minSlopeLineOptopns); + } + } - obj.series = chart.addHistogramSeries({ - title: (conf.titlevisible?conf.name:""), - color: colors.shift(), - priceFormat: {type: 'volume'}, - priceScaleId: conf.priceScaleId, - lastValueVisible: conf.lastValueVisible, - priceScaleId: conf.priceScaleId}); - - obj.series.priceScale().applyOptions({ - // set the positioning of the volume series - scaleMargins: { - top: 0.7, // highest point of the series will be 70% away from the top - bottom: 0, - }, - }); } - else { - - obj.series = chart.addLineSeries({ - color: colors.shift(), - priceScaleId: conf.priceScaleId, - title: (conf.titlevisible?conf.name:""), - lineWidth: 1 - }); - - //toto nejak vymyslet konfiguracne, additional threshold lines - if (key == "slopeMA") { - //natvrdo nakreslime lajnu pro min angle - //TODO predelat na configuracne - const minSlopeLineOptopns = { - price: data.statinds.angle.minimum_slope, - color: '#b67de8', - lineWidth: 1, - lineStyle: 2, // LineStyle.Dotted - axisLabelVisible: true, - title: "max:", - }; + //INDICATOR on new pane + else { console.log("not implemented")} - const minSlopeLine = obj.series.createPriceLine(minSlopeLineOptopns); - } - } + //add options + obj.series.applyOptions({ + lastValueVisible: false, + priceLineVisible: false, + }); + //console.log("problem tu",JSON.stringify(items)) + //add data + obj.series.setData(items) - } - //INDICATOR on new pane - else { console.log("not implemented")} - - //add options - obj.series.applyOptions({ - lastValueVisible: false, - priceLineVisible: false, - }); - - //console.log("problem tu",items) - //add data - obj.series.setData(items) - - // add to indList array - pole zobrazovanych indikatoru - indList.push(obj); + // add to indList array - pole zobrazovanych indikatoru + indList.push(obj); + } } } } - } + }) } //display vwap and volume diff --git a/v2realbot/static/js/livewebsocket.js b/v2realbot/static/js/livewebsocket.js index 2c374fa..5edb610 100644 --- a/v2realbot/static/js/livewebsocket.js +++ b/v2realbot/static/js/livewebsocket.js @@ -5,6 +5,7 @@ var logcnt = 0 var positionsPriceLine = null var limitkaPriceLine = null var angleSeries = 1 +var cbar = false //get details of runner to populate chart status //fetch necessary - it could be initiated by manually inserting runnerId @@ -53,35 +54,21 @@ function connect(event) { ws.onmessage = function(event) { var parsed_data = JSON.parse(event.data) - //console.log(JSON.stringify(parsed_data)) + console.log(JSON.stringify(parsed_data)) - //check received data and display lines - if (parsed_data.hasOwnProperty("bars")) { - var bar = parsed_data.bars - candlestickSeries.update(bar); - volumeSeries.update({ - time: bar.time, - value: bar.volume - }); - vwapSeries.update({ - time: bar.time, - value: bar.vwap - }); - } - - if (parsed_data.hasOwnProperty("bars")) { - // console.log("mame bary") - var bar = parsed_data.bars - candlestickSeries.update(bar); - volumeSeries.update({ - time: bar.time, - value: bar.volume - }); - vwapSeries.update({ - time: bar.time, - value: bar.vwap - }); - } + // //check received data and display lines + // if (parsed_data.hasOwnProperty("bars")) { + // var bar = parsed_data.bars + // candlestickSeries.update(bar); + // volumeSeries.update({ + // time: bar.time, + // value: bar.volume + // }); + // vwapSeries.update({ + // time: bar.time, + // value: bar.vwap + // }); + // } //loglist if (parsed_data.hasOwnProperty("iter_log")) { @@ -344,6 +331,52 @@ function connect(event) { } } } + + if (parsed_data.hasOwnProperty("bars")) { + + var bar = parsed_data.bars + //pokud jde o cbary, tak jako time bereme cas posledniho update + //aby se nam na grafu nepredbihaly cbar indikatory + + //workaround pro identifikaci CBARU + //pokud se vyskytne unconfirmed bar = jde o CBARY - nastavena globalni promena + //standardni bar je vzdy potvrzeny + // if (bar.confirmed == 0) { + // cbar = true } + + + // //pozor CBARY zobrazujeme na konci platnosti baru, nikoliv dle TIME, ale UPDATED + // //kvuli navazovani prubeznych indikatoru na gui + // if (cbar) { + // // CBAR kreslime az po potvrzeni + // if (bar.confirmed == 1) { + // bar.time = bar.updated + // candlestickSeries.update(bar); + // volumeSeries.update({ + // time: bar.time, + // value: bar.volume + // }); + // vwapSeries.update({ + // time: bar.time, + // value: bar.vwap + // }); + // } + // } + // else { + // //time = bar.time + + + candlestickSeries.update(bar); + volumeSeries.update({ + time: bar.time, + value: bar.volume + }); + vwapSeries.update({ + time: bar.time, + value: bar.vwap + }); + //} + } } ws.onclose = function(event) { document.getElementById("status").textContent = "Disconnected from" + runnerId.value diff --git a/v2realbot/static/js/utils.js b/v2realbot/static/js/utils.js index 3270308..09d6aa3 100644 --- a/v2realbot/static/js/utils.js +++ b/v2realbot/static/js/utils.js @@ -9,6 +9,8 @@ var candlestickSeries = null var volumeSeries = null var vwapSeries = null +const sorter = (a, b) => a.time > b.time ? 1 : -1; + indConfig = {} settings = {} settings @@ -16,6 +18,7 @@ settings indConfig = [ {name: "ema", titlevisible: false, embed: true, display: true, priceScaleId: "right", lastValueVisible: false}, {name: "tick_volume", histogram: true, titlevisible: true, embed: true, display: true, priceScaleId: '', lastValueVisible: false}, {name: "tick_price", titlevisible: true, embed: true, display: true, priceScaleId: "right", lastValueVisible: false}, + {name: "ivwap", titlevisible: true, embed: true, display: false, priceScaleId: "right", lastValueVisible: false}, {name: "slope", titlevisible: true, embed: true, display: false, priceScaleId: "middle", lastValueVisible: false}, {name: "slopeMA", titlevisible: true, embed: true, display: true, priceScaleId: "middle", lastValueVisible: false}, {name: "emaSlow", titlevisible: true, embed: true, display: true, priceScaleId: "right", lastValueVisible: false}, diff --git a/v2realbot/strategy/__pycache__/base.cpython-310.pyc b/v2realbot/strategy/__pycache__/base.cpython-310.pyc index 801a362aba452059125d3e240d7f9edadd046cfe..8d53d295ae429785ce2f6a7bb972e499eede41ab 100644 GIT binary patch delta 2141 zcmZuxeQZ-z6o2RS_4TcN?YeazZMRXjZiA}X#+E?9IZ-eege5>^(WtfTmC@3*+wB{$ zrDGWjAsR-M%lI^58-9QqMQcQ(B8Va)iiX65;z#^L+Vgq_`MA}vS$h{ks7seRp_=1OvoS^mW&j(8`2sHJ^F2)Aqh{R_%HxZcQU`#jI?j( zwL8*|=k*zB$0>nfolZq8q_(p~Sj?QQug8d(QG)@og3eTOeT;QSHG?fcA>`!E?uXrl zo$Dej97`Ip&WM>}Y2NRCq>YyApj$NXLWx`gIw+tkD(G@vnfACXc5th@GOa8B*lo%c z{+Ig~V0_r);n#|a;dE9l9st;k!fZH~o%Eal*vcO-U96@t4co%UOA{FGESn3Lvh&Nn zlZp7AhYUaLi@**J{zhCp&tEPVL3X)+5UkJPx>eZ=6>9)1j#~`!x~jvDJ!A_00!DkR zUC6unZ&exCBluzD+jwTy{q9MMJVNjy!As<}PoTLSE5%;sS7#+Oa)%0710VKzcq(9> z&NL9HweCmXGyF)P!zrSGk-%ONS+=3Miw9<}_T_!K!gMrggqa!6-$t6zSv=P57`K~G z&R*saFS6p+4m;7@&-LnsF1svY^TZ`X>H~I-uc;ohC6U|W4t}IM*s@lUOnZMfNc8`h z)Vp@GWKs*-;`#_+cPVVA2_q8Dk=jc8pw6#XS44(Q5X>piC9u%2jao2LYn2Adh2oAE zAuni^rlQ^P;wjx@*hgtP9j#J_l=R|Tl=PnmY0wJNfNqwWWxTJZZm4|f9WIMi78$s$HH&4BxY0mmk)Y9GNSz9jf41o(mAgLOS5yh=cQ&Ta z2LC%4EZv7@&KWbKESyMng%h!~$#(OW+Iz4?pRV=77+vMOqZoAvJ14yw4ZB4JuWScR zrr{_7nZ`#}K)&I&AVWom?3=Zt0LS=!^-JIbK3rc8Kk)=mr_G2N>+Fu28&U>4PI@=N*95&h(&FU@?^+(1uYs&; zpgFWQZLzCTE7dHTqFM2$Y8H8E)^hisG8T41_wb>n0$$SKmsQ|R4X?YpBtsd+z7H3h zt`Uh}ZKxT-R)RP-JGBs^ml`VUGbqR}>8W`Z08*AL+N)zPD z_cT|VT3jcP=hPmSS|2mnA-=4s_GU$@)FS+(qzyjVw2J@H)CgnQ>Uj#x{Q;MREcrG$ zK-&pHxlQ;{sC;E}Z9sft2Z@Wbt|MzPfw;93{JCboI!63^e!RKPx);smJlFg{@FI!d z5PXY}vxUP(sxusBI!PM|_VKnBuj3M#?F4)A;w$|cy|ac~lN4C0c^9(vNJ96rpR@Z~ NMrC-9*UdK}`7d380xAFi delta 1925 zcmZ`(du&r>6u;;8_Cb5wm96c%ZewHZUzn#nSzq!`;K5~~_#AR)gx2IuD z1;tR)O5ABEYZbbgRz^DA<7tv9^W<5Eb)b>dtm{9ag`hhe@8U-X7LMR!qW0BdAyq{H zLEhfiV>FwsMt(|5cUN0Nbha8|6I<^mi0Wby%Q^uLco*X(@5%-DlA=RE)xiNO_~@sD z2D)Zb10)qVuj&Nd!Zx493r^5}#K)(8553q}Tni_2TZ;Puw&UhHpSy5;+)Q+~rY3SX zN=^f0aKgXTCivhT_?N$f!V-ZxIF~yRxT>;JwjXxmpJmOk7dM9LDbbo(31&l@dQ-_A z4`sl*k7AeP>MGWPZ8xV$<6z}s7qTez7dgVZmGlj`VpMxPrgI`Wt;vHkpBMdJy z9A&dJOJa&@CXQiKRR=&P9gRwVsbq?hUec#DD!06gl2dQ-#fEd3*TB`NtXH~% znqrphItoh03QE6j`b-R>(Y+5(CU{mG2Y};A`RgTzy;-lc5iO* z;Zzd|PKynKG`J3x$JYm{N~5jjlC{GCFZ=NS5HS`kDcQiBfKVJZR7;$IGM~ zl6<-Ra9LN&Z&uVTCd+k}51y=9cR)wpDDI@ojqugq)kOputj^@#4ageeYTNi1@^0F}1i zcx<*{S8NTQi_L+N+|Mx$>b{|nsHM>A7dVq7w0{xbN*2o-B2|SC8td{kx3IL7K{jIo zH#LN8BdmXodm3u32T82Q@rGsLOH5v7_?#ebkH?MfmUvu#+(kR{qo~ghx~{Oen&BYz mvm*2p-`PRBL(!ng5qv 0: rt_out["indicators"] = dict() @@ -410,6 +429,13 @@ class Strategy: #zatim takto odchycene identifikatory, ktere nemaji list, ale dict - do budoucna predelat na samostatny typ "indicators_static" except IndexError: pass + #populate cbar indicators + if len(self.state.cbar_indicators) > 0: + for key, value in self.state.cbar_indicators.items(): + try: + rt_out["indicators"][key]= value[-1] + except IndexError: + pass #same for static indicators if len(self.state.statinds) > 0: @@ -544,6 +570,7 @@ class StrategyState: self.bars = AttributeDict(bars) self.trades = AttributeDict(trades) self.indicators = AttributeDict(time=[]) + self.cbar_indicators = AttributeDict(time=[]) self.statinds = AttributeDict() #these methods can be overrided by StrategyType (to add or alter its functionality) self.buy = self.interface.buy diff --git a/v2realbot/utils/__pycache__/utils.cpython-310.pyc b/v2realbot/utils/__pycache__/utils.cpython-310.pyc index ed2020929da56bac9a2983cc370db6f57d240b34..6aed7ebb23d89eaf4ae998185a6925c722e03655 100644 GIT binary patch delta 1502 zcmZ{kZA@EL7{~8vOD%;W(9)LI-ocpbcyXgJCr$=zz<|NVP-AM`cGKsuU1)FXxdjH2 zun7@A;G7<344G3mVggY!f^qm3<4f2~j7F1vurbDHH1nHgpZsEco?Dmb2XB+#dCvcN z{^y?i+;h9p^G>fP>G8M~yl$kEaO-(bC0kmn+s;akLLrYp5s!>GpcuUKInibqmZ#Xj z>_b+jhPT01%sQbKwqbT3sUtSH2e!{UqU{hy_g3D9?j2BvN(T%>Jv7X-XeT@j_wpRr zX=Qgo6T0{RM~gr+Y6sBK!n<(I-EbeyyI~LSf%|7ITPr+(iyq8Mma-QfM5PznI77=m zXvbM!j{+UgIj=zHoGsc9`{BU65eq{Ya+cl3|R?B9C*v+&uqX6?MVDl++ z3~c2%D_5}2K0WGI@tW4sdO{uLhH7YPL`~>sT2mWRLQiR6a5W`3@VJ(yEKC_%LXVA2 zWsPc6q%qRmq&8%Oo&Q#MqnedlEcrLUy@+FoFcVsKsFb^Eyg|1}jvLT4qEGOrQ8#Uf zpi{(Ts_+H_xmMuKIf2qSIaD-v_ls2FpbA#ZL|pz{^b$hm5H-bdcSE*QCWiU1R+gegmJbp2y=up>!0~TtnUAo4cjxWNzTVWn=-`&878I29}Fx|n4M!3&JjMga_E5}^MW~4H%5s^&jX)eTN*{61vHo{e^6}@Q zj&Gv9HSZG?#ixYNtw#N+dfI(dO(#8{iR(DvM^Ql)(QN$BcS0vLd^DPuFi)nm*jOV! zofc#gZ^-T~gKR`z*z#lT$0%f+Da`=Q6m+jlUMv}p^H^F>8m6EP;}(~yoA?YR?YfZ` zArfPR6NEV7EWtX>R=XByYCZBriY^hZ5UiuWLJ_@;Y+J1^(}@$;Wqr*y{A`D7x*P>} K6}#k8P1WDQVS!x$ delta 1311 zcmZ{jU2KzO6vyAE>q^%T*xI-D1GlcFL&{`2HUWq1i+wQm!REZcH^FS~yo0V=JD;~G z5T%Y)C37lpl#hvtEG8@@YRo1qSyWIKlOJ({8szp-=Wo5_X(-mx}4HDz^z=FBI!FP@>liTfqa~T%lGAeRxcC zXP-ASTl9K}QPu_(u$8h6@C4Pz^m?k_2JxI#YlJ@74wX5fH9=qci{3I>pmh}~(5iPqo4$v(+(n0IhXds9h7Rh!rnZrK z5IRZifiCE#E{C884&{mztrsl)O8kZS70WF*9xGWEFJqO*FW$yB&*Y|Qnx$bS1cXP( z5&XsTn@Hl4ce}{oeQ&32lu~jGcdK>&a}+*w5gb;B#UX5uqSkUDvv^irtx9oOl96Ue zhF}f0e^5dI~3C8L9MPFEG|JN*Dkhj04>q!_qX~+y4xN+kC z21R+m`+5*y?Ip%rjP;Uup*+dLDXjJf*N$`=d;OJS4qx|Ih$)=)-xGImB2X#L;O7Cq z?Om>(#+5+S`z?hJ?Sx{nQIrd-kTdvqptRsUMZuClE%pZ=D|?^IBkPEAbb;_A2Z-+k zBhE7%e#p4Yr&sL>Lne$w>gZk8K4x6Sd~ig3Vh-nU$nRbu(XbFyA+Mousdz5*qHmUj zpEzS9*cGd=3ptCALWf0yo!-KXJf0d_{}goIv!!+79{x~zBz%Li zGRl}E{7h@TrCKEC<{QeM6t*uo|0TW>t`NWDrSOvTYZh)Y7Vt=UpmWJAVBBIXGO8K> z83$YbqGhNA-n4_pxweXhi^qBsZ5B+r|e|Flb2rt-Nz6JvyonWfJL>A$ilZ z%@4Nh95}_vfOkMi^Pf1Y?0Q#V}7i!;$$mT;ph#ah<{cjcj6=LpN{BGOmXF UxG%Pe-sJ9BtHrYcFUHFL0c@Q{dH?_b diff --git a/v2realbot/utils/utils.py b/v2realbot/utils/utils.py index c5c0d5e..5955aaf 100644 --- a/v2realbot/utils/utils.py +++ b/v2realbot/utils/utils.py @@ -131,6 +131,14 @@ def price2dec(price: float, decimals: int = 2) -> float: """ return round(price,decimals) if count_decimals(price) > decimals else price +def round2five(price: float): + """ + zatim jen na 3 mista -pripadne predelat na dynamicky + z 23.342 - 23.340 + z 23.346 - 23.345 + """ + return (round(price*100*2)/2)/100 + def count_decimals(number: float) -> int: """ Count the number of decimals in a given float: 1.4335 -> 4 or 3 -> 0