multioutput indicators #15 + talib custom indicator support

This commit is contained in:
David Brazda
2024-01-16 15:17:14 +01:00
parent 5d47a7ac58
commit c1145fec5b
28 changed files with 598 additions and 141 deletions

View File

@ -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

View File

@ -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)
@ -1654,18 +1695,25 @@ def preview_indicator_byTOML(id: UUID, indicator: InstantIndicator, save: bool =
print("removed old from EXT_DATA")
#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())

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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";
//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(index);
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,36 +756,57 @@ 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
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) {
//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, index, def||active);
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, index, def||active);
itemEl = create_indicator_button(item, def||active);
tickButtonElement.appendChild(itemEl)
}
}
}
});

View File

@ -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,

View File

@ -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"

View File

@ -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)
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:

View File

@ -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:

View File

@ -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"

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)
#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)

View File

@ -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)

View File

@ -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)

View File

@ -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"

View File

@ -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")

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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")

View File

@ -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)
#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,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:
src = indicators_dict[name][-MA_length:]
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_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+"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)

View File

@ -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):
#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()):