From c05a1d0936c99ab5820117494b19979dd729d830 Mon Sep 17 00:00:00 2001 From: David Brazda Date: Mon, 18 Sep 2023 15:25:05 +0200 Subject: [PATCH] BE:slreversonly_if , GUI: conf tooltip on ind.btns --- v2realbot/ENTRY_ClassicSL_v01.py | 81 ++++++++++++++---- v2realbot/__pycache__/config.cpython-310.pyc | Bin 3311 -> 3345 bytes v2realbot/config.py | 2 +- v2realbot/static/js/archivechart.js | 17 +++- v2realbot/static/js/utils.js | 1 + .../utils/__pycache__/utils.cpython-310.pyc | Bin 14144 -> 14041 bytes 6 files changed, 81 insertions(+), 20 deletions(-) diff --git a/v2realbot/ENTRY_ClassicSL_v01.py b/v2realbot/ENTRY_ClassicSL_v01.py index d5edef1..4ce1728 100644 --- a/v2realbot/ENTRY_ClassicSL_v01.py +++ b/v2realbot/ENTRY_ClassicSL_v01.py @@ -1007,9 +1007,18 @@ def next(data, state: StrategyState): val = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(options, directive_name, 0.01)) return val - def get_profit_target_price(): + def get_profit_target_price(direction: TradeDirection): + if direction == TradeDirection.LONG: + smer = "long" + else: + smer = "short" + directive_name = "profit" - def_profit = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 0.50)) + def_profit_both_directions = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 0.50)) + + #profit pro dany smer + directive_name = 'profit_'+str(smer) + def_profit = get_override_for_active_trade(directive_name=directive_name, default_value=def_profit_both_directions) normalized_def_profit = normalize_tick(float(def_profit)) @@ -1017,9 +1026,18 @@ def next(data, state: StrategyState): return price2dec(float(state.avgp)+normalized_def_profit,3) if int(state.positions) > 0 else price2dec(float(state.avgp)-normalized_def_profit,3) - def get_max_profit_price(): + def get_max_profit_price(direction: TradeDirection): + if direction == TradeDirection.LONG: + smer = "long" + else: + smer = "short" + directive_name = "max_profit" - max_profit = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 0.35)) + max_profit_both_directions = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, 0.35)) + + #max profit pro dany smer, s fallbackem na bez smeru + directive_name = 'max_profit_'+str(smer) + max_profit = get_override_for_active_trade(directive_name=directive_name, default_value=max_profit_both_directions) normalized_max_profit = normalize_tick(float(max_profit)) @@ -1028,19 +1046,20 @@ 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) #otestuje keyword podminky (napr. reverse_if, nebo exitadd_if) - def keyword_conditions_met(direction: TradeDirection, keyword: KW): + def keyword_conditions_met(direction: TradeDirection, keyword: KW, skip_conf_validation: bool = False): action = str(keyword).upper() 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 skip_conf_validation is False: + 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(lvl=0,e=f"{action} CHECK COND ONLY ON CONFIRMED BAR") - return False + if exit_cond_only_on_confirmed and data['confirmed'] == 0: + state.ilog(lvl=0,e=f"{action} 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 @@ -1429,16 +1448,19 @@ def next(data, state: StrategyState): if int(state.positions) != 0 and float(state.avgp)>0 and state.vars.pending is None: - #pevny target - presunout toto do INIT a pak jen pristupovat - goal_price = get_profit_target_price() - max_price = get_max_profit_price() - state.ilog(lvl=1,e=f"Goal price {goal_price} max price {max_price}") + #close position handling #TBD pridat OPTIMALIZACI POZICE - EXIT 1/2 #mame short pozice - (IDEA: rozlisovat na zaklade aktivniho tradu - umozni mi spoustet i pri soucasne long pozicemi) if int(state.positions) < 0: + #get TARGET PRICE pro dany smer a signal + goal_price = get_profit_target_price(TradeDirection.SHORT) + max_price = get_max_profit_price(TradeDirection.SHORT) + state.ilog(lvl=1,e=f"Goal price {str(TradeDirection.SHORT)} {goal_price} max price {max_price}") + + #EOD EXIT - TBD #FORCED EXIT PRI KONCI DNE @@ -1446,8 +1468,14 @@ def next(data, state: StrategyState): if curr_price > state.vars.activeTrade.stoploss_value: 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)) - followup_action = Followup.REVERSE if reverse_for_SL_exit else None + reverse_for_SL_exit = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, "no")) + + if reverse_for_SL_exit == "always": + followup_action = Followup.REVERSE + elif reverse_for_SL_exit == "cond": + followup_action = Followup.REVERSE if keyword_conditions_met(direction=TradeDirection.SHORT, keyword=KW.slreverseonly, skip_conf_validation=True) else None + else: + followup_action = None close_position(direction=TradeDirection.SHORT, reason="SL REACHED", followup=followup_action) return @@ -1488,13 +1516,28 @@ def next(data, state: StrategyState): return #mame long elif int(state.positions) > 0: + + #get TARGET PRICE pro dany smer a signal + goal_price = get_profit_target_price(TradeDirection.LONG) + max_price = get_max_profit_price(TradeDirection.LONG) + state.ilog(lvl=1,e=f"Goal price {str(TradeDirection.LONG)} {goal_price} max price {max_price}") + #EOD EXIT - TBD #SL - execution if curr_price < state.vars.activeTrade.stoploss_value: 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)) - followup_action = Followup.REVERSE if reverse_for_SL_exit else None + reverse_for_SL_exit = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, "no")) + + state.ilog(lvl=1, e=f"reverse_for_SL_exit {reverse_for_SL_exit}") + + if reverse_for_SL_exit == "always": + followup_action = Followup.REVERSE + elif reverse_for_SL_exit == "cond": + followup_action = Followup.REVERSE if keyword_conditions_met(direction=TradeDirection.LONG, keyword=KW.slreverseonly, skip_conf_validation=True) else None + else: + followup_action = None + close_position(direction=TradeDirection.LONG, reason="SL REACHED", followup=followup_action) return @@ -1965,6 +2008,7 @@ def init(state: StrategyState): 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.conditions.setdefault(KW.exitadd,{}).setdefault(signalname,{})[smer] = get_conditions_from_configuration(action=KW.exitadd+"_" + smer +"_if", section=section) + state.vars.conditions.setdefault(KW.slreverseonly,{}).setdefault(signalname,{})[smer] = get_conditions_from_configuration(action=KW.slreverseonly+"_" + 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") @@ -1975,6 +2019,7 @@ def init(state: StrategyState): state.vars.conditions.setdefault(KW.dont_exit,{}).setdefault("common",{})[smer] = get_conditions_from_configuration(action=KW.dont_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) state.vars.conditions.setdefault(KW.exitadd,{}).setdefault("common",{})[smer] = get_conditions_from_configuration(action=KW.exitadd+"_" + smer +"_if", section=section) + state.vars.conditions.setdefault(KW.slreverseonly,{}).setdefault("common",{})[smer] = get_conditions_from_configuration(action=KW.slreverseonly+"_" + smer +"_if", section=section) #init klice v extData pro ulozeni historie SL state.extData["sl_history"] = [] diff --git a/v2realbot/__pycache__/config.cpython-310.pyc b/v2realbot/__pycache__/config.cpython-310.pyc index 754a4ef24b8220071dbb7ed42894eaaafda2c9a7..4ca0727458e903bcd219e3c0b8c6d6e85c04be7a 100644 GIT binary patch delta 1149 zcmZXSOHUI~6vsPI+A?jSyvm~$O#rnfhEiTah_Riyg|X9_VP?wXY_32j3X+!GDh3xC zH*VAgxt~He#<2AxxYHH-1zfrC3wTcpv|uOqmow+pGxz-e3?Gj~tI??J6Tf&Str5vlRS+E|#sY5x&!q+a z4AIQ3TZ8Bu61XQ$%`Xu0bxCaKgB;p#PIm{sc8_3+66wcor0;e72 zt;5|i^jn4HWwNe`0nD}4ylQXGr*+_ZFf|DM*+Oo1MV-#*mI}&(Yn9d?y1?JpV^$$; zuCBmxdfF<%l%vefrPqLe3o25D$3icpyvu4VAo8GJ)6He-WY-AQ$r{mV-ZqQCk2`Myj2Na}%<38tmAaZzpt-C= zsAX#kfgUWgONK#gD(Vc^0eQ8giB%R&l|VA*(!8eYR51;Qx^`A24z-EMu!#!1N1j-M zL`#mUxteLvTxpZ)T2XT^H8=}8MD%M!Ig`f*pjR4G7{W07< z@L%%v8Hi_HW)~V9zT$6DB%chAP6h^X+7R(+IzXE-tK(v%$2!$S7by2o{Eg-FGf7iIU7+U8yEj`9>Sgswep{c I_=m{$KLe`~J^%m! delta 1047 zcmZXS&rcIk5XZZ;^vCX23KWpG6bvHvKvGH&G=|XbzLvGi?sj)uXfJC^8&f2-c~ug; z7-Ql^FXs6Nc=coqSN{YqMo-Vbz>^2>#y4A_fZe=LW@o-LJAE_nW51`vop6}~VehaGzIl}mD5g!~*zBt}^_ z9s`U6q70urI0kl%jC-y42s6{{^h5^UBXa#GGK5FSEAJ1XF9m&^Jocrf8D?rJAD#f$ zBwz}#2pDKxD-ON{!#X=XA4AY{gTslF@4j7bjs~5qp*TfUrGjH2$wapM;&+U!flNw@ z{s-I~dE;N&gc>whY#k$vJ2(}ToWF({ z=y0=s)NazKQ7jbDj)JN+y;91{W_2yCQlbV@Q#6__=2o}lm5gf0HR-->ly@H4l)Q6e zMlr3IwrYj+iczjDThi*ibcK@BfW$ROB)I3ZJgE?Q9o%@SrNry^l!ewZfN4MuAOU0m z0^|V&z~Chasw^mTIH*gKnKvLhB1OQ15n|ZQR3t7Nb!` z)eDZ5tsqB56{I?OQ{SfKBy@+Pgoff9bQA<;S<{f|FokAYlrNPPHl1xe<}BrV2RGet3(XZzIe a`CQYl=2g({#dpp_P}7~`f8!^g#l}C?bqk&V diff --git a/v2realbot/config.py b/v2realbot/config.py index 6b4439c..fb4b7fc 100644 --- a/v2realbot/config.py +++ b/v2realbot/config.py @@ -114,4 +114,4 @@ class KW: exitadd: str = "exitadd" reverse: str = "reverse" #exitaddsize: str = "exitaddsize" - + slreverseonly: str = "slreverseonly" diff --git a/v2realbot/static/js/archivechart.js b/v2realbot/static/js/archivechart.js index 3ec2a80..5cbee3e 100644 --- a/v2realbot/static/js/archivechart.js +++ b/v2realbot/static/js/archivechart.js @@ -311,6 +311,8 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { var transformed_data = transform_data(data) + data["archRecord"] = archRecord + //initialize resolutions var native_resolution = data.bars.resolution[0]+"s" //console.log("native", native_resolution) @@ -419,6 +421,10 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { //vraci se pole indicatoru, kazdy se svoji casovou osou (time) - nyni standard indikatory a cbar indikatory var indicatorList = data.indicators + //ze stratvars daneho runnera si dotahneme nastaveni indikatoru - pro zobrazeni v tooltipu + var stratvars_toml = TOML.parse(data.archRecord.stratvars_toml) + //console.log(stratvars_toml.stratvars.indicators) + indicatorList.forEach((indicators, index, array) => { //var indicators = data.indicators @@ -426,8 +432,17 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { if (Object.keys(indicators).length > 1) { for (const [key, value] of Object.entries(indicators)) { if (key !== "time") { + //get cnf of indicator to display in the button tooltip + var cnf = null + try { + cnf = JSON.stringify(stratvars_toml.stratvars.indicators[key], null, 2) + } + catch (e) { + //nic + } + //initialize indicator and store reference to array - var obj = {name: key, series: null} + var obj = {name: key, series: null, cnf:cnf} //start //console.log(key) diff --git a/v2realbot/static/js/utils.js b/v2realbot/static/js/utils.js index 76458f1..baac0e2 100644 --- a/v2realbot/static/js/utils.js +++ b/v2realbot/static/js/utils.js @@ -329,6 +329,7 @@ function populate_indicator_buttons(def) { var itemEl = document.createElement('button'); itemEl.innerText = item.name; itemEl.id = "IND"+index; + itemEl.title = item.cnf itemEl.style.color = item.series.options().color; itemEl.classList.add('switcher-item'); if (def) { diff --git a/v2realbot/utils/__pycache__/utils.cpython-310.pyc b/v2realbot/utils/__pycache__/utils.cpython-310.pyc index 5e366a2b3854808bf08dc63353f1f12a1c5bb803..8b244b573a6c2f61a85f329ef35ba7dd35dca8d9 100644 GIT binary patch delta 4059 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 QJl(S}8{f$Cg@uj(1H3Z#O8@`>