From c1145fec5baa170099db3c95aab5dece7fcf9fc4 Mon Sep 17 00:00:00 2001 From: David Brazda Date: Tue, 16 Jan 2024 15:17:14 +0100 Subject: [PATCH] multioutput indicators #15 + talib custom indicator support --- v2realbot/ENTRY_ClassicSL_v01.py | 8 +- v2realbot/controller/services.py | 60 ++++++- v2realbot/main.py | 2 +- v2realbot/static/js/archivechart.js | 66 +++++-- v2realbot/static/js/instantindicators.js | 25 ++- v2realbot/static/js/utils/utils.js | 167 +++++++++++++----- v2realbot/static/main.css | 47 +++++ .../indicators/custom/barparams.py | 2 +- .../indicators/custom/basestats.py | 152 +++++++++++++--- .../indicators/custom/classed.py | 2 +- .../indicators/custom/conditional.py | 2 +- .../strategyblocks/indicators/custom/delta.py | 2 +- .../indicators/custom/divergence.py | 2 +- .../strategyblocks/indicators/custom/ema.py | 2 +- .../indicators/custom/expression.py | 14 +- .../strategyblocks/indicators/custom/ma.py | 3 +- .../indicators/custom/mathop.py | 2 +- .../strategyblocks/indicators/custom/model.py | 2 +- .../indicators/custom/opengap.py | 2 +- .../strategyblocks/indicators/custom/rsi.py | 2 +- .../indicators/custom/sameprice.py | 2 +- .../strategyblocks/indicators/custom/slope.py | 2 +- .../indicators/custom/talib_ind.py | 77 ++++++++ .../indicators/custom/target.py | 2 +- .../indicators/custom/targetema.py | 2 +- .../strategyblocks/indicators/custom/vwma.py | 2 +- .../strategyblocks/indicators/custom_hub.py | 75 +++++--- .../strategyblocks/inits/init_indicators.py | 13 +- 28 files changed, 598 insertions(+), 141 deletions(-) create mode 100644 v2realbot/strategyblocks/indicators/custom/talib_ind.py diff --git a/v2realbot/ENTRY_ClassicSL_v01.py b/v2realbot/ENTRY_ClassicSL_v01.py index 6565da2..7eed9d3 100644 --- a/v2realbot/ENTRY_ClassicSL_v01.py +++ b/v2realbot/ENTRY_ClassicSL_v01.py @@ -252,10 +252,10 @@ def init(state: StrategyState): # High to Low Range: (high[-1] - low[-1]) / low[-1] # Store the ratios in the bars dictionary - state.dailyBars['upper_shadow_ratio'] = upper_shadow - state.dailyBars['lower_shadow_ratio'] = lower_shadow - state.dailyBars['body_size_ratio'] = body_size - state.dailyBars['body_position_ratio'] = body_position + state.dailyBars['upper_shadow_ratio'] = upper_shadow.tolist() + state.dailyBars['lower_shadow_ratio'] = lower_shadow.tolist() + state.dailyBars['body_size_ratio'] = body_size.tolist() + state.dailyBars['body_position_ratio'] = body_position.tolist() #printanyway("daily bars FILLED", state.dailyBars) #zatim ukladame do extData - pro instant indicatory a gui diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index bf5aa23..7f5e996 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -1488,6 +1488,8 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = if output is None: return (-2, "output invalid (bar/tick)") + returns = safe_get(toml_parsed, 'returns', []) + custom_params = safe_get(toml_parsed, "cp", None) print("custom params",custom_params) @@ -1499,7 +1501,7 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = detail = RunArchiveDetail(**val) #print("toto jsme si dotahnuli", detail.bars) - #pokud tento indikator jiz je v detailu, tak ho odmazeme + #pokud tento indikator jiz je v detailu, tak ho odmazeme - jde o main #BAR indikatory if indicator.name in detail.indicators[0]: del detail.indicators[0][indicator.name] @@ -1507,6 +1509,14 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = elif indicator.name in detail.indicators[1]: del detail.indicators[1][indicator.name] + #to same i s pripdadnymi multivystupy + if len(returns)>0: + for ind_name in returns: + if ind_name in detail.indicators[0]: + del detail.indicators[0][ind_name] + #CBAR indikatory + elif ind_name in detail.indicators[1]: + del detail.indicators[1][ind_name] #new dicts new_bars = {key: [] for key in detail.bars.keys()} @@ -1532,6 +1542,10 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = state.cbar_indicators = detail.indicators[1] #mozna toto vubec neopotrebujeme new_inds[indicator.name] = [] new_inds[indicator.name] = [] + #init multiinputu + if len(returns)>0: + for ind_name in returns: + new_inds[ind_name] = [] #pro tick, nechavame bary a nechavame volne pouze tickbary, nad kterymi iterujeme else: state.bars = new_bars @@ -1539,6 +1553,10 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = state.cbar_indicators = new_tick_inds new_tick_inds[indicator.name] = [] new_tick_inds[indicator.name] = [] + #init multiinputu + if len(returns)>0: + for ind_name in returns: + new_tick_inds[ind_name] = [] #pridavame dailyBars z extData if hasattr(detail, "ext_data") and "dailyBars" in detail.ext_data: @@ -1577,6 +1595,10 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = #inicializujeme 0 v novém indikatoru state.indicators[indicator.name].append(0) + #init pro multipuput + for ind_name in returns: + state.indicators[ind_name].append(0) + try: populate_dynamic_indicators(new_data, state) # res_code, new_val = custom_function(state, custom_params) @@ -1587,11 +1609,19 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = #print("Done", state.indicators[indicator.name]) - + output_dict = {} new_inds[indicator.name] = state.indicators[indicator.name] #ukládáme do ArchRunneru detail.indicators[0][indicator.name] = new_inds[indicator.name] + output_dict[indicator.name] = new_inds[indicator.name] + + #to same s multiinputy: + if len(returns)>0: + for ind_name in returns: + new_inds[ind_name] = state.indicators[ind_name] + detail.indicators[0][ind_name] = new_inds[ind_name] + output_dict[ind_name] = new_inds[ind_name] #TOTOZNE PRO TICK INDICATOR else: @@ -1622,6 +1652,10 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = #inicializujeme 0 v novém indikatoru state.cbar_indicators[indicator.name].append(0) + #init pro multipuput + for ind_name in returns: + state.cbar_indicators[ind_name].append(0) + try: populate_dynamic_indicators(new_data, state) # res_code, new_val = custom_function(state, custom_params) @@ -1631,12 +1665,19 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = print(str(e) + format_exc()) #print("Done", state.indicators[indicator.name]) - + output_dict = {} 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] + output_dict[indicator.name] = new_tick_inds[indicator.name] + #to same s multiinputy: + if len(returns)>0: + for ind_name in returns: + new_tick_inds[ind_name] = state.cbar_indicators[ind_name] + detail.indicators[1][ind_name] = new_tick_inds[ind_name] + output_dict[ind_name] = new_tick_inds[ind_name] #do ext dat ukladame jmeno indikatoru (podle toho oznacuje jako zmenene) @@ -1653,19 +1694,26 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool = detail.ext_data["instantindicators"].remove(ind) print("removed old from EXT_DATA") - #a pridame aktualni + #a pridame aktualni + #NOTE - multivystupy tedy nebudou oznacene na GUI detail.ext_data["instantindicators"].append(indicator) print("added to EXT_DATA") + #updatneme ArchRunner res, val = update_archive_detail(id, detail) if res == 0: print(f"arch runner {id} updated") + + #output bude nyni ve formatu {key:list} + #vracime list, kde pozice 0 je bar indicators, pozice 1 je ticks indicators if output == "bar": - return 0, [new_inds[indicator.name], []] + return 0, [output_dict, []] + #return 0, [new_inds[indicator.name], []] else: - return 0, [[], new_tick_inds[indicator.name]] + return 0, [[], output_dict] + #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 5a33553..f90825b 100644 --- a/v2realbot/main.py +++ b/v2realbot/main.py @@ -484,7 +484,7 @@ def _delete_archived_runners_byBatchID(batch_id: str): #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[list[float]]: +def _preview_indicator_byTOML(runner_id: UUID, indicator: InstantIndicator) -> list[dict]: #mozna pak pridat name res, vals = cs.preview_indicator_byTOML(id=runner_id, indicator=indicator) if res == 0: return vals diff --git a/v2realbot/static/js/archivechart.js b/v2realbot/static/js/archivechart.js index 8f275ea..d2b10b2 100644 --- a/v2realbot/static/js/archivechart.js +++ b/v2realbot/static/js/archivechart.js @@ -386,7 +386,7 @@ function chart_indicators(data, visible, offset) { //console.log("ZPETNE STRINGIFIED", TOML.stringify(TOML.parse(data.archRecord.stratvars_toml), {newline: '\n'})) //indicatory //console.log("indicatory TOML", stratvars_toml.stratvars.indicators) - + indId = 1 indicatorList.forEach((indicators, index, array) => { //var indicators = data.indicators @@ -401,6 +401,7 @@ function chart_indicators(data, visible, offset) { //pokud je v nastaveni scale, pouzijeme tu var scale = null var instant = null + var returns = null //console.log(key) //zkusime zda nejde o instantni indikator z arch runneru if ((data.ext_data !== null) && (data.ext_data.instantindicators)) { @@ -410,6 +411,7 @@ function chart_indicators(data, visible, offset) { cnf = instantIndicator.toml scale = TOML.parse(cnf).scale instant = 1 + returns = TOML.parse(cnf).returns } } //pokud nenalezeno, pak bereme standard @@ -418,6 +420,7 @@ function chart_indicators(data, visible, offset) { if (stratvars_toml.stratvars.indicators[key]) { cnf = "#[stratvars.indicators."+key+"]"+TOML.stringify(stratvars_toml.stratvars.indicators[key], {newline: '\n'}) scale = stratvars_toml.stratvars.indicators[key].scale + returns = stratvars_toml.stratvars.indicators[key].returns } } // //kontriolujeme v addedInds @@ -438,7 +441,7 @@ function chart_indicators(data, visible, offset) { // } //initialize indicator and store reference to array - var obj = {name: key, type: index, series: null, cnf:cnf, instant: instant} + var obj = {name: key, type: index, series: null, cnf:cnf, instant: instant, returns: returns, indId:indId++} //start //console.log(key) @@ -623,23 +626,52 @@ function chart_indicators(data, visible, offset) { } //sort by type first (0-bar,1-cbar inds) and then alphabetically - indList.sort((a, b) => { - 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; - } - } + // indList.sort((a, b) => { + // 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; + // } + // } + // }); + + //SORTING tak, aby multioutputs atributy byly vzdy na konci dane skupiny (tzn. v zobrazeni jsou zpracovany svými rodiči) + // Step 1: Create a Set of all names in 'returns' arrays + const namesInReturns = new Set(); + indList.forEach(item => { + if (Array.isArray(item.returns)) { + item.returns.forEach(name => namesInReturns.add(name)); + } }); + // Step 2: Custom sort function + indList.sort((a, b) => { + // First, sort by 'type' + if (a.type !== b.type) { + return a.type - b.type; + } + + // For items with the same 'type', apply secondary sorting + const aInReturns = namesInReturns.has(a.name); + const bInReturns = namesInReturns.has(b.name); + + if (aInReturns && !bInReturns) return 1; // 'a' goes after 'b' + if (!aInReturns && bInReturns) return -1; // 'a' goes before 'b' + + // If both or neither are in 'returns', sort alphabetically by 'name' + return a.name.localeCompare(b.name); + }); + + + //puvodni funkce // indList.sort((a, b) => { // const nameA = a.name.toUpperCase(); // ignore upper and lowercase diff --git a/v2realbot/static/js/instantindicators.js b/v2realbot/static/js/instantindicators.js index 065893f..6fe0c98 100644 --- a/v2realbot/static/js/instantindicators.js +++ b/v2realbot/static/js/instantindicators.js @@ -149,10 +149,27 @@ $(document).ready(function () { //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 - if (data[0].length > 0) { - archData.indicators[0][indName] = data[0] - } else if (data[1].length > 0) { - archData.indicators[1][indName] = data[1] + + //v ramci podpory multioutputu je navrat nazevind:timeserie a to + //pro indicators [0] nebo cbar_indicators [1] list + if (Object.keys(data[0]).length > 0) { + for (let key in data[0]) { + if (data[0].hasOwnProperty(key)) { + archData.indicators[0][key] = data[0][key] + console.log("barind updatovan " + key) + //console.log(data[0][key]); + } + } + //archData.indicators[0][indName] = data[0] + } else if (Object.keys(data[1]).length > 0) { + for (let key in data[1]) { + if (data[1].hasOwnProperty(key)) { + archData.indicators[1][key] = data[1][key] + console.log("cbarind updatovan " + key) + //console.log(data[1][key]); + } + } + //archData.indicators[1][indName] = data[1] } else { alert("neco spatne s response ", data) diff --git a/v2realbot/static/js/utils/utils.js b/v2realbot/static/js/utils/utils.js index ef1765d..cca7a7c 100644 --- a/v2realbot/static/js/utils/utils.js +++ b/v2realbot/static/js/utils/utils.js @@ -419,14 +419,16 @@ function remove_indicator_buttons() { } //pomocna funkce pro vytvoreni buttonu indiaktoru -function create_indicator_button(item, index, def) { +function create_indicator_button(item, def, noaction = false) { // //div pro kazdy button // var buttonContainer = document.createElement('div'); // buttonContainer.classList.add('button-container'); + index = item.indId + var itemEl = document.createElement('button'); itemEl.innerText = item.name; - itemEl.id = "IND"+index; + itemEl.id = "IND"+item.indId; itemEl.title = item.cnf itemEl.style.color = item.series.options().color; //pokud jde o pridanou on the fly - vybarvime jinak @@ -450,9 +452,12 @@ function create_indicator_button(item, index, def) { // actionShow.id = "actionShow"; // actionShow.textContent = "Show"; - itemEl.addEventListener('click', function() { - onItemClickedToggle(index); - }); + //nepouzivat pro urcite pripady (napr. u hlavnich multioutputu indikatoru) - pouze nese predpis(right click) a left clickem zobrazi outputy + if (!noaction) { + itemEl.addEventListener('click', function() { + onItemClickedToggle(item.indId); + }); + } // const actionEdit = document.createElement("div"); // actionEdit.id = "actionEdit"; @@ -460,7 +465,7 @@ function create_indicator_button(item, index, def) { itemEl.addEventListener('contextmenu', function(e) { //edit modal zatim nemame - onItemClickedEdit(e, index); + onItemClickedEdit(e, item.indId); }); // // Append the action buttons to the overlay. @@ -480,13 +485,13 @@ function create_indicator_button(item, index, def) { function onResetClicked() { indList.forEach(function (item, index) { vis = true; - const elem = document.getElementById("IND"+index); + const elem = document.getElementById("IND"+item.indId); if (elem.classList.contains("switcher-active-item")) { vis = false; } elem.classList.toggle("switcher-active-item"); - if (indList[index].series) { - indList[index].series.applyOptions({ + if (obj.series) { + obj.series.applyOptions({ visible: vis }); } }) @@ -656,6 +661,10 @@ function mrkLineToggle() { } +function get_ind_by_id(indId) { + return indList.find(obj => obj.indId === indId); +} + //toggle indiktoru function onItemClickedToggle(index) { vis = true; @@ -665,25 +674,80 @@ function onItemClickedToggle(index) { } elem.classList.toggle("switcher-active-item"); //v ifu kvuli workaroundu - if (indList[index].series) { - //console.log(indList[index].name, indList[index].series) - indList[index].series.applyOptions({ + obj = get_ind_by_id(index) + if (obj.series) { + //console.log(obj.name, obj.series) + obj.series.applyOptions({ visible: vis }); } //zatim takto workaround, pak vymyslet systemove pro vsechny tickbased indikatory tickIndicatorList = ["tick_price", "tick_volume"] - if (tickIndicatorList.includes(indList[index].name)) { - if (!vis && indList[index].series) { - //console.log("pred", indList[index].name, indList[index].series) - chart.removeSeries(indList[index].series) + if (tickIndicatorList.includes(obj.name)) { + if (!vis && obj.series) { + //console.log("pred", obj.name, obj.series) + chart.removeSeries(obj.series) chart.timeScale().fitContent(); - indList[index].series = null - //console.log("po", indList[index].name, indList[index].series) + obj.series = null + //console.log("po", obj.name, obj.series) } } } +//obalka pro collapsovatelny multioutput indicator button +function create_multioutput_button(item, def, active) { + //encapsulating dic + var multiOutEl = document.createElement('div'); + //multiOutEl.id = "tickIndicatorsButtons" + multiOutEl.classList.add('multiOut'); + multiOutEl.classList.add('switcher-item'); + //pouze def - u main indikatoru nepamatujeme stav a pozadujeme noaction pro leftclick + itemEl = create_indicator_button(item, def, true); + //hlavni button ridi expand/collapse + itemEl.setAttribute('data-bs-toggle', 'collapse'); + itemEl.setAttribute('data-bs-target', '.'+item.name); + itemEl.setAttribute('aria-expanded', 'true'); + itemEl.setAttribute('role', 'button'); + //itemEl.setAttribute('aria-controls', 'IND6 IND7 IND8'); + //itemEl.style.outline = 'dotted'; + itemEl.style.marginRight = '0px' + + //prirazeni mainu do divu + multiOutEl.appendChild(itemEl); + + //pokud nektery z multivstupu je aktivni, pak nastavuju vse expanded + const isAnyActive = activatedButtons.some(element => item.returns.includes(element)); + + item.returns.forEach(function (output_name,index) { + active = false + //find and process multioutput parameters + const foundObject = indList.find(obj => obj.name == output_name); + if (foundObject) { + + //aplikujeme remembered state + if ((activatedButtons) && (activatedButtons.includes(output_name))) { + active = true + } + + console.log(foundObject.content); // Access and use the content + itemEl = create_indicator_button(foundObject, def||active); + + itemEl.classList.add('collapse') + //pokud je aktivni jakykoliv, expandujeme vsechny + if (active || isAnyActive) { + itemEl.classList.add('show') + } + itemEl.classList.add(item.name) + itemEl.style.marginRight = '0px' + + multiOutEl.appendChild(itemEl); + } + }); + + + return multiOutEl +} + //funkce pro vytvoreni buttonku indikatoru function populate_indicator_buttons(def) { @@ -692,37 +756,58 @@ function populate_indicator_buttons(def) { buttonElement.id = "indicatorsButtons" buttonElement.classList.add('switcher'); - //incializujeme i bar pro cbar indikator sekci + //incializujeme i div pro cbar indikator sekci var tickButtonElement = document.createElement('div'); tickButtonElement.id = "tickIndicatorsButtons" tickButtonElement.classList.add('tickButtons'); + already_processed = []; //iterace nad indikatory a vytvareni buttonků indList.forEach(function (item, index) { - index_ind = index - active = false + index_ind = item.indId + if (!already_processed.includes(item.name)) { + 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 - } - //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) + if ((activatedButtons) && (activatedButtons.includes(item.name))) { + active = true + } + //bar indikatory jsou serazeny na zacarku + if (item.type == 0) { + //pokud jde o multiinput, pridame ihned souvisejici mutiinputy a vse dame do stejneho divu + //(Object.keys(data[0]).length > 0) + if (item.returns && item.returns.length > 0) { + //prirazeni multiOut do buttonu + multiOutEl = create_multioutput_button(item, def, active) + + buttonElement.appendChild(multiOutEl); + already_processed = already_processed.concat(item.returns) + } + else { + //vytvoreni buttonku + itemEl = create_indicator_button(item, def||active); + //prirazeni do divu + buttonElement.appendChild(itemEl); + } + } + //ted zbyvaji tick barové a ty dáme do separátního divu + else + { + //oper nejdriv multiinput + if (item.returns && item.returns.length > 0) { + + //prirazeni multiOut do buttonu + multiOutEl = create_multioutput_button(item, def, active) + tickButtonElement.appendChild(multiOutEl); + already_processed = already_processed.concat(item.returns) + } + //standardni non multiinput + else { + //vytvoreni buttonku + itemEl = create_indicator_button(item, def||active); + tickButtonElement.appendChild(itemEl) + } } + } }); //nakonec pripojime cely div s tick based indicatory diff --git a/v2realbot/static/main.css b/v2realbot/static/main.css index 8903f46..d40b1a2 100644 --- a/v2realbot/static/main.css +++ b/v2realbot/static/main.css @@ -828,6 +828,53 @@ pre { background-color: #e1eff94d;; } +.multiOut { + height: 20px; + position: relative; + /* background-color: rgb(174, 170, 163); */ + padding: 0px 0px 0px 0px; + margin-right: 6px; + margin-bottom: -6px; + /* position: relative; */ + z-index: 1; + /* cursor: pointer; */ + text-decoration: double; + display: inline-block; + /* padding: 1px 6px; */ + /* font-size: 14px; */ + /* color: #262b3e; */ + /* background-color: #818581; */ + background-color: #0202022e; + margin-right: 8px; + /* margin-bottom: 6px; */ + /* border: 1px; */ + border-radius: 4px; + /* outline: solid 1px; + outline-color: #323232; */ + /* outline-width: 1px; */ + border-style: none; + outline-width: thin; + outline-color: #5a5a5a; +} + +.multiOut.switcher-item::after { + content: ''; + display: inline-block; + margin-left: 5px; + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-left: 5px solid; /* Arrow pointing right for collapsed state */ +} + +.multiOut.switcher-item[aria-expanded="true"]::after { + border-top: 5px solid transparent; + border-bottom: 5px solid transparent; + border-right: 5px solid; /* Arrow pointing left for expanded state */ + border-left: none; +} + + + .material-symbols-outlined { font-variation-settings: 'FILL' 0, diff --git a/v2realbot/strategyblocks/indicators/custom/barparams.py b/v2realbot/strategyblocks/indicators/custom/barparams.py index a991c82..0531819 100644 --- a/v2realbot/strategyblocks/indicators/custom/barparams.py +++ b/v2realbot/strategyblocks/indicators/custom/barparams.py @@ -10,7 +10,7 @@ import math from collections import defaultdict #indicator allowing to be based on any bar parameter (index, high,open,close,trades,volume, etc.) -def barparams(state, params, name): +def barparams(state, params, name, returns): funcName = "barparams" if params is None: return -2, "params required" diff --git a/v2realbot/strategyblocks/indicators/custom/basestats.py b/v2realbot/strategyblocks/indicators/custom/basestats.py index 6118387..ff06468 100644 --- a/v2realbot/strategyblocks/indicators/custom/basestats.py +++ b/v2realbot/strategyblocks/indicators/custom/basestats.py @@ -9,22 +9,25 @@ from collections import defaultdict from scipy.stats import linregress from scipy.fft import fft from v2realbot.strategyblocks.indicators.helpers import value_or_indicator +import pywt #vstupem je bud indicator nebo bar parametr #na tomto vstupu dokaze provest zakladni statisticke funkce pro subpole X hodnot zpatky #podporovane functions: min, max, mean -def basestats(state, params, name): +def basestats(state, params, name, returns): funcName = "basestats" #name of indicator or source = safe_get(params, "source", None) lookback = safe_get(params, "lookback", None) func = safe_get(params, "function", None) - returns = safe_get(params, "returns", None) - source_dict = defaultdict(list) source_dict[source] = get_source_series(state, source) - self = state.indicators[name] + try: + self = state.indicators[name] + except KeyError: + self = state.cbar_indicators[name] + if lookback is None: source_array = source_dict[source] @@ -69,7 +72,23 @@ def basestats(state, params, name): #val = 2 * (angle_deg / 180) - 1 elif func =="stdev": val = np.std(source_array) - #linregres slope + #linregress mutlioutput + # slope : float, Slope of the regression line. + # intercept : float, Intercept of the regression line. + # rvalue : float, The Pearson correlation coefficient. The square of rvalue is equal to the coefficient of determination. + # pvalue : float, The p-value for a hypothesis test whose null hypothesis is that the slope is zero, using Wald Test with t-distribution of the test statistic. See alternative above for alternative hypotheses. + # stderr : float + elif func == "linregress": + if len(source_array) < 4: + return -2, "less than 4 elmnts" + try: + val = [] + np.seterr(all="raise") + slope, intercept, rvalue, pvalue, stderr = linregress(np.arange(len(source_array)), source_array) + val = [slope*1000, intercept,rvalue, pvalue, stderr] + except FloatingPointError: + return -2, "FloatingPointError" + #linregres slope DECOMM elif func == "slope": if len(source_array) < 4: return -2, "less than 4 elmnts" @@ -79,17 +98,82 @@ def basestats(state, params, name): val = val*1000 except FloatingPointError: return -2, "FloatingPointError" - #zatim takto, dokud nebudou podporovany indikatory s vice vystupnimi + #zatim takto, dokud nebudou podporovany indikatory s vice vystupnimi - DECOMM elif func == "intercept": if len(source_array) < 4: return -2, "less than 4 elmnts" try: np.seterr(all="raise") _, val, _, _, _ = linregress(np.arange(len(source_array)), source_array) + val = round(val, 4) except FloatingPointError: return -2, "FloatingPointError" + + #work with different wavelet names and change max_scale + #https://chat.openai.com/c/44b917d7-43df-4d80-be2f-01a5ee92158b + elif func == "wavelet": + def extract_wavelet_features(time_series, wavelet_name='morl', max_scale=64): + scales = np.arange(1, max_scale + 1) + coefficients, frequencies = pywt.cwt(time_series, scales, wavelet_name) + + # Extract features - for instance, mean and variance of coefficients at each scale + mean_coeffs = np.mean(coefficients, axis=1)[-1] # Last value of mean coefficients + var_coeffs = np.var(coefficients, axis=1)[-1] # Last value of variance of coefficients + # Energy distribution for the latest segment + energy = np.sum(coefficients**2, axis=1)[-1] + + # Entropy for the latest segment + entropy = -np.sum((coefficients**2) * np.log(coefficients**2), axis=1)[-1] + + # Dominant and mean frequency for the latest segment + dominant_frequency = frequencies[np.argmax(energy)] + mean_frequency = 0 # np.average(frequencies, weights=energy) + + return [energy, entropy, dominant_frequency, mean_frequency,mean_coeffs, var_coeffs] + + time_series = np.array(source_array) + + wavelet_name = "morl" + max_scale = 64 + features = extract_wavelet_features(time_series) + return 0, features + + #better fourier for frequency bins as suggested here https://chat.openai.com/c/44b917d7-43df-4d80-be2f-01a5ee92158b elif func == "fourier": + def compute_fft_features(time_series, num_bins): + n = len(time_series) + yf = fft(time_series) + + # Frequency values for FFT output + xf = np.linspace(0.0, 1.0/(2.0), n//2) + + # Compute power spectrum + power_spectrum = np.abs(yf[:n//2])**2 + + # Define frequency bins + max_freq = 1.0 / 2.0 + bin_edges = np.linspace(0, max_freq, num_bins + 1) + + # Initialize feature array + features = np.zeros(num_bins) + + # Compute power in each bin + for i in range(num_bins): + # Find indices of frequencies in this bin + indices = np.where((xf >= bin_edges[i]) & (xf < bin_edges[i+1]))[0] + features[i] = np.sum(power_spectrum[indices]) + + return features + + # Example usage + time_series = np.array(source_array) # Replace with your data + num_bins = 20 # Example: 10 frequency bins + features = compute_fft_features(time_series, num_bins) + return 0, features.tolist() + + #returns X frequencies + elif func == "fourier_old": time_series = np.array(source_array) n = len(time_series) @@ -97,30 +181,50 @@ def basestats(state, params, name): yf = fft(time_series) xf = np.linspace(0.0, 1.0/(2.0), n//2) + #three most dominant frequencies dominant_frequencies = xf[np.argsort(np.abs(yf[:n//2]))[-3:]] state.ilog(lvl=1,e=f"IND {name}:{funcName} 3 dominant freq are {str(dominant_frequencies)}", **params) + #rt = dict(zip(returns, dominant_frequencies.tolist())) + return 0, dominant_frequencies.tolist() - if returns is not None: - #vracime druhou - if returns == "second": - if len(dominant_frequencies) > 1: - val = dominant_frequencies[-2] - else: - val = 0 - else: - #vracime most dominant - val = float(np.max(dominant_frequencies)) - return 0, val + + # if returns is not None: + # #vracime druhou + # if returns == "second": + # if len(dominant_frequencies) > 1: + # val = dominant_frequencies[-2] + # else: + # val = 0 + # else: + # #vracime most dominant + # val = float(np.max(dominant_frequencies)) + # return 0, val + #returns histogram bins https://chat.openai.com/share/034f8742-b091-4859-8c3e-570edb9c1006 + # pocet vyskytu v danem binu elif func == "histogram": - #takes only first N - items + + # Convert to numpy array dt = np.array(source_array) - #creates 4 buckets - bins = 4 - mean_of_4th_bin = np.mean(dt[np.where(np.histogram(dt, bins)[1][3] <= dt)[0]]) - if not np.isfinite(mean_of_4th_bin): - mean_of_4th_bin = 0 - return 0, float(mean_of_4th_bin) + + # Create 4 bins + bins = np.histogram_bin_edges(dt, bins=4) + + # Assign elements to bins + bin_indices = np.digitize(dt, bins) + + # Calculate mean for each bin + means = [dt[bin_indices == i].mean() if dt[bin_indices == i].size > 0 else 0 for i in range(1, len(bins))] + return 0, dict(zip(returns, means)) + + # #takes only first N - items + # dt = np.array(source_array) + # #creates 4 buckets + # bins = 4 + # mean_of_4th_bin = np.mean(dt[np.where(np.histogram(dt, bins)[1][3] <= dt)[0]]) + # if not np.isfinite(mean_of_4th_bin): + # mean_of_4th_bin = 0 + # return 0, float(mean_of_4th_bin) elif func == "maxima": if len(source_array) < 3: diff --git a/v2realbot/strategyblocks/indicators/custom/classed.py b/v2realbot/strategyblocks/indicators/custom/classed.py index cf4b83f..65d03e8 100644 --- a/v2realbot/strategyblocks/indicators/custom/classed.py +++ b/v2realbot/strategyblocks/indicators/custom/classed.py @@ -22,7 +22,7 @@ import importlib #OBECNA trida pro statefull indicators - realized by class with the same name, deriving from parent IndicatorBase class #todo v initu inicializovat state.classed_indicators a ve stopu uklidit - resetovat -def classed(state, params, name): +def classed(state, params, name, returns): try: funcName = "classed" if params is None: diff --git a/v2realbot/strategyblocks/indicators/custom/conditional.py b/v2realbot/strategyblocks/indicators/custom/conditional.py index 605a0a0..3eec839 100644 --- a/v2realbot/strategyblocks/indicators/custom/conditional.py +++ b/v2realbot/strategyblocks/indicators/custom/conditional.py @@ -23,7 +23,7 @@ from collections import defaultdict #novy podminkovy indikator, muze obsahovat az N podminek ve stejne syntaxy jako u signalu #u kazde podminky je hodnota, ktera se vraci pokud je true #hodi se pro vytvareni binarnich targetu pro ML -def conditional(state, params, name): +def conditional(state, params, name, returns): funcName = "conditional" if params is None: return -2, "params required" diff --git a/v2realbot/strategyblocks/indicators/custom/delta.py b/v2realbot/strategyblocks/indicators/custom/delta.py index abaea9c..dff0012 100644 --- a/v2realbot/strategyblocks/indicators/custom/delta.py +++ b/v2realbot/strategyblocks/indicators/custom/delta.py @@ -9,7 +9,7 @@ from collections import defaultdict #strength, absolute change of parameter between current value and lookback value (n-past) #used for example to measure unusual peaks -def delta(state, params, name): +def delta(state, params, name, returns): funcName = "delta" source = safe_get(params, "source", None) lookback = safe_get(params, "lookback",1) diff --git a/v2realbot/strategyblocks/indicators/custom/divergence.py b/v2realbot/strategyblocks/indicators/custom/divergence.py index 46a8261..56693ee 100644 --- a/v2realbot/strategyblocks/indicators/custom/divergence.py +++ b/v2realbot/strategyblocks/indicators/custom/divergence.py @@ -8,7 +8,7 @@ import numpy as np from collections import defaultdict #abs/rel divergence of two indicators -def divergence(state, params, name): +def divergence(state, params, name, returns): funcName = "indicatorDivergence" source1 = safe_get(params, "source1", None) source1_series = get_source_series(state, source1) diff --git a/v2realbot/strategyblocks/indicators/custom/ema.py b/v2realbot/strategyblocks/indicators/custom/ema.py index e76a370..995a263 100644 --- a/v2realbot/strategyblocks/indicators/custom/ema.py +++ b/v2realbot/strategyblocks/indicators/custom/ema.py @@ -9,7 +9,7 @@ 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 ema(state, params, name): +def ema(state, params, name, returns): funcName = "ema" source = safe_get(params, "source", None) lookback = safe_get(params, "lookback",14) diff --git a/v2realbot/strategyblocks/indicators/custom/expression.py b/v2realbot/strategyblocks/indicators/custom/expression.py index 6c8caba..2d607e7 100644 --- a/v2realbot/strategyblocks/indicators/custom/expression.py +++ b/v2realbot/strategyblocks/indicators/custom/expression.py @@ -15,7 +15,7 @@ from copy import deepcopy #eval nyni umi i user-defined function, string operation and control statements #teroeticky se dá pouzit i SYMPY - kde se daji vytvorit jednotlive symboly s urcitou funkcni -def expression(state: StrategyState, params, name): +def expression(state: StrategyState, params, name, returns): try: funcName = "expression" #indicator name @@ -56,7 +56,17 @@ def expression(state: StrategyState, params, name): val = eval(operation, {'state': state, 'np': np, 'utls': utls, 'math' : math}, temp_ind_mapping) #printanyway(val) - val = 0 if not np.isfinite(val) else val + + #toto dát nejspíš do custom_hubu asi te automaticky aplikovalo na vše + if isinstance(val, list): + for index, value in enumerate(val): + val[index] = 0 if not np.isfinite(value) else value + elif isinstance(val, dict): + for key, value in val.items(): + val[key] = 0 if not np.isfinite(value) else value + else: + val = 0 if not np.isfinite(val) else val + #val = ne.evaluate(operation, state.ind_mapping) state.ilog(lvl=1,e=f"IND {name}:{funcName} {operation=} res:{val}", **params) diff --git a/v2realbot/strategyblocks/indicators/custom/ma.py b/v2realbot/strategyblocks/indicators/custom/ma.py index fd90f3f..69d6b28 100644 --- a/v2realbot/strategyblocks/indicators/custom/ma.py +++ b/v2realbot/strategyblocks/indicators/custom/ma.py @@ -8,10 +8,11 @@ import numpy as np from collections import defaultdict from v2realbot.strategyblocks.indicators.helpers import value_or_indicator # from talib import BBANDS, MACD, RSI, MA_Type +from talib import BBANDS #IMPLEMENTS different types of moving averages in package v2realbot.indicators.moving_averages -def ma(state, params, name): +def ma(state, params, name, returns): funcName = "ma" type = safe_get(params, "type", "ema") source = safe_get(params, "source", None) diff --git a/v2realbot/strategyblocks/indicators/custom/mathop.py b/v2realbot/strategyblocks/indicators/custom/mathop.py index 853a358..c77d5de 100644 --- a/v2realbot/strategyblocks/indicators/custom/mathop.py +++ b/v2realbot/strategyblocks/indicators/custom/mathop.py @@ -4,7 +4,7 @@ from v2realbot.strategyblocks.indicators.helpers import get_source_series, value #allows basic mathematical operators to one or more indicators (add two indicator, add value to a indicator etc.) #REPLACED by EXPRESSION -def mathop(state, params, name): +def mathop(state, params, name, returns): funcName = "mathop" #indicator name source1 = safe_get(params, "source1", None) diff --git a/v2realbot/strategyblocks/indicators/custom/model.py b/v2realbot/strategyblocks/indicators/custom/model.py index cde093e..9566f9e 100644 --- a/v2realbot/strategyblocks/indicators/custom/model.py +++ b/v2realbot/strategyblocks/indicators/custom/model.py @@ -10,7 +10,7 @@ from collections import defaultdict """ """ -def model(state, params, ind_name): +def model(state, params, ind_name, returns): funcName = "model" if params is None: return -2, "params required" diff --git a/v2realbot/strategyblocks/indicators/custom/opengap.py b/v2realbot/strategyblocks/indicators/custom/opengap.py index 2b6e66b..40f9499 100644 --- a/v2realbot/strategyblocks/indicators/custom/opengap.py +++ b/v2realbot/strategyblocks/indicators/custom/opengap.py @@ -9,7 +9,7 @@ from collections import defaultdict #WIP - #testing custom indicator CODE -def opengap(state, params, name): +def opengap(state, params, name, returns): funcName = "opengap" param1 = safe_get(params, "param1") param2 = safe_get(params, "param2") diff --git a/v2realbot/strategyblocks/indicators/custom/rsi.py b/v2realbot/strategyblocks/indicators/custom/rsi.py index ebcfbfb..9c105e5 100644 --- a/v2realbot/strategyblocks/indicators/custom/rsi.py +++ b/v2realbot/strategyblocks/indicators/custom/rsi.py @@ -10,7 +10,7 @@ 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): +def rsi(state, params, name, returns): req_source = safe_get(params, "source", "vwap") rsi_length = safe_get(params, "length",14) start = safe_get(params, "start","linear") #linear/sharp diff --git a/v2realbot/strategyblocks/indicators/custom/sameprice.py b/v2realbot/strategyblocks/indicators/custom/sameprice.py index 66c75bd..e8a8ba4 100644 --- a/v2realbot/strategyblocks/indicators/custom/sameprice.py +++ b/v2realbot/strategyblocks/indicators/custom/sameprice.py @@ -10,7 +10,7 @@ import bisect #strength, absolute change of parameter between current value and lookback value (n-past) #used for example to measure unusual peaks -def sameprice(state, params, name): +def sameprice(state, params, name, returns): funcName = "sameprice" typ = safe_get(params, "type", None) diff --git a/v2realbot/strategyblocks/indicators/custom/slope.py b/v2realbot/strategyblocks/indicators/custom/slope.py index 4b3677d..8ba055c 100644 --- a/v2realbot/strategyblocks/indicators/custom/slope.py +++ b/v2realbot/strategyblocks/indicators/custom/slope.py @@ -8,7 +8,7 @@ import numpy as np from collections import defaultdict #rate of change - last value of source indicator vs lookback value of lookback_priceline indicator -def slope(state, params, name): +def slope(state, params, name, returns): funcName = "slope" source = safe_get(params, "source", None) source_series = get_source_series(state, source) diff --git a/v2realbot/strategyblocks/indicators/custom/talib_ind.py b/v2realbot/strategyblocks/indicators/custom/talib_ind.py new file mode 100644 index 0000000..72dd77d --- /dev/null +++ b/v2realbot/strategyblocks/indicators/custom/talib_ind.py @@ -0,0 +1,77 @@ +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 +import v2realbot.indicators.moving_averages as mi +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 collections import defaultdict +from v2realbot.strategyblocks.indicators.helpers import value_or_indicator +# from talib import BBANDS, MACD, RSI, MA_Type +import talib + + +# příklad toml pro indikátor ATR(high, low, close, timeperiod=14) +# POSITION MATTERS +# params.series.high = "high"   //series key určuje, že jde o série +# params.series.low = "low" +# params.series.low = "close" +# params.val.timeperiod = 14  //val key určuje, že jde o konkrétní hodnotu, tzn. buď hodnotu nebo název série, ze které vezmu poslední hodnotu (v tom případě by to byl string) + + +# params.series = ["high","low","close"] #pozicni parametry +# params.keys.timeperiod = 14 #keyword argumenty + +#TA-lib prijma positional arguments (zejmena teda ty series)m tzn. series musi byt pozicni + +# lookback se aplikuje na vsechy ? + + +#IMPLEMENTS usiong of any indicator from TA-lib library +def talib_ind(state, params, name, returns): + funcName = "ma" + type = safe_get(params, "type", "SMA") + #ßsource = safe_get(params, "source", None) + lookback = safe_get(params, "lookback",None) #celkovy lookback pro vsechny vstupni serie + start = safe_get(params, "start","linear") #linear/sharp + defval = safe_get(params, "defval",0) + + params = safe_get(params, "params", dict(series=[], keys=[])) + #lookback muze byt odkaz na indikator, pak berem jeho hodnotu + lookback = int(value_or_indicator(state, lookback)) + defval = int(value_or_indicator(state, defval)) + + + #TODO dopracovat caching, tzn. jen jednou pri inicializaci (linkuje se list) nicmene pri kazde iteraci musime prevest na numpy + #NOTE doresit, kdyz je val indiaktor, aby se i po inicializaci bral z indikatoru (doresit az pokud bude treba) + #NOTE doresit lookback, zda se aplikuje na vsechny pred volanim funkce nebo kdy? + series_list = [] + keyArgs = {} + for index, item in enumerate(params.get("series",[])): + source_series = get_source_series(state, item) + #upravujeme lookback pokud not enough values (staci jen pro prvni - jsou vsechny stejne) + if index == 0 and lookback is not None: + akt_pocet = len(source_series) + if akt_pocet < lookback and start == "linear": + lookback = akt_pocet + + series_list.append(np.array(source_series[-lookback:] if lookback is not None else source_series)) + + for key, val in params.get("keys",{}).items(): + keyArgs[key] = int(value_or_indicator(state, val)) + + type = "talib."+type + talib_function = eval(type) + + ma_value = talib_function(*series_list, **keyArgs) + + if not np.isfinite(ma_value[-1]): + val = defval + else: + val = round(ma_value[-1],4) + + if val == 0: + val = defval + + state.ilog(lvl=1,e=f"INSIDE {name}:{funcName} {val} {type=} {lookback=}", **params) + return 0, val \ No newline at end of file diff --git a/v2realbot/strategyblocks/indicators/custom/target.py b/v2realbot/strategyblocks/indicators/custom/target.py index 4926ed6..73e4a86 100644 --- a/v2realbot/strategyblocks/indicators/custom/target.py +++ b/v2realbot/strategyblocks/indicators/custom/target.py @@ -20,7 +20,7 @@ pct_change_full_scale = #pct change that is considered 1, used in scaler to dete TODO musi se signal trochu tahnout az kde se opravdu rozjede, aby si to model spojil """"" -def target(state, params, name): +def target(state, params, name, returns): funcName = "target" source = safe_get(params, "source", "vwap") source_series = get_source_series(state, source) diff --git a/v2realbot/strategyblocks/indicators/custom/targetema.py b/v2realbot/strategyblocks/indicators/custom/targetema.py index b1bf2ff..70332fa 100644 --- a/v2realbot/strategyblocks/indicators/custom/targetema.py +++ b/v2realbot/strategyblocks/indicators/custom/targetema.py @@ -17,7 +17,7 @@ Where - start is last crossing of source with ema and - end is the current position -1 """"" -def targetema(state, params, name): +def targetema(state, params, name, returns): try: funcName = "targetema" window_length_value = safe_get(params, "window_length_value", None) diff --git a/v2realbot/strategyblocks/indicators/custom/vwma.py b/v2realbot/strategyblocks/indicators/custom/vwma.py index c584b99..e59edcd 100644 --- a/v2realbot/strategyblocks/indicators/custom/vwma.py +++ b/v2realbot/strategyblocks/indicators/custom/vwma.py @@ -10,7 +10,7 @@ from collections import defaultdict from v2realbot.strategyblocks.indicators.helpers import value_or_indicator # Volume(or reference_source) Weighted moving Average -def vwma(state, params, name): +def vwma(state, params, name, returns): funcName = "vwma" source = safe_get(params, "source", None) ref_source = safe_get(params, "ref_source", "volume") diff --git a/v2realbot/strategyblocks/indicators/custom_hub.py b/v2realbot/strategyblocks/indicators/custom_hub.py index bf04efb..988fba3 100644 --- a/v2realbot/strategyblocks/indicators/custom_hub.py +++ b/v2realbot/strategyblocks/indicators/custom_hub.py @@ -46,6 +46,8 @@ def populate_dynamic_custom_indicator(data, state: StrategyState, name): save_to_past = int(safe_get(options, "save_to_past", 0)) save_to_past_unit = safe_get(options, "save_to_past_unit", "position") + #pokud neni multioutput, davame vystup do stejnojmenne serie + returns = safe_get(options, 'returns', [name]) def is_time_to_run(): # on_confirmed_only = true (def. False) @@ -139,15 +141,17 @@ def populate_dynamic_custom_indicator(data, state: StrategyState, name): state.vars.indicators[name]["last_run_index"] = data["index"] - #pomocna funkce - def save_to_past_func(indicators_dict,name,save_to_past_unit, steps, new_val): + #pomocna funkce (returns je pole toho , co indikator vraci a ret_val je dictionary, kde key je item z pole a val hodnota) + def save_to_past_func(indicators_dict,name,save_to_past_unit, steps, ret_val): if save_to_past_unit == "position": - indicators_dict[name][-1-steps]=new_val + for ind_name, ind_value in ret_val.items(): + indicators_dict[ind_name][-1-steps]=ind_value #time else: ##find index X seconds ago lookback_idx = find_index_optimized(time_list=indicators_dict["time"], seconds=steps) - indicators_dict[name][lookback_idx]=new_val + for ind_name, ind_value in ret_val.items(): + indicators_dict[ind_name][lookback_idx]=ind_value # - volame custom funkci pro ziskani hodnoty indikatoru # - tu ulozime jako novou hodnotu indikatoru a prepocteme MAcka pokud je pozadovane @@ -157,40 +161,61 @@ def populate_dynamic_custom_indicator(data, state: StrategyState, name): subtype = "ci."+subtype+"."+subtype custom_function = eval(subtype) - res_code, new_val = custom_function(state, custom_params, name) + res_code, ret_val = custom_function(state, custom_params, name, returns) if res_code == 0: - save_to_past_func(indicators_dict,name,save_to_past_unit, 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 = indicators_dict[name][-MA_length:] - MA_res = ema(src, MA_length) - MA_value = round(MA_res[-1],7) + #ret_val byl puvodne jedna hodnota + #nyni podporujeme multi output ve format dict(indName:value, indName2:value2...) + + #podporujeme vystup (list, dict a single value) - vse se transformuje do dict formatu + # pri listu zipneme s return) a vytvorime dict (v pripade mismatch sizes se matchnou jen kratsi) + if isinstance(ret_val, list): + ret_val = dict(zip(returns, ret_val)) + #pokud je to neco jineho nez dict (float,int..) jde o puvodni single output udelame z toho dict s hlavnim jmenem as key + elif not isinstance(ret_val, dict): + ret_val = {name: ret_val} + #v ostatnich pripadech predpokladame jiz dict - save_to_past_func(indicators_dict,name+"MA",save_to_past_unit, save_to_past, MA_value) - state.ilog(lvl=0,e=f"IND {name}MA {subtype} {MA_value}",save_to_past=save_to_past) + save_to_past_func(indicators_dict,name,save_to_past_unit, save_to_past, ret_val) + state.ilog(lvl=1,e=f"IND {name} {subtype} VAL FROM FUNCTION: {ret_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 + #pokud je MA nastaveno, tak pocitame MAcka pro vsechny multiouputy, tzn. vytvorime novem multioutput dict (ma_val) + if MA_length is not None: + ma_val = {} + for ind_name, ind_val in ret_val.items(): + src = indicators_dict[ind_name][-MA_length:] + MA_res = ema(src, MA_length) + MA_value = round(MA_res[-1],7) + ma_val[ind_name+"MA"] = MA_value + + save_to_past_func(indicators_dict,name+"MA",save_to_past_unit, save_to_past, ma_val) + state.ilog(lvl=0,e=f"IND {name}MA {subtype} {ma_val}",save_to_past=save_to_past) return else: - err = f"IND ERROR {name} {subtype}Funkce {custom_function} vratila {res_code} {new_val}." + err = f"IND ERROR {name} {subtype}Funkce {custom_function} vratila {res_code} {ret_val}." raise Exception(err) except Exception as e: - 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()) + use_last_values(indicators_dict, name, returns, MA_length) + state.ilog(lvl=1,e=f"IND ERROR {name} {subtype} necháváme původní u vsech z returns", returns=str(returns), message=str(e)+format_exc()) else: - state.ilog(lvl=0,e=f"IND {name} {subtype} COND NOT READY: {msg}") - + state.ilog(lvl=0,e=f"IND {name} {subtype} COND NOT READY: {msg}", returns=returns) #not time to run - copy last value + use_last_values(indicators_dict, name, returns, MA_length) + state.ilog(lvl=0,e=f"IND {name} {subtype} NOT TIME TO RUN - value(and MA) still original", returns=returns) + +#zde nechavame puvodni (pri multiinputu nastavime predchozi hodnoty u vsech vystupu v defaultnim returns) +def use_last_values(indicators_dict, name, returns, MA_length): + def use_last_values_(indicators_dict, name, MA_length): 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=0,e=f"IND {name} {subtype} NOT TIME TO RUN - value(and MA) still original") + + if returns is not None and len(returns)>0: + for ind_name in returns: + use_last_values_(indicators_dict, ind_name, MA_length) + else: + use_last_values_(indicators_dict, name, MA_length) diff --git a/v2realbot/strategyblocks/inits/init_indicators.py b/v2realbot/strategyblocks/inits/init_indicators.py index 0897115..3f41be1 100644 --- a/v2realbot/strategyblocks/inits/init_indicators.py +++ b/v2realbot/strategyblocks/inits/init_indicators.py @@ -32,10 +32,21 @@ def initialize_dynamic_indicators(state): case _: raise(f"ind output must be bar or tick {indname}") + #inicializujeme vzdy main indicators_dict[indname] = [] + #inicializujeme multioutputs + returns = safe_get(indsettings, 'returns', []) + for ind_name in returns: + indicators_dict[ind_name] = [] #pokud ma MA_length incializujeme i MA variantu if safe_get(indsettings, 'MA_length', False): - indicators_dict[indname+"MA"] = [] + #inicializujeme bud v hlavni serii + if len(returns)==0: + indicators_dict[indname+"MA"] = [] + #nebo v multivystupech + else: + for ind_name in returns: + indicators_dict[ind_name+"MA"] = [] #Specifické Inicializace dle type for option,value in list(indsettings.items()):