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: