diff --git a/testy/evalscope.py b/testy/evalscope.py
new file mode 100644
index 0000000..815aa3d
--- /dev/null
+++ b/testy/evalscope.py
@@ -0,0 +1,23 @@
+# def my_function():
+# return "Hello, World!"
+
+
+# def call_function_by_name():
+# func_name = "my_function"
+# # Use eval to call the function
+# result = eval(func_name)()
+
+def my_function():
+ return "Hello, World!"
+
+
+def call_function_by_name():
+ # Create a closure to capture the my_function function
+ def inner_function():
+ return eval("my_function")()
+
+ result = inner_function()
+ return result
+
+
+print(call_function_by_name())
\ No newline at end of file
diff --git a/testy/higherhighs.py b/testy/higherhighs.py
new file mode 100644
index 0000000..151e4cc
--- /dev/null
+++ b/testy/higherhighs.py
@@ -0,0 +1,184 @@
+def is_rising_trend(price_list):
+ """
+ This function determines whether prices are consistently creating higher highs and higher lows.
+
+ Args:
+ price_list: A list of prices.
+
+ Returns:
+ True if the prices are in a rising trend, False otherwise.
+ """
+
+ if len(price_list) < 2:
+ return False
+ #
+ global last_last_low
+ global last_high
+ global last_low
+ global last_last_high
+ global last_last_low
+ last_high = price_list[0]
+ last_low = None
+ last_last_high = price_list[0]
+ last_last_low = price_list[0]
+ print(price_list)
+
+ for i in range(1, len(price_list)):
+ print("processing",price_list[i])
+
+ #pokud je dalsi rostouci
+ if price_list[i] > price_list[i-1]:
+ #je vetsi nez LH - stává se LH
+ if price_list[i] > last_high:
+ #last_last_high = last_high
+ last_high = price_list[i]
+ #print("nova last last high",last_last_high)
+ print("nove last high",last_high)
+
+ #pokud je klesajici
+ elif price_list[i] < price_list[i-1]:
+
+ #pokud je cena nad last last jsme ok
+ if price_list[i] > last_last_low:
+ if last_low is None or price_list[i] < last_low:
+ if last_low is not None:
+ #vytvorime nove last last low
+ last_last_low = last_low
+ print("nova last last low",last_last_low)
+ #rovnou porovname cenu zda neklesla
+ if price_list[i] < last_last_low:
+ print("kleslo pod last last low")
+ return False
+ #mame nove last low
+ last_low = price_list[i]
+ print("nove last low",last_low)
+ else:
+ print("kleslo pod last last low, neroste")
+ return False
+
+ print("funkce skoncila, stale roste")
+ return True
+
+# Example usage:
+#price_list = [1,2,3,2,2.5,3,1.8,4,5,4,4.5,4.3,4.8,4.5,6]
+
+
+price_list = [
+ # -0.0106,
+ # -0.001,
+ # 0.0133,
+ # 0.0116,
+ # 0.0075,
+ -0.015,
+ -0.0142,
+ -0.0071,
+ -0.0077,
+ -0.0083,
+ 0.0016,
+ 0.0266,
+ 0.0355,
+ 0.0455,
+ 0.0563,
+ 0.1064,
+ 0.1283,
+ 0.1271,
+ 0.1277,
+ 0.1355,
+ 0.152,
+ 0.1376,
+ 0.1164,
+ 0.1115,
+ 0.102,
+ 0.0808,
+ 0.0699,
+ 0.0625,
+ 0.0593,
+ 0.0485,
+ 0.0323,
+ 0.0382,
+ 0.0403,
+ 0.0441,
+ 0.0526,
+ 0.0728,
+ 0.0841,
+ 0.1029,
+ 0.1055,
+ 0.0964,
+ 0.0841,
+ 0.0677,
+ 0.0782,
+ 0.0877,
+ 0.1099,
+ 0.1215,
+ 0.1379,
+ 0.1234,
+ 0.1,
+ 0.0949,
+ 0.1133,
+ 0.1428,
+ 0.1525,
+ 0.166,
+ 0.1788,
+ 0.1901,
+ 0.1967,
+ 0.2099,
+ 0.2407,
+ 0.2719,
+ 0.2897,
+ 0.3101,
+ 0.331,
+ 0.328,
+ 0.3241,
+ 0.3258,
+ 0.3275,
+ 0.3188,
+ 0.3071,
+ 0.2942,
+ 0.2939,
+ 0.277,
+ 0.2498,
+ 0.2464,
+ 0.2413,
+ 0.2377,
+ 0.2112,
+ 0.2076,
+ 0.2018,
+ 0.1975,
+ 0.1814,
+ 0.1776,
+ 0.1761,
+ 0.1868,
+ 0.1961,
+ 0.2016,
+ 0.2313,
+ 0.2485,
+ 0.2668,
+ 0.2973,
+ 0.3278,
+ 0.3581,
+ 0.3893,
+ 0.3997,
+ 0.4176,
+ 0.4285,
+ 0.4369,
+ 0.4457,
+ 0.4524,
+ 0.4482,
+ 0.4439,
+ 0.4302,
+ 0.4205,
+ 0.4278,
+ 0.4345,
+ 0.4403,
+ 0.4504,
+ 0.4523,
+ 0.461,
+ 0.4649,
+ 0.4618,
+ 0.4675,
+ 0.4724]
+
+
+result = is_rising_trend(price_list)
+print(result) # This will print [(4, 60), (7, 62)] for the provided example
+
diff --git a/v2realbot/ENTRY_ClassicSL_v01.py b/v2realbot/ENTRY_ClassicSL_v01.py
index 619b1e0..b3c01e1 100644
--- a/v2realbot/ENTRY_ClassicSL_v01.py
+++ b/v2realbot/ENTRY_ClassicSL_v01.py
@@ -9,9 +9,10 @@ from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeSt
from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, print, safe_get, round2five, is_open_rush, is_close_rush, is_still, is_window_open, eval_cond_dict, Average, crossed_down, crossed_up, crossed, is_pivot, json_serial
from v2realbot.utils.directive_utils import get_conditions_from_configuration
from v2realbot.common.model import SLHistory
-from datetime import datetime
+from datetime import datetime, timedelta
from v2realbot.config import KW
from uuid import uuid4
+import random
import json
#from icecream import install, ic
#from rich import print
@@ -250,9 +251,174 @@ def next(data, state: StrategyState):
populate_dynamic_ema_indicator(name = name)
elif type == "NATR":
populate_dynamic_natr_indicator(name = name)
+ elif type == "custom":
+ populate_dynamic_custom_indicator(name = name)
else:
return
+
+ #WIP -
+ def populate_dynamic_custom_indicator(name):
+ ind_type = "custom"
+ options = safe_get(state.vars.indicators, name, None)
+ if options is None:
+ state.ilog(e=f"No options for {name} in stratvars")
+ return
+
+ if safe_get(options, "type", False) is False or safe_get(options, "type", False) != ind_type:
+ state.ilog(e="Type error")
+ return
+
+ #poustet kazdy tick nebo jenom na confirmed baru (on_confirmed_only = true)
+ subtype = safe_get(options, 'subtype', False)
+ if subtype is False:
+ state.ilog(e=f"No subtype for {name} in stratvars")
+ return
+
+ #if MA is required
+ MA_length = safe_get(options, "MA_length", None)
+
+ def is_time_to_run():
+ # on_confirmed_only = true (def. False)
+ # start_at_bar_index = 2 (def. None)
+ # start_at_time = "9:31" (def. None)
+ # repeat_every_Nbar = N (def.None) (opakovat každý N bar, 1 - každý bar, 2 - každý 2., 0 - pouze jednou)
+ # repeat_every_Nmin = N (def. None) opakovat každých N minut
+
+ on_confirmed_only = safe_get(options, 'on_confirmed_only', False)
+ start_at_bar_index = safe_get(options, 'start_at_bar_index', None)
+ start_at_time = safe_get(options, 'start_at_time', None) # "9:30"
+ repeat_every_Nbar = safe_get(options, 'repeat_every_Nbar', None)
+ repeat_every_Nmin = safe_get(options, 'repeat_every_Nmin', None)
+
+ #stavové promenne v ramci indikatoru last_run_time a last_run_index - pro repeat_every.. direktivy
+ last_run_time = safe_get(options, 'last_run_time', None)
+ last_run_index = safe_get(options, 'last_run_index', None)
+
+ #confirmed
+ cond = on_confirmed_only is False or (on_confirmed_only is True and data['confirmed']==1)
+ if cond is False:
+ return cond, "not confirmed"
+
+ #start_at_time - v rámci optimalizace presunout do INIT parametru indikátorů, které se naplní v initu a celou dobu se nemění
+ if start_at_time is not None:
+ dt_now = datetime.fromtimestamp(data["updated"]).astimezone(zoneNY)
+ # Parse the maxTime string into a datetime object with the same date as timeA
+ req_start_time = datetime.strptime(start_at_time, "%H:%M").replace(
+ year=dt_now.year, month=dt_now.month, day=dt_now.day)
+
+ # Compare the time components (hours and minutes) of timeA and maxTime
+ if dt_now.time() > req_start_time.time():
+ state.ilog(e=f"IND {name} {subtype} START FROM TIME - PASSED: now:{dt_now.time()} reqtime:{req_start_time.time()}")
+ else:
+ state.ilog(e=f"IND {name} {subtype} START FROM TIME - NOT YET: now:{dt_now.time()} reqtime:{req_start_time.time()}")
+ cond = False
+
+ if cond is False:
+ return cond, "start_at_time not yet"
+
+ #start_on_bar = 0
+ if start_at_bar_index is not None:
+ cond = start_at_bar_index < data["index"]
+ if cond:
+ state.ilog(e=f"IND {name} {subtype} START FROM BAR - PASSED: now:{data['index']} reqbar:{start_at_bar_index}")
+ else:
+ state.ilog(e=f"IND {name} {subtype} START FROM BAR - NOT YET: now:{data['index']} reqbar:{start_at_bar_index}")
+
+ if cond is False:
+ return cond, "start_at_bar_index not yet"
+
+ #pokud 0 - opakujeme jednou, pokud 1 tak opakujeme vzdy, jinak dle poctu
+ if repeat_every_Nbar is not None:
+ #jiz bezelo - delame dalsi checky, pokud nebezelo, poustime jako true
+ if last_run_index is not None:
+ required_bar_to_run = last_run_index + repeat_every_Nbar
+ if repeat_every_Nbar == 0:
+ state.ilog(e=f"IND {name} {subtype} RUN ONCE ALREADY at:{last_run_index} at:{last_run_time}", repeat_every_Nbar=repeat_every_Nbar, last_run_index=last_run_index)
+ cond = False
+ elif repeat_every_Nbar == 1:
+ pass
+ elif data["index"] < required_bar_to_run:
+ state.ilog(e=f"IND {name} {subtype} REPEAT EVERY N BAR WAITING: req:{required_bar_to_run} now:{data['index']}", repeat_every_Nbar=repeat_every_Nbar, last_run_index=last_run_index)
+ cond = False
+
+ if cond is False:
+ return cond, "repeat_every_Nbar not yet"
+
+ #pokud nepozadovano, pak poustime
+ if repeat_every_Nmin is not None:
+ #porovnavame jen pokud uz bezelo
+ if last_run_time is not None:
+ required_time_to_run = last_run_time + timedelta(minutes=repeat_every_Nmin)
+ datetime_now = datetime.fromtimestamp(data["updated"]).astimezone(zoneNY)
+ if datetime_now < required_time_to_run:
+ state.ilog(e=f"IND {name} {subtype} REPEAT EVERY {repeat_every_Nmin}MINS WAITING", last_run_time=last_run_time, required_time_to_run=required_time_to_run, datetime_now=datetime_now)
+ cond = False
+
+ if cond is False:
+ return cond, "repeat_every_Nmin not yet"
+
+ return cond, "ok"
+
+ #testing custom indicator CODE
+ #TODO customer params bud implicitne **params nebo jako dict
+ # return 0, new_val or -2, "err msg"
+ def opengap(params):
+ funcName = "opengap"
+ param1 = safe_get(params, "param1")
+ param2 = safe_get(params, "param2")
+ state.ilog(e=f"INSIDE {funcName} {param1=} {param2=}", **params)
+ return 0, random.randint(10, 20)
+
+ should_run, msg = is_time_to_run()
+
+ if should_run:
+ #TODO get custom params
+ custom_params = safe_get(options, "cp", None)
+ #vyplnime last_run_time a last_run_index
+ state.vars.indicators[name]["last_run_time"] = datetime.fromtimestamp(data["updated"]).astimezone(zoneNY)
+ state.vars.indicators[name]["last_run_index"] = data["index"]
+
+ # - volame custom funkci pro ziskani hodnoty indikatoru
+ # - tu ulozime jako novou hodnotu indikatoru a prepocteme MAcka pokud je pozadovane
+ # - pokud cas neni, nechavame puvodni, vcetna pripadneho MAcka
+ #pozor jako defaultní hodnotu dává engine 0 - je to ok?
+ try:
+ custom_function = eval(subtype)
+ res_code, new_val = custom_function(custom_params)
+ if res_code == 0:
+ state.indicators[name][-1]=new_val
+ state.ilog(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"])
+ #prepocitame MA if required
+ if MA_length is not None:
+ src = state.indicators[name][-MA_length:]
+ MA_res = ema(src, MA_length)
+ MA_value = round(MA_res[-1],4)
+ state.indicators[name+"MA"][-1]=MA_value
+ state.ilog(e=f"IND {name}MA {subtype} {MA_value}")
+
+ else:
+ raise ValueError(f"IND ERROR {name} {subtype}Funkce {custom_function} vratila {res_code} {new_val}.")
+
+ except Exception as e:
+ if len(state.indicators[name]) >= 2:
+ state.indicators[name][-1]=state.indicators[name][-2]
+ if MA_length is not None and len(state.indicators[name+"MA"]):
+ state.indicators[name+"MA"][-1]=state.indicators[name+"MA"][-2]
+ state.ilog(e=f"IND ERROR {name} {subtype} necháváme původní", message=str(e)+format_exc())
+
+ else:
+ state.ilog(e=f"IND {name} {subtype} COND NOT READY: {msg}")
+
+ #not time to run
+ if len(state.indicators[name]) >= 2:
+ state.indicators[name][-1]=state.indicators[name][-2]
+
+ if MA_length is not None and len(state.indicators[name+"MA"]):
+ state.indicators[name+"MA"][-1]=state.indicators[name+"MA"][-2]
+
+ state.ilog(e=f"IND {name} {subtype} NOT TIME TO RUN - value(and MA) still original")
+
#EMA INDICATOR
# type = EMA, source = [close, vwap, hlcc4], length = [14], on_confirmed_only = [true, false]
def populate_dynamic_ema_indicator(name):
@@ -502,60 +668,72 @@ def next(data, state: StrategyState):
if on_confirmed_only is False or (on_confirmed_only is True and data['confirmed']==1):
try:
- #slow_slope = 99
slope_lookback = safe_get(options, 'slope_lookback', 100)
+ lookback_priceline = safe_get(options, 'lookback_priceline', None)
+ lookback_offset = safe_get(options, 'lookback_offset', 25)
minimum_slope = safe_get(options, 'minimum_slope', 25)
maximum_slope = safe_get(options, "maximum_slope",0.9)
- lookback_offset = safe_get(options, 'lookback_offset', 25)
- #lookback has to be even
- if lookback_offset % 2 != 0:
- lookback_offset += 1
+ #jako levy body pouzivame lookback_priceline INDIKATOR vzdaleny slope_lookback barů
+ if lookback_priceline is not None:
+ try:
+ lookbackprice = state.indicators[lookback_priceline][-slope_lookback-1]
+ lookbacktime = state.bars.updated[-slope_lookback-1]
+ except IndexError:
+ max_delka = len(state.indicators[lookback_priceline])
+ lookbackprice = state.indicators[lookback_priceline][-max_delka]
+ lookbacktime = state.bars.updated[-max_delka]
- #TBD pripdadne /2
- if len(state.bars.close) > (slope_lookback + lookback_offset):
- #test prumer nejvyssi a nejnizsi hodnoty
- # if name == "slope":
-
- #levy bod bude vzdy vzdaleny o slope_lookback
- #ten bude prumerem hodnot lookback_offset a to tak ze polovina offsetu z kazde strany
- array_od = slope_lookback + int(lookback_offset/2)
- array_do = slope_lookback - int(lookback_offset/2)
- lookbackprice_array = state.bars.vwap[-array_od:-array_do]
-
- #dame na porovnani jen prumer
- lookbackprice = round(sum(lookbackprice_array)/lookback_offset,3)
- #lookbackprice = round((min(lookbackprice_array)+max(lookbackprice_array))/2,3)
- # else:
- # #puvodni lookback a od te doby dozadu offset
- # array_od = slope_lookback + lookback_offset
- # array_do = slope_lookback
- # lookbackprice_array = state.bars.vwap[-array_od:-array_do]
- # #obycejný prumer hodnot
- # lookbackprice = round(sum(lookbackprice_array)/lookback_offset,3)
-
- lookbacktime = state.bars.time[-slope_lookback]
else:
- #kdyz neni dostatek hodnot, pouzivame jako levy bod open hodnotu close[0]
- #lookbackprice = state.bars.vwap[0]
-
- #dalsi vyarianta-- lookback je pole z toho všeho co mame
- #lookbackprice = Average(state.bars.vwap)
+ #NEMAME LOOKBACK PRICLINE - pouzivame stary způsob výpočtu, toto pozdeji decomissionovat
+ #lookback has to be even
+ if lookback_offset % 2 != 0:
+ lookback_offset += 1
-
+ #TBD pripdadne /2
+ if len(state.bars.close) > (slope_lookback + lookback_offset):
+ #test prumer nejvyssi a nejnizsi hodnoty
+ # if name == "slope":
- #pokud neni dostatek, bereme vzdy prvni petinu z dostupnych barů
- # a z ní uděláme průměr
- cnt = len(state.bars.close)
- if cnt>5:
- sliced_to = int(cnt/5)
- lookbackprice= Average(state.bars.vwap[:sliced_to])
- lookbacktime = state.bars.time[int(sliced_to/2)]
+ #levy bod bude vzdy vzdaleny o slope_lookback
+ #ten bude prumerem hodnot lookback_offset a to tak ze polovina offsetu z kazde strany
+ array_od = slope_lookback + int(lookback_offset/2)
+ array_do = slope_lookback - int(lookback_offset/2)
+ lookbackprice_array = state.bars.vwap[-array_od:-array_do]
+
+ #dame na porovnani jen prumer
+ lookbackprice = round(sum(lookbackprice_array)/lookback_offset,3)
+ #lookbackprice = round((min(lookbackprice_array)+max(lookbackprice_array))/2,3)
+ # else:
+ # #puvodni lookback a od te doby dozadu offset
+ # array_od = slope_lookback + lookback_offset
+ # array_do = slope_lookback
+ # lookbackprice_array = state.bars.vwap[-array_od:-array_do]
+ # #obycejný prumer hodnot
+ # lookbackprice = round(sum(lookbackprice_array)/lookback_offset,3)
+
+ lookbacktime = state.bars.time[-slope_lookback]
else:
- lookbackprice = Average(state.bars.vwap)
- lookbacktime = state.bars.time[0]
-
- state.ilog(e=f"IND {name} slope - not enough data bereme left bod open", slope_lookback=slope_lookback, lookbackprice=lookbackprice)
+ #kdyz neni dostatek hodnot, pouzivame jako levy bod open hodnotu close[0]
+ #lookbackprice = state.bars.vwap[0]
+
+ #dalsi vyarianta-- lookback je pole z toho všeho co mame
+ #lookbackprice = Average(state.bars.vwap)
+
+
+
+ #pokud neni dostatek, bereme vzdy prvni petinu z dostupnych barů
+ # a z ní uděláme průměr
+ cnt = len(state.bars.close)
+ if cnt>5:
+ sliced_to = int(cnt/5)
+ lookbackprice= Average(state.bars.vwap[:sliced_to])
+ lookbacktime = state.bars.time[int(sliced_to/2)]
+ else:
+ lookbackprice = Average(state.bars.vwap)
+ lookbacktime = state.bars.time[0]
+
+ state.ilog(e=f"IND {name} slope - not enough data bereme left bod open", slope_lookback=slope_lookback, lookbackprice=lookbackprice)
#výpočet úhlu - a jeho normalizace
slope = ((state.bars.close[-1] - lookbackprice)/lookbackprice)*100
@@ -577,7 +755,9 @@ def next(data, state: StrategyState):
state.indicators[name+"MA"][-1]=slopeMA
last_slopesMA = state.indicators[name+"MA"][-10:]
- state.ilog(e=f"{name=} {slope=} {slopeMA=}", msg=f"{lookbackprice=} {lookbacktime=}", slope_lookback=slope_lookback, lookbackoffset=lookback_offset, lookbacktime=lookbacktime, minimum_slope=minimum_slope, last_slopes=state.indicators[name][-10:], last_slopesMA=last_slopesMA)
+ lb_priceline_string = "from "+lookback_priceline if lookback_priceline is not None else ""
+
+ state.ilog(e=f"IND {name} {lb_priceline_string} {slope=} {slopeMA=}", msg=f"{lookbackprice=} {lookbacktime=}", lookback_priceline=lookback_priceline, lookbackprice=lookbackprice, lookbacktime=lookbacktime, slope_lookback=slope_lookback, lookbackoffset=lookback_offset, minimum_slope=minimum_slope, last_slopes=state.indicators[name][-10:], last_slopesMA=last_slopesMA)
#dale pracujeme s timto MAckovanym slope
#slope = slopeMA
@@ -764,7 +944,91 @@ def next(data, state: StrategyState):
return price2dec(float(state.avgp)+normalized_max_profit,3) if int(state.positions) > 0 else price2dec(float(state.avgp)-normalized_max_profit,3)
- #TBD pripadne opet dat parsovani pole do INITu
+
+ def reverse_conditions_met(direction: TradeDirection):
+ if direction == TradeDirection.LONG:
+ smer = "long"
+ else:
+ smer = "short"
+
+ directive_name = "exit_cond_only_on_confirmed"
+ exit_cond_only_on_confirmed = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
+
+ if exit_cond_only_on_confirmed and data['confirmed'] == 0:
+ state.ilog("REVERSAL CHECK COND ONLY ON CONFIRMED BAR")
+ return False
+
+ #TOTO zatim u REVERSU neresime
+ # #POKUD je nastaven MIN PROFIT, zkontrolujeme ho a az pripadne pustime CONDITIONY
+ # directive_name = "exit_cond_min_profit"
+ # exit_cond_min_profit = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, None))
+
+ # #máme nastavený exit_cond_min_profit
+ # # zjistíme, zda jsme v daném profit a případně nepustíme dál
+ # # , zjistíme aktuální cenu a přičteme k avgp tento profit a podle toho pustime dal
+
+ # if exit_cond_min_profit is not None:
+ # exit_cond_min_profit_normalized = normalize_tick(float(exit_cond_min_profit))
+ # exit_cond_goal_price = price2dec(float(state.avgp)+exit_cond_min_profit_normalized,3) if int(state.positions) > 0 else price2dec(float(state.avgp)-exit_cond_min_profit_normalized,3)
+ # curr_price = float(data["close"])
+ # state.ilog(e=f"EXIT COND min profit {exit_cond_goal_price=} {exit_cond_min_profit=} {exit_cond_min_profit_normalized=} {curr_price=}")
+ # if (int(state.positions) < 0 and curr_price<=exit_cond_goal_price) or (int(state.positions) > 0 and curr_price>=exit_cond_goal_price):
+ # state.ilog(e=f"EXIT COND min profit PASS - POKRACUJEME")
+ # else:
+ # state.ilog(e=f"EXIT COND min profit NOT PASS")
+ # return False
+
+ #TOTO ZATIM NEMA VYZNAM
+ # options = safe_get(state.vars, 'exit_conditions', None)
+ # if options is None:
+ # state.ilog(e="No options for exit conditions in stratvars")
+ # return False
+
+ # disable_exit_proteciton_when = dict(AND=dict(), OR=dict())
+
+ # #preconditions
+ # disable_exit_proteciton_when['disabled_in_config'] = safe_get(options, 'enabled', False) is False
+ # #too good to be true (maximum profit)
+ # #disable_sell_proteciton_when['tgtbt_reached'] = safe_get(options, 'tgtbt', False) is False
+ # disable_exit_proteciton_when['disable_if_positions_above'] = int(safe_get(options, 'disable_if_positions_above', 0)) < abs(int(state.positions))
+
+ # #testing preconditions
+ # result, conditions_met = eval_cond_dict(disable_exit_proteciton_when)
+ # if result:
+ # state.ilog(e=f"EXIT_CONDITION for{smer} DISABLED by {conditions_met}", **conditions_met)
+ # return False
+
+ #bereme bud exit condition signalu, ktery activeTrade vygeneroval+ fallback na general
+ state.ilog(e=f"REVERSE CONDITIONS ENTRY {smer}", conditions=state.vars.conditions[KW.reverse])
+
+ mother_signal = state.vars.activeTrade.generated_by
+
+ if mother_signal is not None:
+ cond_dict = state.vars.conditions[KW.reverse][state.vars.activeTrade.generated_by][smer]
+ result, conditions_met = evaluate_directive_conditions(cond_dict, "OR")
+ state.ilog(e=f"REVERSE CONDITIONS of {mother_signal} =OR= {result}", **conditions_met, cond_dict=cond_dict)
+ if result:
+ return True
+
+ #OR neprosly testujeme AND
+ result, conditions_met = evaluate_directive_conditions(cond_dict, "AND")
+ state.ilog(e=f"REVERSE CONDITIONS of {mother_signal} =AND= {result}", **conditions_met, cond_dict=cond_dict)
+ if result:
+ return True
+
+
+ #pokud nemame mother signal nebo exit nevratil nic, fallback na common
+ cond_dict = state.vars.conditions[KW.reverse]["common"][smer]
+ result, conditions_met = evaluate_directive_conditions(cond_dict, "OR")
+ state.ilog(e=f"REVERSE CONDITIONS of COMMON =OR= {result}", **conditions_met, cond_dict=cond_dict)
+ if result:
+ return True
+
+ #OR neprosly testujeme AND
+ result, conditions_met = evaluate_directive_conditions(cond_dict, "AND")
+ state.ilog(e=f"REVERSE CONDITIONS of COMMON =AND= {result}", **conditions_met, cond_dict=cond_dict)
+ if result:
+ return True
def exit_conditions_met(direction: TradeDirection):
if direction == TradeDirection.LONG:
@@ -945,8 +1209,9 @@ def next(data, state: StrategyState):
insert_SL_history()
state.ilog(e=f"SL TRAIL GOAL {smer} reached {move_SL_threshold} SL moved to {state.vars.activeTrade.stoploss_value}", offset_normalized=offset_normalized, def_SL_normalized=def_SL_normalized)
- def close_position(direction: TradeDirection, reason: str):
- state.ilog(e=f"CLOSING TRADE {reason} {str(direction)}", curr_price=data["close"], trade=state.vars.activeTrade)
+ def close_position(direction: TradeDirection, reason: str, reverse: bool = False):
+ reversal_text = "REVERSAL" if reverse else ""
+ state.ilog(e=f"CLOSING TRADE {reversal_text} {reason} {str(direction)}", curr_price=data["close"], trade=state.vars.activeTrade)
if direction == TradeDirection.SHORT:
res = state.buy(size=abs(int(state.positions)))
if isinstance(res, int) and res < 0:
@@ -959,12 +1224,14 @@ def next(data, state: StrategyState):
else:
raise Exception(f"unknow TradeDirection in close_position")
-
+
#pri uzavreni tradu zapisujeme SL history - lepsi zorbazeni v grafu
insert_SL_history()
state.vars.pending = state.vars.activeTrade.id
state.vars.activeTrade = None
- state.vars.last_exit_index = data["index"]
+ state.vars.last_exit_index = data["index"]
+ if reverse:
+ state.vars.reverse_requested = True
def eval_close_position():
curr_price = float(data['close'])
@@ -987,12 +1254,24 @@ def next(data, state: StrategyState):
#SL - execution
if curr_price > state.vars.activeTrade.stoploss_value:
- close_position(direction=TradeDirection.SHORT, reason="SL REACHED")
+
+ directive_name = 'reverse_for_SL_exit_short'
+ reverse_for_SL_exit = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
+
+ close_position(direction=TradeDirection.SHORT, reason="SL REACHED", reverse=reverse_for_SL_exit)
return
+ #REVERSE BASED ON REVERSE CONDITIONS
+ if reverse_conditions_met(TradeDirection.SHORT):
+ close_position(direction=TradeDirection.SHORT, reason="REVERSE COND MET", reverse=True)
+ return
+
#CLOSING BASED ON EXIT CONDITIONS
if exit_conditions_met(TradeDirection.SHORT):
- close_position(direction=TradeDirection.SHORT, reason="EXIT COND MET")
+ directive_name = 'reverse_for_cond_exit_short'
+ reverse_for_cond_exit = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
+
+ close_position(direction=TradeDirection.SHORT, reason="EXIT COND MET", reverse=reverse_for_cond_exit)
return
#PROFIT
@@ -1011,11 +1290,24 @@ def next(data, state: StrategyState):
#SL - execution
if curr_price < state.vars.activeTrade.stoploss_value:
- close_position(direction=TradeDirection.LONG, reason="SL REACHED")
- return
+ directive_name = 'reverse_for_SL_exit_long'
+ reverse_for_SL_exit = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
+ close_position(direction=TradeDirection.LONG, reason="SL REACHED", reverse=reverse_for_SL_exit)
+ return
+
+
+ #REVERSE BASED ON REVERSE CONDITIONS
+ if reverse_conditions_met(TradeDirection.LONG):
+ close_position(direction=TradeDirection.LONG, reason="REVERSE COND MET", reverse=True)
+ return
+
+ #EXIT CONDITIONS
if exit_conditions_met(TradeDirection.LONG):
- close_position(direction=TradeDirection.LONG, reason="EXIT CONDS MET")
+ directive_name = 'reverse_for_cond_exit_long'
+ reverse_for_cond_exit = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False))
+
+ close_position(direction=TradeDirection.LONG, reason="EXIT CONDS MET", reverse=reverse_for_cond_exit)
return
#PROFIT
@@ -1059,7 +1351,11 @@ def next(data, state: StrategyState):
if state.vars.activeTrade:
if state.vars.activeTrade.direction == TradeDirection.LONG:
state.ilog(e="odesilame LONG ORDER", trade=json.loads(json.dumps(state.vars.activeTrade, default=json_serial)))
- res = state.buy(size=state.vars.chunk)
+ if state.vars.activeTrade.size is not None:
+ size = state.vars.activeTrade.size
+ else:
+ size = state.vars.chunk
+ res = state.buy(size=size)
if isinstance(res, int) and res < 0:
raise Exception(f"error in required operation LONG {res}")
#pokud neni nastaveno SL v prescribe, tak nastavuji default dle stratvars
@@ -1073,7 +1369,11 @@ def next(data, state: StrategyState):
state.vars.pending = state.vars.activeTrade.id
elif state.vars.activeTrade.direction == TradeDirection.SHORT:
state.ilog(e="odesilame SHORT ORDER",trade=json.loads(json.dumps(state.vars.activeTrade, default=json_serial)))
- res = state.sell(size=state.vars.chunk)
+ if state.vars.activeTrade.size is not None:
+ size = state.vars.activeTrade.size
+ else:
+ size = state.vars.chunk
+ res = state.sell(size=size)
if isinstance(res, int) and res < 0:
raise Exception(f"error in required operation SHORT {res}")
#pokud neni nastaveno SL v prescribe, tak nastavuji default dle stratvars
@@ -1245,11 +1545,19 @@ def next(data, state: StrategyState):
if common_go_preconditions_check(signalname=name, options=options) is False:
return
- plugin = safe_get(options, 'plugin', None)
+ # signal_plugin = "reverzni"
+ # signal_plugin_run_once_at_index = 3
+ #pokud existuje plugin, tak pro signal search volame plugin a ignorujeme conditiony
+ signal_plugin = safe_get(options, 'plugin', None)
+ signal_plugin_run_once_at_index = safe_get(options, 'signal_plugin_run_once_at_index', 3)
#pokud je plugin True, spusti se kod
- if plugin:
- execute_signal_generator_plugin(name)
+ if signal_plugin is not None and signal_plugin_run_once_at_index==data["index"]:
+ try:
+ custom_function = eval(signal_plugin)
+ custom_function()
+ except NameError:
+ state.ilog(e="Custom plugin {signal_plugin} not found")
else:
short_enabled = safe_get(options, "short_enabled",safe_get(state.vars, "short_enabled",True))
long_enabled = safe_get(options, "long_enabled",safe_get(state.vars, "long_enabled",True))
@@ -1431,6 +1739,7 @@ def init(state: StrategyState):
state.vars.conditions.setdefault(KW.dont_go,{}).setdefault(signalname,{})[smer] = get_conditions_from_configuration(action=KW.dont_go+"_" + smer +"_if", section=section)
state.vars.conditions.setdefault(KW.go,{}).setdefault(signalname,{})[smer] = get_conditions_from_configuration(action=KW.go+"_" + smer +"_if", section=section)
state.vars.conditions.setdefault(KW.exit,{}).setdefault(signalname,{})[smer] = get_conditions_from_configuration(action=KW.exit+"_" + smer +"_if", section=section)
+ state.vars.conditions.setdefault(KW.reverse,{}).setdefault(signalname,{})[smer] = get_conditions_from_configuration(action=KW.reverse+"_" + smer +"_if", section=section)
# state.vars.work_dict_dont_do[signalname+"_"+ smer] = get_work_dict_with_directive(starts_with=signalname+"_dont_"+ smer +"_if")
# state.vars.work_dict_signal_if[signalname+"_"+ smer] = get_work_dict_with_directive(starts_with=signalname+"_"+smer+"_if")
@@ -1439,6 +1748,7 @@ def init(state: StrategyState):
section = state.vars.exit["conditions"]
for smer in TradeDirection:
state.vars.conditions.setdefault(KW.exit,{}).setdefault("common",{})[smer] = get_conditions_from_configuration(action=KW.exit+"_" + smer +"_if", section=section)
+ state.vars.conditions.setdefault(KW.reverse,{}).setdefault("common",{})[smer] = get_conditions_from_configuration(action=KW.reverse+"_" + smer +"_if", section=section)
#init klice v extData pro ulozeni historie SL
state.extData["sl_history"] = []
@@ -1450,6 +1760,8 @@ def init(state: StrategyState):
state.vars.activeTrade = None #pending/Trade
#obsahuje pripravene Trady ve frontě
state.vars.prescribedTrades = []
+ #flag pro reversal
+ state.vars.reverse_requested = False
#TODO presunout inicializaci work_dict u podminek - sice hodnoty nepujdou zmenit, ale zlepsi se performance
#pripadne udelat refresh kazdych x-iterací
diff --git a/v2realbot/__pycache__/config.cpython-310.pyc b/v2realbot/__pycache__/config.cpython-310.pyc
index 2e0906a..375d966 100644
Binary files a/v2realbot/__pycache__/config.cpython-310.pyc and b/v2realbot/__pycache__/config.cpython-310.pyc differ
diff --git a/v2realbot/common/PrescribedTradeModel.py b/v2realbot/common/PrescribedTradeModel.py
index 47fd082..3d247ca 100644
--- a/v2realbot/common/PrescribedTradeModel.py
+++ b/v2realbot/common/PrescribedTradeModel.py
@@ -23,6 +23,7 @@ class Trade(BaseModel):
generated_by: Optional[str] = None
direction: TradeDirection
entry_price: Optional[float] = None
+ size: Optional[int] = None
# stoploss_type: TradeStoplossType
stoploss_value: Optional[float] = None
profit: Optional[float] = 0
diff --git a/v2realbot/config.py b/v2realbot/config.py
index aaf8d9b..0a75a79 100644
--- a/v2realbot/config.py
+++ b/v2realbot/config.py
@@ -102,4 +102,5 @@ class KW:
dont_go: str = "dont_go"
go: str = "go"
activate: str = "activate"
- exit: str = "exit"
\ No newline at end of file
+ exit: str = "exit"
+ reverse: str = "reverse"
\ No newline at end of file
diff --git a/v2realbot/static/index.html b/v2realbot/static/index.html
index 68a45d8..bf5cc75 100644
--- a/v2realbot/static/index.html
+++ b/v2realbot/static/index.html
@@ -573,7 +573,7 @@
-
+
diff --git a/v2realbot/static/js/archivetables.js b/v2realbot/static/js/archivetables.js
index 5a4ea68..f496cf4 100644
--- a/v2realbot/static/js/archivetables.js
+++ b/v2realbot/static/js/archivetables.js
@@ -38,6 +38,7 @@ $(document).ready(function () {
rows = archiveRecords.rows('.selected').data();
var record1 = new Object()
//console.log(JSON.stringify(rows))
+
record1 = JSON.parse(rows[0].strat_json)
//record1.json = rows[0].json
//record1.id = rows[0].id;
@@ -48,6 +49,8 @@ $(document).ready(function () {
// record1.script = rows[0].script;
// record1.open_rush = rows[0].open_rush;
// record1.close_rush = rows[0].close_rush;
+ //console.log(record1.stratvars_conf)
+
record1.stratvars_conf = TOML.parse(record1.stratvars_conf);
record1.add_data_conf = TOML.parse(record1.add_data_conf);
// record1.note = rows[0].note;
@@ -65,6 +68,7 @@ $(document).ready(function () {
// record2.script = rows[1].script;
// record2.open_rush = rows[1].open_rush;
// record2.close_rush = rows[1].close_rush;
+
record2.stratvars_conf = TOML.parse(record2.stratvars_conf);
record2.add_data_conf = TOML.parse(record2.add_data_conf);
// record2.note = rows[1].note;
diff --git a/v2realbot/static/main.css b/v2realbot/static/main.css
index ebb3394..6ab2da8 100644
--- a/v2realbot/static/main.css
+++ b/v2realbot/static/main.css
@@ -356,6 +356,7 @@ pre {
display: flex;
align-items: center;
margin-left: 54px;
+ width: 323px;
height: 30px;
margin-top: 8px;
color: #2196F3;
diff --git a/v2realbot/strategy/StrategyClassicSL.py b/v2realbot/strategy/StrategyClassicSL.py
index 8f0ebd2..7be63fd 100644
--- a/v2realbot/strategy/StrategyClassicSL.py
+++ b/v2realbot/strategy/StrategyClassicSL.py
@@ -4,6 +4,7 @@ from v2realbot.utils.tlog import tlog, tlog_exception
from v2realbot.enums.enums import Mode, Order, Account, RecordType
#from alpaca.trading.models import TradeUpdate
from v2realbot.common.model import TradeUpdate
+from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus
from alpaca.trading.enums import TradeEvent, OrderStatus
from v2realbot.indicators.indicators import ema
import json
@@ -13,7 +14,7 @@ from random import randrange
from alpaca.common.exceptions import APIError
import copy
from threading import Event
-from uuid import UUID
+from uuid import UUID, uuid4
class StrategyClassicSL(Strategy):
@@ -42,13 +43,30 @@ class StrategyClassicSL(Strategy):
send_to_telegram(f"QUITTING MAX SUM LOSS REACHED {max_sum_loss_to_quit=} {self.state.profit=}")
self.se.set()
+
+ async def add_reversal(self, direction: TradeDirection, size: int, signal_name: str):
+ trade_to_add = Trade(
+ id=uuid4(),
+ last_update=datetime.fromtimestamp(self.state.time).astimezone(zoneNY),
+ status=TradeStatus.READY,
+ size=size,
+ generated_by=signal_name,
+ direction=direction,
+ entry_price=None,
+ stoploss_value = None)
+
+ self.state.vars.prescribedTrades.append(trade_to_add)
+
+ self.state.vars.reverse_requested = None
+
+ self.state.ilog(e=f"REVERZAL {direction} added to prescr.trades {signal_name=} {size=}", trade=trade_to_add)
+
async def orderUpdateBuy(self, data: TradeUpdate):
o: Order = data.order
signal_name = None
##nejak to vymyslet, aby se dal poslat cely Trade a serializoval se
self.state.ilog(e="Příchozí BUY notif", msg=o.status, trade=json.loads(json.dumps(data, default=json_serial)))
-
if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL:
#jde o uzavření short pozice - počítáme PROFIT
@@ -84,7 +102,12 @@ class StrategyClassicSL(Strategy):
#self.state.ilog(f"updatnut tradeList o profit", tradeData=json.loads(json.dumps(tradeData, default=json_serial)))
#test na maximalni profit/loss
- await self.check_max_profit_loss()
+ await self.check_max_profit_loss()
+
+ #pIF REVERSAL REQUIRED - reverse position is added to prescr.Trades with same signal name
+ #jen při celém FILLU
+ if data.event == TradeEvent.FILL and self.state.vars.reverse_requested:
+ await self.add_reversal(direction=TradeDirection.LONG, size=data.qty, signal_name=signal_name)
else:
#zjistime nazev signalu a updatneme do tradeListu - abychom meli svazano
@@ -109,6 +132,7 @@ class StrategyClassicSL(Strategy):
#davame pryc pending
self.state.vars.pending = None
+
async def orderUpdateSell(self, data: TradeUpdate):
self.state.ilog(e="Příchozí SELL notif", msg=data.order.status, trade=json.loads(json.dumps(data, default=json_serial)))
@@ -148,6 +172,10 @@ class StrategyClassicSL(Strategy):
await self.check_max_profit_loss()
+ #IF REVERSAL REQUIRED - reverse position is added to prescr.Trades with same signal name
+ if data.event == TradeEvent.FILL and self.state.vars.reverse_requested:
+ await self.add_reversal(direction=TradeDirection.SHORT, size=data.qty, signal_name=signal_name)
+
else:
#zjistime nazev signalu a updatneme do tradeListu - abychom meli svazano
for trade in self.state.vars.prescribedTrades: