tulipy_ind support, multioutput scale and gzip cache support #106 #115 #114 #112 #107

This commit is contained in:
David Brazda
2024-02-04 17:54:03 +07:00
parent c1145fec5b
commit 8456e6d739
15 changed files with 632 additions and 55 deletions

View File

@ -0,0 +1,22 @@
from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase
from collections import deque
class SimpleMovingAverage(IndicatorBase):
"""
Calculates the Simple Moving Average (SMA) of a given data list.
The SMA is calculated over a specified window size.
If there are insufficient data points for the full window, the behavior
can be controlled by `return_last_if_insufficient`: if True, the last data point is returned,
otherwise, 0 is returned.
"""
def __init__(self, period, return_last_if_insufficient=False, state=None):
super().__init__(state)
self.window_size = period
self.return_last_if_insufficient = return_last_if_insufficient
self.data_points = deque(maxlen=period)
def next(self, data):
self.data_points.append(data[-1])
if len(self.data_points) < self.window_size:
return data[-1] if self.return_last_if_insufficient else 0
return sum(self.data_points) / len(self.data_points)

View File

@ -0,0 +1,64 @@
from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase
from collections import deque
import numpy as np
class SuperTrend(IndicatorBase):
"""
Advanced implementation of the SuperTrend indicator, dynamically calculating the trend direction.
This approach considers the previous trend when determining the new trend, making the indicator
more responsive to price changes. Returns [up, dn, is_trend] with is_trend being 0 (no trend),
1 (uptrend), or -1 (downtrend). If there aren't enough values for ATR, it returns the last close
for up and dn, and 0 for is_trend.
Generated with Indicator Plugin Builder.
Link: [https://chat.openai.com/g/g-aCKuSmbIe-indicator-plugin-builder/c/8cf9ec38-31e0-4577-8331-22919ae149ab]
Zajimavá advanced verze - detaily viz link výše
"""
def __init__(self, atr_period=14, multiplier=3, state=None):
super().__init__(state)
self.atr_period = atr_period
self.multiplier = multiplier
self.tr_queue = deque(maxlen=atr_period)
self.final_upperband = None
self.final_lowerband = None
self.is_trend = 0
def next(self, high, low, close):
if len(close) < self.atr_period:
return [close[-1], close[-1],close[-1], close[-1], 0]
# True Range calculation
current_high = high[-1]
current_low = low[-1]
previous_close = close[-2] if len(close) > 1 else close[-1]
true_range = max(current_high - current_low, abs(current_high - previous_close), abs(current_low - previous_close))
# Updating the True Range queue
self.tr_queue.append(true_range)
if len(self.tr_queue) < self.atr_period:
return [close[-1], close[-1],close[-1], close[-1], 0]
# ATR calculation
atr = sum(self.tr_queue) / self.atr_period
# Basic upper and lower bands
basic_upperband = (current_high + current_low) / 2 + self.multiplier * atr
basic_lowerband = (current_high + current_low) / 2 - self.multiplier * atr
# Final upper and lower bands
if self.final_upperband is None or self.final_lowerband is None:
self.final_upperband = basic_upperband
self.final_lowerband = basic_lowerband
else:
if close[-1] <= self.final_upperband:
self.final_upperband = basic_upperband
if close[-1] >= self.final_lowerband:
self.final_lowerband = basic_lowerband
# Trend determination
if close[-1] > self.final_upperband:
self.is_trend = 1
elif close[-1] < self.final_lowerband:
self.is_trend = -1
return [basic_upperband,basic_lowerband,self.final_upperband, self.final_lowerband, self.is_trend]

View File

@ -0,0 +1,57 @@
from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase
from collections import deque
class SuperTrend1(IndicatorBase):
"""
The SuperTrend indicator is a trend following indicator which uses ATR to calculate its values.
It returns a list with three elements: [up, dn, is_trend].
`is_trend` can be 1 (uptrend), -1 (downtrend), or 0 (no trend or not enough data).
If there are not enough values for ATR, it returns close[-1] for both `up` and `dn`, and 0 for `is_trend`.
Note: Code generated with Indicator Plugin Builder.
Link: [Indicator Plugin Builder Conversation](https://openai.com/chat/)
"""
def __init__(self, multiplier=3, period=14, state=None):
super().__init__(state)
self.multiplier = multiplier
self.period = period
self.atr_values = deque(maxlen=period)
self.previous_supertrend = None
self.previous_close = None
self.previous_trend = 0
def next(self, high, low, close):
if len(high) < self.period or len(low) < self.period or len(close) < self.period:
return [close[-1], close[-1], 0]
# Calculate True Range
tr = max(high[-1] - low[-1], abs(high[-1] - close[-2]), abs(low[-1] - close[-2]))
self.atr_values.append(tr)
if len(self.atr_values) < self.period:
return [close[-1], close[-1], 0]
# Calculate ATR
atr = sum(self.atr_values) / self.period
# Calculate Supertrend
up = close[-1] - (self.multiplier * atr)
dn = close[-1] + (self.multiplier * atr)
if self.previous_supertrend is None:
self.previous_supertrend = up
trend = 0
if close[-1] > self.previous_supertrend:
trend = 1
up = max(up, self.previous_supertrend)
elif close[-1] < self.previous_supertrend:
trend = -1
dn = min(dn, self.previous_supertrend)
self.previous_supertrend = up if trend == 1 else dn
self.previous_close = close[-1]
self.previous_trend = trend
return [up, dn, trend]

View File

@ -0,0 +1,61 @@
from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase
from collections import deque
class SuperTrendBard(IndicatorBase):
"""
Calculates Supertrend indicator values.
"""
def __init__(self, period=10, multiplier=3, state=None):
super().__init__(state)
self.atr_period = period
self.multiplier = multiplier
self.highs = deque(maxlen=period)
self.lows = deque(maxlen=period)
self.atr = 0
self.upbound = None # Can set a default value if desired
self.downbound = None # Can set a default value if desired
self.is_trend = None
def next(self, high, low, close):
high = high[-1]
low = low[-1]
close = close[-1]
# Update ATR calculation
self.highs.append(high)
self.lows.append(low)
# Check for sufficient data
if len(self.highs) < self.atr_period or len(self.lows) < self.atr_period:
return [close, close, 0]
if len(self.highs) == self.atr_period:
true_range = max(high - low, abs(high - self.highs[0]), abs(low - self.lows[0]))
self.atr = (self.atr * (self.atr_period - 1) + true_range) / self.atr_period
# Calculate Supertrend
if self.upbound is None:
self.upbound = close - (self.multiplier * self.atr)
self.downbound = close + (self.multiplier * self.atr)
self.is_trend = None # Set initial trend state to unknown
else:
# Determine trend based on previous trend and current price
if self.is_trend == 1:
# Uptrend continuation
self.upbound = max(self.upbound, close - (self.multiplier * self.atr)) # Adjust upbound dynamically
self.is_trend = 1 if close > self.upbound else 0 # Recalculate trend if needed
elif self.is_trend == -1 and close < self.downbound:
# Downtrend continues
self.downbound = min(self.downbound, low + (self.multiplier * self.atr))
self.is_trend = -1
else:
# Recalculate trend based on current price
self.is_trend = 1 if close > self.upbound else -1 if close < self.downbound else 0
# Update Supertrend lines based on new trend
if self.is_trend == 1:
self.upbound = max(self.upbound, close - (self.multiplier * self.atr))
else:
self.downbound = min(self.downbound, low + (self.multiplier * self.atr))
return [self.upbound, self.downbound, self.is_trend]

View File

@ -0,0 +1,50 @@
from v2realbot.strategyblocks.indicators.custom.classes.indicatorbase import IndicatorBase
from collections import deque
import numpy as np
class SuperTrendTV(IndicatorBase):
"""
The SuperTrend indicator is a trend following indicator that is used to identify the direction of the price relative to its historical volatility.
It combines the Average True Range (ATR) with the moving average to determine trend direction and reversal points.
This implementation was generated with Indicator Plugin Builder.
See conversation: [Indicator Plugin Builder](https://chat.openai.com/g/g-aCKuSmbIe-indicator-plugin-builder/c/1ad650dc-05f1-4cf6-b936-772c0ea86ffa)
inspirace https://www.tradingview.com/script/r6dAP7yi/
"""
def __init__(self, atr_period=10, atr_multiplier=3.0, state=None):
super().__init__(state)
self.atr_period = atr_period
self.atr_multiplier = atr_multiplier
self.highs = deque(maxlen=atr_period)
self.lows = deque(maxlen=atr_period)
self.closes = deque(maxlen=atr_period)
self.up = None
self.down = None
self.trend = 1
def next(self, high, low, close):
self.highs.append(high[-1])
self.lows.append(low[-1])
self.closes.append(close[-1])
if len(self.highs) < self.atr_period:
return [close[-1], close[-1], 0]
tr = [max(hi - lo, abs(hi - cl), abs(lo - cl))
for hi, lo, cl in zip(self.highs, self.lows, self.closes)]
atr = np.mean(tr[-self.atr_period:])
src = (high[-1] + low[-1]) / 2
up = src - (self.atr_multiplier * atr)
dn = src + (self.atr_multiplier * atr)
if self.up is None:
self.up = up
self.down = dn
else:
self.up = max(up, self.up) if close[-2] > self.up else up
self.down = min(dn, self.down) if close[-2] < self.down else dn
previous_trend = self.trend
self.trend = 1 if (self.trend == -1 and close[-1] > self.down) else -1 if (self.trend == 1 and close[-1] < self.up) else self.trend
return [self.up, self.down, self.trend]

View File

@ -11,36 +11,30 @@ from v2realbot.strategyblocks.indicators.helpers import value_or_indicator
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)
# příklad toml pro indikátor ATR(high, low, close, timeperiod=14) -
# 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 ?
# 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")
funcName = "talib_ind"
type = safe_get(params, "type", None)
if type is None:
return -2, "type is required"
#ßsource = safe_get(params, "source", None)
lookback = safe_get(params, "lookback",None) #celkovy lookback pro vsechny vstupni serie
if lookback is not None:
#lookback muze byt odkaz na indikator, pak berem jeho hodnotu
lookback = int(value_or_indicator(state, lookback))
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))
defval = float(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)
@ -55,7 +49,7 @@ def talib_ind(state, params, name, returns):
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))
series_list.append(np.array(source_series[-lookback:] if lookback is not None else source_series, dtype=np.float64))
for key, val in params.get("keys",{}).items():
keyArgs[key] = int(value_or_indicator(state, val))
@ -65,13 +59,28 @@ def talib_ind(state, params, name, returns):
ma_value = talib_function(*series_list, **keyArgs)
if not np.isfinite(ma_value[-1]):
val = defval
#jde o multioutput, dostavame tuple a prevedeme na list (odpovida poradi v returns)
#TODO zapracovat sem def val a isfinite
if isinstance(ma_value, tuple):
ma_value = list(ma_value)
for index, res in enumerate(ma_value):
if not np.isfinite(res[-1]):
ma_value[index] = defval
else:
ma_value[index] = round(res[-1],5)
if res[-1] == 0:
ma_value[index] = defval
val = ma_value
#single output
else:
val = round(ma_value[-1],4)
if not np.isfinite(ma_value[-1]):
val = defval
else:
val = round(ma_value[-1],4)
if val == 0:
val = defval
if val == 0:
val = defval
state.ilog(lvl=1,e=f"INSIDE {name}:{funcName} {val} {type=} {lookback=}", **params)
state.ilog(lvl=1,e=f"INSIDE {name}:{funcName} {str(val)} {type=} {lookback=}", **params)
return 0, val

View File

@ -0,0 +1,100 @@
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 tulipy
#NOTE if Exception is raised by the plugin - the system stores previous value for the indicator
#plugin returns configurable default value when exception happens
#this overrides default behaviour which is when plugin raises exception the custom_hub will store previous value of the indicator as next value
#IMPLEMENTS usiong of any indicator from tulipy library
def tulipy_ind(state, params, name, returns):
funcName = "tulipy_ind"
type = safe_get(params, "type", None)
if type is None:
return -2, "type is required"
#ßsource = safe_get(params, "source", None)
lookback = safe_get(params, "lookback",None) #celkovy lookback pro vsechny vstupni serie
if lookback is not None:
#lookback muze byt odkaz na indikator, pak berem jeho hodnotu
lookback = int(value_or_indicator(state, lookback))
start = safe_get(params, "start","sharp") #linear/sharp
defval = safe_get(params, "defval",0)
params = safe_get(params, "params", dict(series=[], keys=[]))
defval = float(value_or_indicator(state, defval))
try:
#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)
#REMOVED: neupravujeme lookback, ale az options nize
# if index == 0 and lookback is not None:
# akt_pocet = len(source_series)
# if akt_pocet < lookback and start == "linear":
# lookback = akt_pocet
#to same pokud mame nejake op
series_list.append(np.array(source_series[-lookback:] if lookback is not None else source_series, dtype=np.float64))
for key, val in params.get("keys",{}).items():
keyArgs[key] = int(value_or_indicator(state, val))
#pokud jsou zde nejake options s period nebo timeperiodou a mame nastaveny linear, pak zkracujeme pro lepsi rozjezd
#zatim porovnavame s prvni serii - bereme ze jsou vsechny ze stejne skupiny
#zatim pri linearu davame vzdy akt. - 1 (napr. RSI potrebuje extra datapoint)
if key in ["period", "timeperiod"]:
akt_pocet = len(series_list[0])
if akt_pocet < keyArgs[key] and start == "linear" and akt_pocet != 1:
keyArgs[key] = akt_pocet - 1
printanyway(f"zkracujeme na rozjezd celkem v serii {akt_pocet} nastavujeme period na {keyArgs[key]}")
type = "tulipy."+type
talib_function = eval(type)
ma_value = talib_function(*series_list, **keyArgs)
#jde o multioutput, dostavame tuple a prevedeme na list (odpovida poradi v returns)
#TODO zapracovat sem def val a isfinite
if isinstance(ma_value, tuple):
ma_value = list(ma_value)
for index, res in enumerate(ma_value):
if not np.isfinite(res[-1]):
ma_value[index] = defval
else:
ma_value[index] = round(res[-1],5)
if res[-1] == 0:
ma_value[index] = defval
val = ma_value
#single output
else:
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} {str(val)} {type=} {lookback=}", **params)
#pri Exceptione vracime default value (pokud raisneme Exception do custom_hubu o patro vys, tak ten pouzije posledni hodnotu)
except Exception as e:
state.ilog(lvl=1,e=f"IND ERROR {name} {funcName} vracime default {defval}", message=str(e)+format_exc())
val = defval if defval is not None else 0
finally:
return 0, val

View File

@ -7,6 +7,7 @@ from traceback import format_exc
import importlib
import v2realbot.strategyblocks.indicators.custom as ci
from v2realbot.strategyblocks.indicators.helpers import find_index_optimized
import numpy as np
def populate_dynamic_custom_indicator(data, state: StrategyState, name):
@ -172,6 +173,9 @@ def populate_dynamic_custom_indicator(data, state: StrategyState, name):
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):
#checkneme jestli nejde o numpy typ (napr. posledni clen z numpy), prevedem na python basic typ
if isinstance(ret_val, (np.ndarray, np.generic)):
ret_val = ret_val.item()
ret_val = {name: ret_val}
#v ostatnich pripadech predpokladame jiz dict

View File

@ -28,6 +28,7 @@ def find_index_optimized(time_list, seconds):
#podle toho jak se osvedci se zakl.indikatory to s state
#zatim se mi to moc nezda
#vraci skalar nebo posledni hodnoty indikatoru
def value_or_indicator(state,value):
#preklad direktivy podle typu, pokud je int anebo float - je to primo hodnota
#pokud je str, jde o indikator a dotahujeme posledni hodnotu z nej
@ -45,7 +46,28 @@ def value_or_indicator(state,value):
ret = 0
state.ilog(lvl=1,e=f"Neexistuje indikator s nazvem {value} vracime 0" + str(e) + format_exc())
return ret
#vraci skalar nebo pokud je indikator tak cely list indikatoru
def value_or_indicator_list(state,value):
#preklad direktivy podle typu, pokud je int anebo float - je to primo hodnota
#pokud je str, jde o indikator a dotahujeme posledni hodnotu z nej
if isinstance(value, (float, int)):
return value
elif isinstance(value, str):
try:
#pokud existuje v indikatoru MA bereme MA jinak indikator, pokud neexistuje bereme bar
ret = get_source_or_MA(state, indicator=value)
lvl = 0
#TODO tento len dat pryc po overeni
delka = len(ret)
if delka == 0:
lvl = 1
state.ilog(lvl=1,e=f"Pro porovnani bereme cely list s delkou {delka} z indikatoru {value}")
except Exception as e :
ret = 0
state.ilog(lvl=1,e=f"Neexistuje indikator s nazvem {value} vracime 0" + str(e) + format_exc())
return ret
#OPTIMALIZOVANO CHATGPT
#funkce vytvori podminky (bud pro AND/OR) z pracovniho dict
def evaluate_directive_conditions(state, work_dict, cond_type):
@ -67,9 +89,9 @@ def evaluate_directive_conditions(state, work_dict, cond_type):
"risingc": lambda ind, val: isrisingc(get_source_or_MA(state, ind), val),
"falling": lambda ind, val: isfalling(get_source_or_MA(state, ind), val),
"rising": lambda ind, val: isrising(get_source_or_MA(state, ind), val),
"crossed_down": lambda ind, val: buy_if_crossed_down(state, ind, value_or_indicator(state,val)),
"crossed_up": lambda ind, val: buy_if_crossed_up(state, ind, value_or_indicator(state,val)),
"crossed": lambda ind, val: buy_if_crossed_down(state, ind, value_or_indicator(state,val)) or buy_if_crossed_up(state, ind, value_or_indicator(state,val)),
"crossed_down": lambda ind, val: buy_if_crossed_down(state, ind, value_or_indicator_list(state,val)),
"crossed_up": lambda ind, val: buy_if_crossed_up(state, ind, value_or_indicator_list(state,val)),
"crossed": lambda ind, val: buy_if_crossed_down(state, ind, value_or_indicator_list(state,val)) or buy_if_crossed_up(state, ind, value_or_indicator_list(state,val)),
"pivot_a": lambda ind, val: is_pivot(source=get_source_or_MA(state, ind), leg_number=val, type="A"),
"pivot_v": lambda ind, val: is_pivot(source=get_source_or_MA(state, ind), leg_number=val, type="V"),
"still_for": lambda ind, val: is_still(get_source_or_MA(state, ind), val, 2),
@ -129,13 +151,13 @@ def get_source_series(state, source: str, numpy: bool = False):
#TYTO NEJSPIS DAT do util
#vrati true pokud dany indikator prekrocil threshold dolu
def buy_if_crossed_down(state, indicator, value):
res = crossed_down(threshold=value, list=get_source_or_MA(state, indicator))
res = crossed_down(value=value, primary_line=get_source_or_MA(state, indicator))
#state.ilog(lvl=0,e=f"signal_if_crossed_down {indicator} {value} {res}")
return res
#vrati true pokud dany indikator prekrocil threshold nahoru
def buy_if_crossed_up(state, indicator, value):
res = crossed_up(threshold=value, list=get_source_or_MA(state, indicator))
res = crossed_up(value=value, primary_line=get_source_or_MA(state, indicator))
#state.ilog(lvl=0,e=f"signal_if_crossed_up {indicator} {value} {res}")
return res