From 3158cdb68b2a4052b701cffdec64b3ce025ad8c0 Mon Sep 17 00:00:00 2001 From: David Brazda Date: Mon, 11 Dec 2023 19:24:06 +0100 Subject: [PATCH] tick based support including gui preview, custom suppoer, new classed tickbased inds,#85 --- v2realbot/ENTRY_ClassicSL_v01.py | 3 +- v2realbot/controller/services.py | 132 ++++++++++++++---- v2realbot/main.py | 12 +- v2realbot/static/js/archivechart.js | 44 ++++-- v2realbot/static/js/instantindicators.js | 26 ++-- v2realbot/static/js/utils/utils.js | 45 ++++-- .../indicators/custom/classed.py | 31 ++-- .../indicators/custom/classes/CUSUM.py | 3 +- .../custom/classes/TickTimeBasedROC.py | 37 +++++ .../indicators/custom/classes/TickVariance.py | 34 +++++ .../strategyblocks/indicators/custom/rsi.py | 33 +++++ .../strategyblocks/indicators/custom/slope.py | 51 +++++-- .../strategyblocks/indicators/custom_hub.py | 34 +++-- .../strategyblocks/indicators/helpers.py | 12 +- .../indicators/indicators_hub.py | 7 +- .../strategyblocks/inits/init_indicators.py | 22 ++- v2realbot/utils/utils.py | 27 +++- 17 files changed, 431 insertions(+), 122 deletions(-) create mode 100644 v2realbot/strategyblocks/indicators/custom/classes/TickTimeBasedROC.py create mode 100644 v2realbot/strategyblocks/indicators/custom/classes/TickVariance.py create mode 100644 v2realbot/strategyblocks/indicators/custom/rsi.py diff --git a/v2realbot/ENTRY_ClassicSL_v01.py b/v2realbot/ENTRY_ClassicSL_v01.py index 1e5f7a4..66c84d5 100644 --- a/v2realbot/ENTRY_ClassicSL_v01.py +++ b/v2realbot/ENTRY_ClassicSL_v01.py @@ -123,10 +123,11 @@ def init(state: StrategyState): intialize_directive_conditions(state) #intitialize indicator mapping (for use in operation) - mozna presunout do samostatne funkce prip dat do base kdyz se osvedci + local_dict_cbar_inds = {key: state.cbar_indicators[key] for key in state.cbar_indicators.keys() if key != "time"} local_dict_inds = {key: state.indicators[key] for key in state.indicators.keys() if key != "time"} local_dict_bars = {key: state.bars[key] for key in state.bars.keys() if key != "time"} - state.ind_mapping = {**local_dict_inds, **local_dict_bars} + state.ind_mapping = {**local_dict_inds, **local_dict_bars, **local_dict_cbar_inds} print("IND MAPPING DONE:", state.ind_mapping) #30 DAYS historicall data fill - pridat do base pokud se osvedci diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 03114a0..43c5f7e 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -1453,6 +1453,10 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = subtype = safe_get(toml_parsed, 'subtype', False) if subtype is None: return (-2, "subtype invalid") + + output = safe_get(toml_parsed, 'output', "bar") + if output is None: + return (-2, "output invalid (bar/tick)") custom_params = safe_get(toml_parsed, "cp", None) print("custom params",custom_params) @@ -1466,8 +1470,12 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = #print("toto jsme si dotahnuli", detail.bars) #pokud tento indikator jiz je v detailu, tak ho odmazeme + #BAR indikatory if indicator.name in detail.indicators[0]: del detail.indicators[0][indicator.name] + #CBAR indikatory + elif indicator.name in detail.indicators[1]: + del detail.indicators[1][indicator.name] #new dicts @@ -1477,6 +1485,8 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = new_data= AttributeDict(**new_data) new_inds = {key: [] for key in detail.indicators[0].keys()} new_inds = AttributeDict(**new_inds) + new_tick_inds = {key: [] for key in detail.indicators[1].keys()} + new_tick_inds = AttributeDict(**new_tick_inds) interface = BacktestInterface(symbol="X", bt=None) ##dame nastaveni indikatoru do tvaru, ktery stratvars ocekava (pro dynmaicke inicializace) @@ -1485,12 +1495,20 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = state = StrategyState(name="XX", symbol = "X", stratvars = AttributeDict(**stratvars), interface=interface) - #inicializujeme novy indikator v cilovem dict a stavovem inds. - new_inds[indicator.name] = [] - new_inds[indicator.name] = [] - - state.bars = new_bars - state.indicators = new_inds + #inicializujeme stavove promenne a novy indikator v cilovem dict + if output == "bar": + state.bars = new_bars + state.indicators = new_inds + state.cbar_indicators = detail.indicators[1] #mozna toto vubec neopotrebujeme + new_inds[indicator.name] = [] + new_inds[indicator.name] = [] + #pro tick, nechavame bary a nechavame volne pouze tickbary, nad kterymi iterujeme + else: + state.bars = new_bars + state.indicators = new_inds + state.cbar_indicators = new_tick_inds + new_tick_inds[indicator.name] = [] + new_tick_inds[indicator.name] = [] #pridavame dailyBars z extData if hasattr(detail, "ext_data") and "dailyBars" in detail.ext_data: @@ -1499,9 +1517,10 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = print("delka",len(detail.bars["close"])) #intitialize indicator mapping - in order to use eval in expression + local_dict_cbar_inds = {key: state.cbar_indicators[key] for key in state.cbar_indicators.keys() if key != "time"} local_dict_inds = {key: state.indicators[key] for key in state.indicators.keys() if key != "time"} local_dict_bars = {key: state.bars[key] for key in state.bars.keys() if key != "time"} - state.ind_mapping = {**local_dict_inds, **local_dict_bars} + state.ind_mapping = {**local_dict_inds, **local_dict_bars, **local_dict_cbar_inds} print("IND MAPPING DONE:", state.ind_mapping) ##intialize dynamic indicators @@ -1513,34 +1532,81 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = # print("funkce", function) # custom_function = eval(function) - #iterujeme nad bary a on the fly pridavame novou hodnotu do vsech indikatoru a nakonec nad tim spustime indikator - #tak muzeme v toml pouzit i hodnoty ostatnich indikatoru - for i in range(len(detail.bars["close"])): - for key in detail.bars: - state.bars[key].append(detail.bars[key][i]) - #naplnime i data aktualne - new_data[key] = state.bars[key][-1] - for key in detail.indicators[0]: - state.indicators[key].append(detail.indicators[0][key][i]) + #pokud jde o bar, tak cbary nemennime, nechavame je inicializovane plnou hodnotou pro pripadne dotazeni + if output == "bar": + #iterujeme nad bary a on the fly pridavame novou hodnotu do vsech indikatoru a nakonec nad tim spustime indikator + #tak muzeme v toml pouzit i hodnoty ostatnich indikatoru + for i in range(len(detail.bars["close"])): + for key in detail.bars: + state.bars[key].append(detail.bars[key][i]) + #naplnime i data aktualne + new_data[key] = state.bars[key][-1] + for key in detail.indicators[0]: + state.indicators[key].append(detail.indicators[0][key][i]) - #inicializujeme 0 v novém indikatoru - state.indicators[indicator.name].append(0) + #inicializujeme 0 v novém indikatoru + state.indicators[indicator.name].append(0) - try: - populate_dynamic_indicators(new_data, state) - # res_code, new_val = custom_function(state, custom_params) - # if res_code == 0: - # new_inds[indicator.name][-1]=new_val - except Exception as e: - print(str(e) + format_exc()) + try: + populate_dynamic_indicators(new_data, state) + # res_code, new_val = custom_function(state, custom_params) + # if res_code == 0: + # new_inds[indicator.name][-1]=new_val + except Exception as e: + print(str(e) + format_exc()) - #print("Done", state.indicators[indicator.name]) - - new_inds[indicator.name] = state.indicators[indicator.name] + #print("Done", state.indicators[indicator.name]) - #ukládáme do ArchRunneru - detail.indicators[0][indicator.name] = new_inds[indicator.name] + new_inds[indicator.name] = state.indicators[indicator.name] + + #ukládáme do ArchRunneru + detail.indicators[0][indicator.name] = new_inds[indicator.name] + + #TOTOZNE PRO TICK INDICATOR + else: + #iterujeme nad bary a on the fly pridavame novou hodnotu do vsech indikatoru a nakonec nad tim spustime indikator + #OMEZENI ZATIM pro preview jde velmi omezeně používat bary - OTESTOVAT poradne + new_data = dict(close=0,confirmed=0,high=0,hlcc4=0,index=0,low=0,open=0,resolution=0,time=0,trades=0,updated=0,volume=0,vwap=0) + bar_idx = 0 + max_bar_idx = len(detail.bars["time"]) + for i in range(len(detail.indicators[1]["time"])): + for key in detail.indicators[1]: + state.cbar_indicators[key].append(detail.indicators[1][key][i]) + match key: + case "time": + #pokud existuje bar a indikator s mensim casem vkladame je + if bar_idx < max_bar_idx and detail.indicators[1][key][i] >= detail.bars[key][bar_idx]: + for bar_key in detail.bars: + state.bars[bar_key].append(detail.bars[bar_key][bar_idx]) + #naplnime i data aktualne + new_data[bar_key] = state.bars[bar_key][-1] + for ind_key in detail.indicators[0]: + state.indicators[ind_key].append(detail.indicators[0][ind_key][bar_idx]) + bar_idx += 1 + case "tick_price": + new_data['close'] = detail.indicators[1][key][i] + # case "tick_volume": + # new_data['volume'] = detail.indicators[1][key][i] + + #inicializujeme 0 v novém indikatoru + state.cbar_indicators[indicator.name].append(0) + + try: + populate_dynamic_indicators(new_data, state) + # res_code, new_val = custom_function(state, custom_params) + # if res_code == 0: + # new_inds[indicator.name][-1]=new_val + except Exception as e: + print(str(e) + format_exc()) + + #print("Done", state.indicators[indicator.name]) + + new_tick_inds[indicator.name] = state.cbar_indicators[indicator.name] + + #ukládáme do ArchRunneru + detail.indicators[1][indicator.name] = new_tick_inds[indicator.name] + #do ext dat ukladame jmeno indikatoru (podle toho oznacuje jako zmenene) @@ -1565,7 +1631,11 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = if res == 0: print(f"arch runner {id} updated") - return 0, new_inds[indicator.name] + #vracime list, kde pozice 0 je bar indicators, pozice 1 je ticks indicators + if output == "bar": + return 0, [new_inds[indicator.name], []] + else: + return 0, [[], new_tick_inds[indicator.name]] except Exception as e: print(str(e) + format_exc()) diff --git a/v2realbot/main.py b/v2realbot/main.py index 449f8d1..e1b39b3 100644 --- a/v2realbot/main.py +++ b/v2realbot/main.py @@ -480,10 +480,11 @@ def _delete_archived_runners_byBatchID(batch_id: str): raise HTTPException(status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"Error not changed: {res}:{batch_id}:{id}") -#WIP - TOM indicator preview from frontend -#return indicator value for archived runner +#WIP - TOM indicator preview from frontend f +#return indicator value for archived runner, return values list0 - bar indicators, list1 - ticks indicators +#TBD mozna predelat na dict pro prehlednost @app.put("/archived_runners/{runner_id}/previewindicator", dependencies=[Depends(api_key_auth)], status_code=status.HTTP_200_OK) -def _preview_indicator_byTOML(runner_id: UUID, indicator: InstantIndicator) -> list[float]: +def _preview_indicator_byTOML(runner_id: UUID, indicator: InstantIndicator) -> list[list[float]]: #mozna pak pridat name res, vals = cs.preview_indicator_byTOML(id=runner_id, indicator=indicator) if res == 0: return vals @@ -927,10 +928,5 @@ if __name__ == "__main__": print("closing insert_conn connection") insert_conn.close() print("closed") -##TODO pridat moznost behu na PAPER a LIVE per strategie -# zjistit zda order notification websocket muze bezet na obou soucasne -# pokud ne, mohl bych vyuzivat jen zive data -# a pro paper trading(live interface) a notifications bych pouzival separatni paper ucet -# to by asi slo diff --git a/v2realbot/static/js/archivechart.js b/v2realbot/static/js/archivechart.js index 93e7296..d8196e3 100644 --- a/v2realbot/static/js/archivechart.js +++ b/v2realbot/static/js/archivechart.js @@ -367,6 +367,7 @@ function prepare_data(archRunner, timeframe_amount, timeframe_unit, archivedRunn //pomocna sluzba pro naplneni indListu a charting indikatoru function chart_indicators(data, visible, offset) { + console.log(data) //console.log("indikatory", JSON.stringify(data.indicators,null,2)) //podobne v livewebsokcets.js - dat do jedne funkce if (data.hasOwnProperty("indicators")) { @@ -389,6 +390,8 @@ function chart_indicators(data, visible, offset) { indicatorList.forEach((indicators, index, array) => { //var indicators = data.indicators + //index 0 - bar indikatory + //index 1 - tick based indikatory //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)) { @@ -435,7 +438,7 @@ function chart_indicators(data, visible, offset) { // } //initialize indicator and store reference to array - var obj = {name: key, series: null, cnf:cnf, instant: instant} + var obj = {name: key, type: index, series: null, cnf:cnf, instant: instant} //start //console.log(key) @@ -619,19 +622,38 @@ function chart_indicators(data, visible, offset) { }) } + //sort by type first (0-bar,1-cbar inds) and then alphabetically indList.sort((a, b) => { - const nameA = a.name.toUpperCase(); // ignore upper and lowercase - const nameB = b.name.toUpperCase(); // ignore upper and lowercase - if (nameA < nameB) { - return -1; + if (a.type !== b.type) { + return a.type - b.type; + } else { + let nameA = a.name.toUpperCase(); + let nameB = b.name.toUpperCase(); + if (nameA < nameB) { + return -1; + } else if (nameA > nameB) { + return 1; + } else { + // If uppercase names are equal, compare original names to prioritize uppercase + return a.name < b.name ? -1 : 1; + } } - if (nameA > nameB) { - return 1; - } - // names must be equal - return 0; - }); + + //puvodni funkce + // indList.sort((a, b) => { + // const nameA = a.name.toUpperCase(); // ignore upper and lowercase + // const nameB = b.name.toUpperCase(); // ignore upper and lowercase + // if (nameA < nameB) { + // return -1; + // } + // if (nameA > nameB) { + // return 1; + // } + // // names must be equal + // return 0; + + // }); //vwap a volume zatim jen v detailnim zobrazeni if (!offset) { //display vwap and volume diff --git a/v2realbot/static/js/instantindicators.js b/v2realbot/static/js/instantindicators.js index a15ddef..065893f 100644 --- a/v2realbot/static/js/instantindicators.js +++ b/v2realbot/static/js/instantindicators.js @@ -56,13 +56,16 @@ $(document).ready(function () { if (archData.indicators[0][indname]) { delete archData.indicators[0][indname] - //delete addedInds[indname] - //get active resolution - const element = document.querySelector('.switcher-active-item'); - resolution = element.textContent - //console.log("aktivni rozliseni", resolution) - switch_to_interval(resolution, archData) } + else if (archData.indicators[1][indname]) { + delete archData.indicators[1][indname] + } + //delete addedInds[indname] + //get active resolution + const element = document.querySelector('.switcher-active-item'); + resolution = element.textContent + //console.log("aktivni rozliseni", resolution) + switch_to_interval(resolution, archData) }, error: function(xhr, status, error) { var err = eval("(" + xhr.responseText + ")"); @@ -142,11 +145,18 @@ $(document).ready(function () { success:function(data){ //kod pro update/vytvoreni je zde stejny - updatujeme jen zdrojove dictionary window.$('#indicatorModal').modal('hide'); - //console.log(data) + console.log("navrat",data) //indName = $('#indicatorName').val() //updatneme/vytvorime klic v globalni promennou obsahujici vsechny arch data //TBD nebude fungovat az budu mit vic chartů otevřených - předělat - archData.indicators[0][indName] = data + if (data[0].length > 0) { + archData.indicators[0][indName] = data[0] + } else if (data[1].length > 0) { + archData.indicators[1][indName] = data[1] + } + else { + alert("neco spatne s response ", data) + } //pridame pripadne upatneme v ext_data diff --git a/v2realbot/static/js/utils/utils.js b/v2realbot/static/js/utils/utils.js index 7c6118e..c587b9e 100644 --- a/v2realbot/static/js/utils/utils.js +++ b/v2realbot/static/js/utils/utils.js @@ -657,24 +657,41 @@ function populate_indicator_buttons(def) { buttonElement.id = "indicatorsButtons" buttonElement.classList.add('switcher'); + //incializujeme i bar pro cbar indikator sekci + var tickButtonElement = document.createElement('div'); + tickButtonElement.id = "tickIndicatorsButtons" + tickButtonElement.classList.add('tickButtons'); + //iterace nad indikatory a vytvareni buttonků indList.forEach(function (item, index) { - index_ind = index - active = false + index_ind = index + active = false - //console.log("activatedButtons", activatedButtons) - //console.log("obsahuje item.name", activatedButtons.includes(item.name), item.name) - //pokud existuje v aktivnich pak - //console.log("vytvarime button",item.name,activatedButtons) - if ((activatedButtons) && (activatedButtons.includes(item.name))) { - active = true - } - //vytvoreni buttonku - itemEl = create_indicator_button(item, index, def||active); - //prirazeni do divu - buttonElement.appendChild(itemEl); ; - }); + //console.log("activatedButtons", activatedButtons) + //console.log("obsahuje item.name", activatedButtons.includes(item.name), item.name) + //pokud existuje v aktivnich pak + //console.log("vytvarime button",item.name,activatedButtons) + if ((activatedButtons) && (activatedButtons.includes(item.name))) { + active = true + } + //bar indikatory jsou serazeny na zacarku + if (item.type == 0) { + //vytvoreni buttonku + itemEl = create_indicator_button(item, index, def||active); + //prirazeni do divu + buttonElement.appendChild(itemEl); + } + //ted zbyvaji tick barové a ty dáme do separátního divu + else + { + //vytvoreni buttonku + itemEl = create_indicator_button(item, index, def||active); + tickButtonElement.appendChild(itemEl) + } + }); + //nakonec pripojime cely div s tick based indicatory + buttonElement.appendChild(tickButtonElement); var funcButtonElement = document.createElement('div'); funcButtonElement.id = "funcIndicatorsButtons" diff --git a/v2realbot/strategyblocks/indicators/custom/classed.py b/v2realbot/strategyblocks/indicators/custom/classed.py index bc3798d..ecb3587 100644 --- a/v2realbot/strategyblocks/indicators/custom/classed.py +++ b/v2realbot/strategyblocks/indicators/custom/classed.py @@ -30,12 +30,26 @@ def classed(state, params, name): init_params = safe_get(params, "init", None) #napr sekce obcahuje threshold = 1222, ktere jdou kwargs do initu fce #next_params = safe_get(params, "next", None) - source = safe_get(params, "source", None) #source, ktery jde do initu - source = get_source_series(state, source) - #lookback = int(value_or_indicator(state, lookback)) + #List of sources, ktere jde do nextu (muze jit i vice serie) + #Do nextu jde ve stejnojmenném parametru + next_sources = safe_get(params, "next", []) #this will map to the sources_dict + next_mapping = safe_get(params, "next_mapping", next_sources) #this will dictate the final name of the key in sources_dict + + #ukládáme si do cache incializaci + cache = safe_get(params, "CACHE", None) + if cache is None: + if len(next_sources) != len(next_mapping): + return -2, "next and next_mapping length must be the same" + # Vytvorime dictionary pro kazdy source a priradime serii + #source_dict = {name: get_source_series(state, name) for name in next_sources} + #TBD toto optimalizovat aby se nevolalo pri kazde iteraci + source_dict = {new_key: get_source_series(state, name) + for name, new_key in zip(next_sources, next_mapping)} + params["CACHE"] = {} + params["CACHE"]["source_dict"] = source_dict + else: + source_dict = params["CACHE"]["source_dict"] - #class_next_params = safe_get(params, "class_next_params", None) - try: if name not in state.classed_indicators: classname = name @@ -46,8 +60,8 @@ def classed(state, params, name): state.classed_indicators[name] = instance state.ilog(lvl=1,e=f"IND CLASS {name} INITIALIZED", **params) - if source is not None: - val = state.classed_indicators[name].next(source[-1]) + if len(source_dict) >0: + val = state.classed_indicators[name].next(**source_dict) else: val = state.classed_indicators[name].next() @@ -56,5 +70,4 @@ def classed(state, params, name): except Exception as e: printanyway(str(e)+format_exc()) - return -2, str(e)+format_exc() - + return -2, str(e)+format_exc() \ No newline at end of file diff --git a/v2realbot/strategyblocks/indicators/custom/classes/CUSUM.py b/v2realbot/strategyblocks/indicators/custom/classes/CUSUM.py index bfda46d..404a468 100644 --- a/v2realbot/strategyblocks/indicators/custom/classes/CUSUM.py +++ b/v2realbot/strategyblocks/indicators/custom/classes/CUSUM.py @@ -7,7 +7,8 @@ class CUSUM(IndicatorBase): self.cumulative_sum = 0 self.previous_price = None - def next(self, new_price): + def next(self, close): + new_price = close[-1] if self.previous_price is None: # First data point, no previous price to compare with self.previous_price = new_price diff --git a/v2realbot/strategyblocks/indicators/custom/classes/TickTimeBasedROC.py b/v2realbot/strategyblocks/indicators/custom/classes/TickTimeBasedROC.py new file mode 100644 index 0000000..115be99 --- /dev/null +++ b/v2realbot/strategyblocks/indicators/custom/classes/TickTimeBasedROC.py @@ -0,0 +1,37 @@ +from collections import deque +#import time +from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase + +class TickTimeBasedROC(IndicatorBase): + def __init__(self, state, window_size_seconds=5): + """ + Initialize the TimeBasedROC class. + :param window_size_seconds: Window size in seconds for the rate of change. + """ + super().__init__(state) + self.window_size_seconds = window_size_seconds + self.tick_data = deque() # Efficient deque for (timestamp, price) + + def next(self, time, close): + """ + Update the ROC with a new tick time and price. + :param new_time: Timestamp of the new tick (float with up to 6 decimals). + :param new_price: Price of the new tick. + :return: The updated ROC value, or None if the window is not yet full. + """ + new_time = time[-1] + new_price = close[-1] + # Add new tick data + self.tick_data.append((new_time, new_price)) + + # Remove old data outside the time window efficiently + while self.tick_data and new_time - self.tick_data[0][0] > self.window_size_seconds: + self.tick_data.popleft() + + if len(self.tick_data) >= 2: + # Compute ROC using the earliest and latest prices in the window + old_time, old_price = self.tick_data[0] + roc = ((new_price - old_price) / old_price) * 100 if old_price != 0 else 0 + return round(float(roc),5) + else: + return 0 # ROC is undefined until the window has enough data diff --git a/v2realbot/strategyblocks/indicators/custom/classes/TickVariance.py b/v2realbot/strategyblocks/indicators/custom/classes/TickVariance.py new file mode 100644 index 0000000..d0b657b --- /dev/null +++ b/v2realbot/strategyblocks/indicators/custom/classes/TickVariance.py @@ -0,0 +1,34 @@ +import numpy as np +from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase + +#usecase - pocitat variance ticku +# v ramci BARu - posilame sem index a resetujeme pri naslednem indxu +# do budoucna mo +class TickVariance(IndicatorBase): + def __init__(self, state, window_size=1): + """ + Initialize the TickPriceVariance class. + + :param window_size: The size of the window for calculating variance - zatim mame jeden bar, do budoucna X + """ + super().__init__(state) + self.window_size = window_size + self.window_prices = [] + self.prev_index = None + + def next(self, close, index): + close = close[-1] + index = index[-1] + # Add new price to the window + self.window_prices.append(close) + + if self.prev_index is not None and self.prev_index != index: + self.window_prices = [] + + self.prev_index = index + # Calculate the variance for the current window + if len(self.window_prices) > 1: + return round(float(np.var(self.window_prices)),5) + else: + return 0 # Variance is undefined for a single data point + diff --git a/v2realbot/strategyblocks/indicators/custom/rsi.py b/v2realbot/strategyblocks/indicators/custom/rsi.py new file mode 100644 index 0000000..ebcfbfb --- /dev/null +++ b/v2realbot/strategyblocks/indicators/custom/rsi.py @@ -0,0 +1,33 @@ +from v2realbot.utils.utils import isrising, isfalling,zoneNY, price2dec, print, safe_get, is_still, is_window_open, eval_cond_dict, crossed_down, crossed_up, crossed, is_pivot, json_serial, pct_diff, create_new_bars, slice_dict_lists +from v2realbot.strategy.base import StrategyState +from v2realbot.indicators.indicators import ema as ext_ema +from v2realbot.strategyblocks.indicators.helpers import get_source_series +from rich import print as printanyway +from traceback import format_exc +import numpy as np +from v2realbot.indicators.oscillators import rsi as ind_rsi +from collections import defaultdict +from v2realbot.strategyblocks.indicators.helpers import value_or_indicator +#strength, absolute change of parameter between current value and lookback value (n-past) +#used for example to measure unusual peaks +def rsi(state, params, name): + req_source = safe_get(params, "source", "vwap") + rsi_length = safe_get(params, "length",14) + start = safe_get(params, "start","linear") #linear/sharp + + #lookback muze byt odkaz na indikator, pak berem jeho hodnotu + rsi_length = int(value_or_indicator(state, rsi_length)) + source = get_source_series(state, req_source) + delka = len(source) + + if delka > rsi_length or start == "linear": + if delka <= rsi_length and start == "linear": + rsi_length = delka + + rsi_res = ind_rsi(source, rsi_length) + val = rsi_res[-1] if np.isfinite(rsi_res[-1]) else 0 + return 0, round(val,4) + + else: + state.ilog(lvl=0,e=f"IND {name} RSI necháváme 0", message="not enough source data", source=source, rsi_length=rsi_length) + return -2, "necháváma 0 nedostatek hodnot" diff --git a/v2realbot/strategyblocks/indicators/custom/slope.py b/v2realbot/strategyblocks/indicators/custom/slope.py index d3bcd54..07d9e52 100644 --- a/v2realbot/strategyblocks/indicators/custom/slope.py +++ b/v2realbot/strategyblocks/indicators/custom/slope.py @@ -13,17 +13,31 @@ def slope(state, params, name): source = safe_get(params, "source", None) source_series = get_source_series(state, source) + lookback_type = safe_get(params, "lookback_type", "positions") lookback = safe_get(params, "lookback", 5) - lookback_priceline = safe_get(params, "lookback_priceline", None) + lookback_priceline = safe_get(params, "lookback_priceline", None) #bars|close lookback_series = get_source_series(state, lookback_priceline) - try: - lookbackprice = lookback_series[-lookback-1] - lookbacktime = state.bars.updated[-lookback-1] - except IndexError: - max_delka = len(lookback_series) - lookbackprice =lookback_series[-max_delka] - lookbacktime = state.bars.updated[-max_delka] + match lookback_type: + case "positions": + try: + lookbackprice = lookback_series[-lookback-1] + lookbacktime = state.bars.updated[-lookback-1] + except IndexError: + max_delka = len(lookback_series) + lookbackprice =lookback_series[-max_delka] + lookbacktime = state.bars.updated[-max_delka] + case "seconds": + #předpokládáme, že lookback_priceline je ve formě #bars|close + #abychom ziskali relevantní time + split_index = lookback_priceline.find("|") + if split_index == -1: + return -2, "for time it is required in format bars|close" + dict_name = lookback_priceline[:split_index] + time_series = getattr(state, dict_name)["time"] + lookback_idx = find_index_optimized(time_list=time_series, seconds=lookback) + lookbackprice = lookback_series[lookback_idx] + lookbacktime = time_series[lookback_idx] #výpočet úhlu - a jeho normalizace currval = source_series[-1] @@ -32,3 +46,24 @@ def slope(state, params, name): state.ilog(lvl=1,e=f"INSIDE {name}:{funcName} {slope} {source=} {lookback=}", currval_source=currval, lookbackprice=lookbackprice, lookbacktime=lookbacktime, **params) return 0, slope + +""" +TODO pripadne dat do +Finds index of first value less than X seconds +This version assumes: +time_list is always non-empty and sorted. +There's always a timestamp at least 5 seconds before the current time. +""" +def find_index_optimized(time_list, seconds): + current_time = time_list[-1] + threshold = current_time - seconds + left, right = 0, len(time_list) - 1 + + while left < right: + mid = (left + right) // 2 + if time_list[mid] < threshold: + left = mid + 1 + else: + right = mid + + return left if time_list[left] >= threshold else None \ No newline at end of file diff --git a/v2realbot/strategyblocks/indicators/custom_hub.py b/v2realbot/strategyblocks/indicators/custom_hub.py index d9a3b8f..3d2090d 100644 --- a/v2realbot/strategyblocks/indicators/custom_hub.py +++ b/v2realbot/strategyblocks/indicators/custom_hub.py @@ -27,6 +27,16 @@ def populate_dynamic_custom_indicator(data, state: StrategyState, name): #if MA is required MA_length = safe_get(options, "MA_length", None) + output = safe_get(options, "output", "bar") + match output: + case "bar": + indicators_dict = state.indicators + case "tick": + indicators_dict = state.cbar_indicators + case _: + state.ilog(lvl=1,e=f"Output must be bar or tick for {name} in stratvars") + return + active = safe_get(options, 'active', True) if not active: return @@ -121,7 +131,7 @@ def populate_dynamic_custom_indicator(data, state: StrategyState, name): if should_run: #TODO get custom params custom_params = safe_get(options, "cp", None) - #vyplnime last_run_time a last_run_index + #vyplnime last_run_time a last_run_index do stratvars state.vars.indicators[name]["last_run_time"] = datetime.fromtimestamp(data["updated"]).astimezone(zoneNY) state.vars.indicators[name]["last_run_index"] = data["index"] @@ -135,14 +145,14 @@ def populate_dynamic_custom_indicator(data, state: StrategyState, name): custom_function = eval(subtype) res_code, new_val = custom_function(state, custom_params, name) if res_code == 0: - state.indicators[name][-1-save_to_past]=new_val + indicators_dict[name][-1-save_to_past]=new_val state.ilog(lvl=1,e=f"IND {name} {subtype} VAL FROM FUNCTION: {new_val}", lastruntime=state.vars.indicators[name]["last_run_time"], lastrunindex=state.vars.indicators[name]["last_run_index"], save_to_past=save_to_past) #prepocitame MA if required if MA_length is not None: - src = state.indicators[name][-MA_length:] + src = indicators_dict[name][-MA_length:] MA_res = ema(src, MA_length) MA_value = round(MA_res[-1],7) - state.indicators[name+"MA"][-1-save_to_past]=MA_value + indicators_dict[name+"MA"][-1-save_to_past]=MA_value state.ilog(lvl=0,e=f"IND {name}MA {subtype} {MA_value}",save_to_past=save_to_past) else: @@ -150,20 +160,20 @@ def populate_dynamic_custom_indicator(data, state: StrategyState, name): raise Exception(err) except Exception as e: - if len(state.indicators[name]) >= 2: - state.indicators[name][-1]=state.indicators[name][-2] - if MA_length is not None and len(state.indicators[name+"MA"])>=2: - state.indicators[name+"MA"][-1]=state.indicators[name+"MA"][-2] + if len(indicators_dict[name]) >= 2: + indicators_dict[name][-1]=indicators_dict[name][-2] + if MA_length is not None and len(indicators_dict[name+"MA"])>=2: + indicators_dict[name+"MA"][-1]=indicators_dict[name+"MA"][-2] state.ilog(lvl=1,e=f"IND ERROR {name} {subtype} necháváme původní", message=str(e)+format_exc()) else: state.ilog(lvl=0,e=f"IND {name} {subtype} COND NOT READY: {msg}") #not time to run - copy last value - if len(state.indicators[name]) >= 2: - state.indicators[name][-1]=state.indicators[name][-2] + if len(indicators_dict[name]) >= 2: + indicators_dict[name][-1]=indicators_dict[name][-2] - if MA_length is not None and len(state.indicators[name+"MA"])>=2: - state.indicators[name+"MA"][-1]=state.indicators[name+"MA"][-2] + if MA_length is not None and len(indicators_dict[name+"MA"])>=2: + indicators_dict[name+"MA"][-1]=indicators_dict[name+"MA"][-2] state.ilog(lvl=0,e=f"IND {name} {subtype} NOT TIME TO RUN - value(and MA) still original") diff --git a/v2realbot/strategyblocks/indicators/helpers.py b/v2realbot/strategyblocks/indicators/helpers.py index 7e1c574..f1e4d4c 100644 --- a/v2realbot/strategyblocks/indicators/helpers.py +++ b/v2realbot/strategyblocks/indicators/helpers.py @@ -60,6 +60,8 @@ def evaluate_directive_conditions(state, work_dict, cond_type): return eval_cond_dict(cond) +#TODO toto pripadne sloucit s get_source_series - revidovat dopady + def get_source_or_MA(state, indicator): #pokud ma, pouzije MAcko, pokud ne tak standardni indikator #pokud to jmeno neexistuje, tak pripadne bere z barů (close,open,hlcc4, vwap atp.) @@ -69,7 +71,10 @@ def get_source_or_MA(state, indicator): try: return state.indicators[indicator] except KeyError: - return state.bars[indicator] + try: + return state.bars[indicator] + except KeyError: + return state.cbar_indicators[indicator] def get_source_series(state, source: str): """ @@ -85,7 +90,10 @@ def get_source_series(state, source: str): try: return state.indicators[source] except KeyError: - return None + try: + return state.cbar_indicators[source] + except KeyError: + return None else: dict_name = source[:split_index] key = source[split_index + 1:] diff --git a/v2realbot/strategyblocks/indicators/indicators_hub.py b/v2realbot/strategyblocks/indicators/indicators_hub.py index 49eca6a..d8fce86 100644 --- a/v2realbot/strategyblocks/indicators/indicators_hub.py +++ b/v2realbot/strategyblocks/indicators/indicators_hub.py @@ -63,12 +63,11 @@ def populate_all_indicators(data, state: StrategyState): else: pass + #toto je spíše interní ukládání tick_price a tick_volume - s tím pak mohou pracovat jak bar based tak tick based indikatory + #TODO do budoucna prejmenovat state.cbar_indicators na state.tick_indicators populate_cbar_tick_price_indicator(data, state) - #TBD nize predelat na typizovane RSI (a to jak na urovni CBAR tak confirmed) - #populate_cbar_rsi_indicator() - - #populate indicators, that have type in stratvars.indicators + #populate indicators, that have type in stratvars.indicators - pridana podpora i pro CBAR typu CUSTOM populate_dynamic_indicators(data, state) #vytiskneme si indikatory diff --git a/v2realbot/strategyblocks/inits/init_indicators.py b/v2realbot/strategyblocks/inits/init_indicators.py index fa33b0d..015cd37 100644 --- a/v2realbot/strategyblocks/inits/init_indicators.py +++ b/v2realbot/strategyblocks/inits/init_indicators.py @@ -23,13 +23,23 @@ def initialize_dynamic_indicators(state): ##ßprintanyway(state.vars, state) dict_copy = state.vars.indicators.copy() for indname, indsettings in dict_copy.items(): + #inicializace indikatoru na dane urovni + output = safe_get(indsettings, 'output', "bar") + match output: + case "bar": + indicators_dict = state.indicators + case "tick": + indicators_dict = state.cbar_indicators + case _: + raise(f"ind output must be bar or tick {indname}") + + indicators_dict[indname] = [] + #pokud ma MA_length incializujeme i MA variantu + if safe_get(indsettings, 'MA_length', False): + indicators_dict[indname+"MA"] = [] + + #Specifické Inicializace dle type for option,value in list(indsettings.items()): - #inicializujeme nejenom typizovane - #if option == "type": - state.indicators[indname] = [] - #pokud ma MA_length incializujeme i MA variantu - if safe_get(indsettings, 'MA_length', False): - state.indicators[indname+"MA"] = [] #specifika pro slope if option == "type": if value == "slope": diff --git a/v2realbot/utils/utils.py b/v2realbot/utils/utils.py index b695851..499e434 100644 --- a/v2realbot/utils/utils.py +++ b/v2realbot/utils/utils.py @@ -302,21 +302,34 @@ def eval_cond_dict(cond: dict) -> tuple[bool, str]: def Average(lst): return sum(lst) / len(lst) +#OPTIMIZED by CHATGPT def safe_get(collection, key, default=None): """Get values from a collection without raising errors""" - - try: + # Check if the collection supports the .get method (like dict) + if hasattr(collection, 'get'): return collection.get(key, default) - except TypeError: - pass - try: + # Check if the key is within the bounds for list-like collections + if isinstance(collection, (list, tuple)) and 0 <= key < len(collection): return collection[key] - except (IndexError, TypeError): - pass return default +# def safe_get(collection, key, default=None): +# """Get values from a collection without raising errors""" + +# try: +# return collection.get(key, default) +# except TypeError: +# pass + +# try: +# return collection[key] +# except (IndexError, TypeError): +# pass + +# return default + def send_to_telegram(message): apiToken = '5836666362:AAGPuzwp03tczMQTwTBiHW6VsZZ-1RCMAEE' chatID = '5029424778'