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 85a2755..25a29de 100644 Binary files a/v2realbot/__pycache__/config.cpython-310.pyc and b/v2realbot/__pycache__/config.cpython-310.pyc differ diff --git a/v2realbot/common/__pycache__/model.cpython-310.pyc b/v2realbot/common/__pycache__/model.cpython-310.pyc index 15f04b6..59c7cad 100644 Binary files a/v2realbot/common/__pycache__/model.cpython-310.pyc and b/v2realbot/common/__pycache__/model.cpython-310.pyc differ diff --git a/v2realbot/common/model.py b/v2realbot/common/model.py index 7826f1d..1887059 100644 --- a/v2realbot/common/model.py +++ b/v2realbot/common/model.py @@ -194,7 +194,7 @@ class RunArchiveDetail(BaseModel): name: str bars: dict #trades: Optional[dict] - indicators: dict + indicators: List[dict] statinds: dict trades: List[TradeUpdate] diff --git a/v2realbot/config.py b/v2realbot/config.py index 2baf679..de82591 100644 --- a/v2realbot/config.py +++ b/v2realbot/config.py @@ -6,7 +6,7 @@ from appdirs import user_data_dir NORMALIZED_TICK_BASE_PRICE = 30.00 LOG_RUNNER_EVENTS = False #no print in console -QUIET_MODE = True +QUIET_MODE = False #how many consecutive trades with the fill price are necessary for LIMIT fill to happen in backtesting #0 - optimistic, every knot high will fill the order #N - N consecutive trades required diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 6d82423..6c8daf8 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -458,6 +458,8 @@ def archive_runner(runner: Runner, strat: StrategyInstance): #flatten indicators from numpy array flattened_indicators = {} + #pole indicatoru, kazdy ma svoji casovou osu time + flattened_indicators_list = [] for key, value in strat.state.indicators.items(): if isinstance(value, ndarray): #print("is numpy", key,value) @@ -466,11 +468,22 @@ def archive_runner(runner: Runner, strat: StrategyInstance): else: #print("is not numpy", key, value) flattened_indicators[key]= value + flattened_indicators_list.append(flattened_indicators) + flattened_indicators = {} + for key, value in strat.state.cbar_indicators.items(): + if isinstance(value, ndarray): + #print("is numpy", key,value) + flattened_indicators[key]= value.tolist() + #print("changed numpy:",value.tolist()) + else: + #print("is not numpy", key, value) + flattened_indicators[key]= value + flattened_indicators_list.append(flattened_indicators) runArchiveDetail: RunArchiveDetail = RunArchiveDetail(id = runner.id, name=runner.run_name, bars=strat.state.bars, - indicators=flattened_indicators, + indicators=flattened_indicators_list, statinds=strat.state.statinds, trades=strat.state.tradeList) resh = db_arch_h.insert(runArchive.__dict__) diff --git a/v2realbot/loader/__pycache__/aggregator.cpython-310.pyc b/v2realbot/loader/__pycache__/aggregator.cpython-310.pyc index 6b36c26..4380db8 100644 Binary files a/v2realbot/loader/__pycache__/aggregator.cpython-310.pyc and b/v2realbot/loader/__pycache__/aggregator.cpython-310.pyc differ 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 801a362..8d53d29 100644 Binary files a/v2realbot/strategy/__pycache__/base.cpython-310.pyc and b/v2realbot/strategy/__pycache__/base.cpython-310.pyc differ diff --git a/v2realbot/strategy/base.py b/v2realbot/strategy/base.py index 3648b9f..3f58a39 100644 --- a/v2realbot/strategy/base.py +++ b/v2realbot/strategy/base.py @@ -138,11 +138,12 @@ class Strategy: def save_item_history(self,item): if self.rectype == RecordType.BAR: #jako cas indikatorů pridavame cas baru a inicialni hodnoty vsech indikatoru - self.state.indicators['time'].append(item['time']) + for key in self.state.indicators: if key == 'time': - continue - self.state.indicators[key].append(0) + self.state.indicators['time'].append(item['updated']) + else: + self.state.indicators[key].append(0) self.append_bar(self.state.bars,item) elif self.rectype == RecordType.TRADE: pass @@ -150,25 +151,42 @@ class Strategy: #self.state.indicators['time'].append(datetime.fromtimestamp(self.state.last_trade_time)) #self.append_trade(self.state.trades,item) elif self.rectype == RecordType.CBAR: - #novy vzdy pridame if self.nextnew: - self.state.indicators['time'].append(item['updated']) + #standardni identifikatory - populace hist zaznamu pouze v novem baru (dale se deji jen udpaty) for key in self.state.indicators: if key == 'time': - continue - self.state.indicators[key].append(0) + self.state.indicators['time'].append(item['time']) + else: + self.state.indicators[key].append(0) + #cbar indikatory populace v kazde iteraci + for key in self.state.cbar_indicators: + if key == 'time': + self.state.cbar_indicators['time'].append(item['updated']) + else: + self.state.cbar_indicators[key].append(0) + + #populujeme i novy bar v historii self.append_bar(self.state.bars,item) self.nextnew = 0 #nasledujici updatneme, po potvrzeni, nasleduje novy bar + #nasledujici identifikatory v ramci cbaru take pridavame + # (udrzujeme historii prubehu identifikatoru v ramci cbaru) else: + #bary updatujeme, pridavame jen prvni + self.replace_prev_bar(self.state.bars,item) + + #u cbar indikatoru, pridavame kazdou zmenu ceny, krome potvrzeneho baru + if item['confirmed'] == 0: - self.state.indicators['time'][-1]=item['updated'] - self.replace_prev_bar(self.state.bars,item) - #confirmed + #v naslednych updatech baru inicializujeme vzdy jen cbar indikatory + for key in self.state.cbar_indicators: + if key == 'time': + self.state.cbar_indicators['time'].append(item['updated']) + else: + self.state.cbar_indicators[key].append(0) else: - self.state.indicators['time'][-1]=item['updated'] - self.replace_prev_bar(self.state.bars,item) + #pokud je potvrzeny, pak nenese nikdy zmenu ceny, nepridavame zaznam nic self.nextnew = 1 """"refresh positions and avgp - for CBAR once per confirmed, for BARS each time""" @@ -395,11 +413,12 @@ class Strategy: ##posilame dict s objekty: bars, trades podle cbaru, a dale indicators naplnene time a pripadnymi identifikatory (EMA) if self.rtqueue is not None: rt_out = dict() - + if self.rectype == RecordType.BAR or self.rectype == RecordType.CBAR: rt_out["bars"] = item else: rt_out["trades"] = item + #get only last values from indicators, if there are any indicators present if len(self.state.indicators) > 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 ed20209..6aed7eb 100644 Binary files a/v2realbot/utils/__pycache__/utils.cpython-310.pyc and b/v2realbot/utils/__pycache__/utils.cpython-310.pyc differ 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