From e8f5805fafb2c61d2f1c786f27fc432b92dbf72b Mon Sep 17 00:00:00 2001 From: David Brazda Date: Wed, 13 Sep 2023 09:56:51 +0200 Subject: [PATCH] exitadd direktiva + bugfixy na partialfill profit --- v2realbot/ENTRY_ClassicSL_v01.py | 185 ++++++++++++++---- v2realbot/__pycache__/config.cpython-310.pyc | Bin 3253 -> 3281 bytes v2realbot/config.py | 9 +- .../enums/__pycache__/enums.cpython-310.pyc | Bin 2316 -> 2494 bytes v2realbot/enums/enums.py | 4 + v2realbot/main.py | 34 ++-- v2realbot/static/js/archivechart.js | 79 +++++++- v2realbot/strategy/StrategyClassicSL.py | 76 ++++--- 8 files changed, 296 insertions(+), 91 deletions(-) diff --git a/v2realbot/ENTRY_ClassicSL_v01.py b/v2realbot/ENTRY_ClassicSL_v01.py index bdd7621..ec92942 100644 --- a/v2realbot/ENTRY_ClassicSL_v01.py +++ b/v2realbot/ENTRY_ClassicSL_v01.py @@ -2,7 +2,7 @@ import os,sys sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from v2realbot.strategy.base import StrategyState from v2realbot.strategy.StrategyOrderLimitVykladaciNormalizedMYSELL import StrategyOrderLimitVykladaciNormalizedMYSELL -from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide, OrderType +from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, OrderSide, OrderType, Followup from v2realbot.indicators.indicators import ema, natr, roc from v2realbot.indicators.oscillators import rsi from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType @@ -1011,8 +1011,9 @@ 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) - - def reverse_conditions_met(direction: TradeDirection): + #otestuje keyword podminky (napr. reverse_if, nebo exitadd_if) + def keyword_conditions_met(direction: TradeDirection, keyword: KW): + action = str(keyword).upper() if direction == TradeDirection.LONG: smer = "long" else: @@ -1022,7 +1023,7 @@ def next(data, state: StrategyState): 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="REVERSAL CHECK COND ONLY ON CONFIRMED BAR") + state.ilog(lvl=0,e=f"{action} CHECK COND ONLY ON CONFIRMED BAR") return False #TOTO zatim u REVERSU neresime @@ -1066,37 +1067,123 @@ def next(data, state: StrategyState): # return False #bereme bud exit condition signalu, ktery activeTrade vygeneroval+ fallback na general - state.ilog(lvl=0,e=f"REVERSE CONDITIONS ENTRY {smer}", conditions=state.vars.conditions[KW.reverse]) + state.ilog(lvl=0,e=f"{action} 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] + cond_dict = state.vars.conditions[keyword][state.vars.activeTrade.generated_by][smer] result, conditions_met = evaluate_directive_conditions(cond_dict, "OR") - state.ilog(lvl=1,e=f"REVERSE CONDITIONS of {mother_signal} =OR= {result}", **conditions_met, cond_dict=cond_dict) + state.ilog(lvl=1,e=f"{action} 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(lvl=1,e=f"REVERSE CONDITIONS of {mother_signal} =AND= {result}", **conditions_met, cond_dict=cond_dict) + state.ilog(lvl=1,e=f"{action} 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] + cond_dict = state.vars.conditions[keyword]["common"][smer] result, conditions_met = evaluate_directive_conditions(cond_dict, "OR") - state.ilog(lvl=1,e=f"REVERSE CONDITIONS of COMMON =OR= {result}", **conditions_met, cond_dict=cond_dict) + state.ilog(lvl=1,e=f"{action} 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(lvl=0,e=f"REVERSE CONDITIONS of COMMON =AND= {result}", **conditions_met, cond_dict=cond_dict) + state.ilog(lvl=0,e=f"{action} CONDITIONS of COMMON =AND= {result}", **conditions_met, cond_dict=cond_dict) if result: return True + #DECOMISSIONOVANI - nahrazeno obacnou keyword_conditions_met + # 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(lvl=0,e="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(lvl=0,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(lvl=0,e=f"EXIT COND min profit PASS - POKRACUJEME") + # # else: + # # state.ilog(lvl=0,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(lvl=0,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(lvl=0,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(lvl=0,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(lvl=1,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(lvl=1,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(lvl=1,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(lvl=0,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: smer = "long" @@ -1276,9 +1363,9 @@ def next(data, state: StrategyState): insert_SL_history() state.ilog(lvl=1,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, reverse: bool = False): - reversal_text = "REVERSAL" if reverse else "" - state.ilog(lvl=1,e=f"CLOSING TRADE {reversal_text} {reason} {str(direction)}", curr_price=data["close"], trade=state.vars.activeTrade) + def close_position(direction: TradeDirection, reason: str, followup: Followup = None): + followup_text = str(followup) if followup is not None else "" + state.ilog(lvl=1,e=f"CLOSING TRADE {followup_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: @@ -1297,8 +1384,8 @@ def next(data, state: StrategyState): state.vars.pending = state.vars.activeTrade.id state.vars.activeTrade = None state.vars.last_exit_index = data["index"] - if reverse: - state.vars.reverse_requested = True + if followup is not None: + state.vars.requested_followup = followup def eval_close_position(): curr_price = float(data['close']) @@ -1324,21 +1411,33 @@ def next(data, state: StrategyState): 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) + followup_action = Followup.REVERSE if reverse_for_SL_exit else None + close_position(direction=TradeDirection.SHORT, reason="SL REACHED", followup=followup_action) return #REVERSE BASED ON REVERSE CONDITIONS - if reverse_conditions_met(TradeDirection.SHORT): - close_position(direction=TradeDirection.SHORT, reason="REVERSE COND MET", reverse=True) + if keyword_conditions_met(direction=TradeDirection.SHORT, keyword=KW.reverse): + close_position(direction=TradeDirection.SHORT, reason="REVERSE COND MET", followup=Followup.REVERSE) + return + + #EXIT ADD CONDITIONS MET (exit and add) + if keyword_conditions_met(direction=TradeDirection.SHORT, keyword=KW.exitadd): + close_position(direction=TradeDirection.SHORT, reason="EXITADD COND MET", followup=Followup.ADD) return #CLOSING BASED ON EXIT CONDITIONS if exit_conditions_met(TradeDirection.SHORT): 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) + reverse_for_cond_exit_short = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) + directive_name = 'add_for_cond_exit_short' + add_for_cond_exit_short = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) + if reverse_for_cond_exit_short: + followup_action = Followup.REVERSE + elif add_for_cond_exit_short: + followup_action = Followup.ADD + else: + followup_action = None + close_position(direction=TradeDirection.SHORT, reason="EXIT COND MET", followup=followup_action) return #PROFIT @@ -1359,22 +1458,34 @@ def next(data, state: StrategyState): 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)) - - close_position(direction=TradeDirection.LONG, reason="SL REACHED", reverse=reverse_for_SL_exit) + followup_action = Followup.REVERSE if reverse_for_SL_exit else None + close_position(direction=TradeDirection.LONG, reason="SL REACHED", followup=followup_action) return #REVERSE BASED ON REVERSE CONDITIONS - if reverse_conditions_met(TradeDirection.LONG): - close_position(direction=TradeDirection.LONG, reason="REVERSE COND MET", reverse=True) + if keyword_conditions_met(TradeDirection.LONG, KW.reverse): + close_position(direction=TradeDirection.LONG, reason="REVERSE COND MET", followup=Followup.REVERSE) + return + + #EXIT ADD CONDITIONS MET (exit and add) + if keyword_conditions_met(TradeDirection.LONG, KW.exitadd): + close_position(direction=TradeDirection.LONG, reason="EXITADD COND MET", followup=Followup.ADD) return #EXIT CONDITIONS if exit_conditions_met(TradeDirection.LONG): 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) + reverse_for_cond_exit_long = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) + directive_name = 'add_for_cond_exit_long' + add_for_cond_exit_long = get_override_for_active_trade(directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) + if reverse_for_cond_exit_long: + followup_action = Followup.REVERSE + elif add_for_cond_exit_long: + followup_action = Followup.ADD + else: + followup_action = None + close_position(direction=TradeDirection.LONG, reason="EXIT CONDS MET", followup=followup_action) return #PROFIT @@ -1520,13 +1631,13 @@ def next(data, state: StrategyState): #TESTUJEME GO SIGNAL cond_dict = state.vars.conditions[KW.go][signalname][smer] result, conditions_met = evaluate_directive_conditions(cond_dict, "OR") - state.ilog(lvl=0,e=f"EVAL GO SIGNAL {smer} =OR= {result}", **conditions_met, cond_dict=cond_dict) + state.ilog(lvl=1,e=f"EVAL GO SIGNAL {smer} =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(lvl=0,e=f"EVAL GO SIGNAL {smer} =AND= {result}", **conditions_met, cond_dict=cond_dict) + state.ilog(lvl=1,e=f"EVAL GO SIGNAL {smer} =AND= {result}", **conditions_met, cond_dict=cond_dict) if result: return True @@ -1598,11 +1709,11 @@ def next(data, state: StrategyState): state.ilog(lvl=1,e=f"PRECOND GENERAL not met {cond_met}", message=cond_met, precond_check=precond_check) return False - state.ilog(lvl=0,e=f"{signalname} ALL PRECOND MET") + state.ilog(lvl=1,e=f"{signalname} ALL PRECOND MET") return True def execute_signal_generator(name): - state.ilog(lvl=1,e=f"SIGNAL SEARCH for {name}", cond_go=state.vars.conditions[KW.go][name], cond_dontgo=state.vars.conditions[KW.dont_go][name], cond_activate=state.vars.conditions[KW.activate][name] ) + state.ilog(lvl=0,e=f"SIGNAL SEARCH for {name}", cond_go=state.vars.conditions[KW.go][name], cond_dontgo=state.vars.conditions[KW.dont_go][name], cond_activate=state.vars.conditions[KW.activate][name] ) options = safe_get(state.vars.signals, name, None) if options is None: @@ -1814,7 +1925,7 @@ def init(state: StrategyState): 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.conditions.setdefault(KW.exitadd,{}).setdefault(signalname,{})[smer] = get_conditions_from_configuration(action=KW.exitadd+"_" + 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") @@ -1823,7 +1934,7 @@ def init(state: StrategyState): 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) - + state.vars.conditions.setdefault(KW.exitadd,{}).setdefault("common",{})[smer] = get_conditions_from_configuration(action=KW.exitadd+"_" + smer +"_if", section=section) #init klice v extData pro ulozeni historie SL state.extData["sl_history"] = [] @@ -1835,7 +1946,7 @@ def init(state: StrategyState): #obsahuje pripravene Trady ve frontě state.vars.prescribedTrades = [] #flag pro reversal - state.vars.reverse_requested = False + state.vars.requested_followup = None #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 828af3e05f5d9d27a3a659f6c98b625811ce9646..56c196a27efe6aeb0e8af7d201ed110981ab7a1c 100644 GIT binary patch delta 96 zcmdlgc~O!#pO=@50SJo18B#ZF* RAS*_)t@RL7vY)kWqWbm<;l zqg(%k_YPf)?!7013-4q2e!OpHAH_$`8)h;o0$+Sa;$3IrmFd%@{jhvQl&`Y)L}g<) zrguzc)KZ^>;`RM2(I4kAKqOT}CKQ#HN=T+G(Akt94hMtb_2p>l>i87APdzHqf{CPXM6!_j2W+HBPXGV_ delta 299 zcmdld+#|%7&&$ij00a+9KcxI*oyaG{*fUYPn=OSum_buu~Ft9MPuyHUJ$xJq2kzp#5o_v5cicc0OQzQ=}6oAC!jqIZ0azKUz5F>Ok19iy( zb;(XHWUFJ8o&14KOHv7_0;KXY$RS{}Sy&i>>Xaa+D^3n%_XaAR%dW|<0#u<6#6`*= zLIr01XZEi^1ur?&WmSRVhCqz4krikXNYqc0cd{<0wFJl~MIh^nK=u}aOa#jV9V{|= bCMUm$08oU7L4=WyQHV!~Q;1E7Q;Z7$`57>j diff --git a/v2realbot/enums/enums.py b/v2realbot/enums/enums.py index 805951a..c05ff71 100644 --- a/v2realbot/enums/enums.py +++ b/v2realbot/enums/enums.py @@ -12,6 +12,10 @@ class Order: self.filled_time = filled_time self.limit_price = limit_price +class Followup(str, Enum): + REVERSE = "reverse" + ADD = "add" + class FillCondition(str, Enum): """ Execution settings: diff --git a/v2realbot/main.py b/v2realbot/main.py index f982f87..6b67a55 100644 --- a/v2realbot/main.py +++ b/v2realbot/main.py @@ -27,8 +27,10 @@ from queue import Queue, Empty from threading import Thread import asyncio from v2realbot.common.db import insert_queue, insert_conn, pool -from v2realbot.utils.utils import json_serial +from v2realbot.utils.utils import json_serial, send_to_telegram from uuid import uuid4 +from sqlite3 import OperationalError +from time import sleep #from async io import Queue, QueueEmpty # install() @@ -519,17 +521,25 @@ def insert_queue2db(): # Retrieve data from the queue data = insert_queue.get() - # Unpack the data - runner_id, loglist = data - - c = insert_conn.cursor() - insert_data = [] - for i in loglist: - row = (str(runner_id), i["time"], json.dumps(i, default=json_serial)) - insert_data.append(row) - c.executemany("INSERT INTO runner_logs VALUES (?,?,?)", insert_data) - insert_conn.commit() - # Mark the task as done in the queue + try: + # Unpack the data + runner_id, loglist = data + c = insert_conn.cursor() + insert_data = [] + for i in loglist: + row = (str(runner_id), i["time"], json.dumps(i, default=json_serial)) + insert_data.append(row) + c.executemany("INSERT INTO runner_logs VALUES (?,?,?)", insert_data) + insert_conn.commit() + # Mark the task as done in the queue + except OperationalError as e: + send_to_telegram("insert logs daemon returned" + str(e) + "RETRYING") + if "database is locked" in str(e): + # Database is locked, wait for a while and retry + insert_queue.put(data) # Put the data back into the queue for retry + sleep(1) # You can adjust the sleep duration + else: + raise # If it's another error, raise it #join cekej na dokonceni vsech for i in cs.db.runners: diff --git a/v2realbot/static/js/archivechart.js b/v2realbot/static/js/archivechart.js index ebcc5ab..f9ec99e 100644 --- a/v2realbot/static/js/archivechart.js +++ b/v2realbot/static/js/archivechart.js @@ -8,6 +8,7 @@ console.log("CHART_SHOW_TEXT archchart", CHART_SHOW_TEXT) // var volumeSeries = null var markersLine = null var avgBuyLine = null +var profitLine = null var slLine = [] //TRANSFORM object returned from REST API get_arch_run_detail //to series and markers required by lightweigth chart @@ -111,6 +112,7 @@ function transform_data(data) { //get markers - avgp line for all buys var avgp_buy_line = [] var avgp_markers = [] + var sum_profit_line = [] var markers = [] var markers_line = [] var last_timestamp = 0.1 @@ -139,9 +141,9 @@ function transform_data(data) { last_timestamp = timestamp iterator = 0.002 } - //puvodne bylo pro buy - //pro sell muzeme teoreticky taky mit buyline (pri shortu) - if ((trade.order.side == "buy") || (trade.order.side == "sell")) { + + //AVG BUY LINE - zatim docasne vypiname + if (((trade.order.side == "buy") || (trade.order.side == "sell")) && 1==2) { //avgp lajnu vytvarime jen pokud je v tradeventu prumerna cena if ((trade.pos_avg_price !== null) && (trade.pos_avg_price !== 0)) { //line pro avgp markers @@ -161,6 +163,26 @@ function transform_data(data) { } } + if ((trade.order.side == "buy") || (trade.order.side == "sell")) { + //avgp lajnu vytvarime jen pokud je v tradeventu prumerna cena + if ((trade.profit_sum !== null)) { + //line pro avgp markers + obj["time"] = timestamp; + obj["value"] = trade.profit_sum.toFixed(1); + sum_profit_line.push(obj) + + //avgp markers pro prumernou cenu aktualnich pozic + // a_markers["time"] = timestamp + // a_markers["position"] = "aboveBar" + // a_markers["color"] = "#e8c76d" + // a_markers["shape"] = "arrowDown" + // a_markers["text"] = trade.profit_sum.toFixed(1); + // //if (CHART_SHOW_TEXT) + // //a_markers["text"] = trade.position_qty + " " + parseFloat(trade.pos_avg_price).toFixed(3) + // //a_markers["text"] = CHART_SHOW_TEXT ? trade.position_qty + "/" + parseFloat(trade.pos_avg_price).toFixed(3) :trade.position_qty + // avgp_markers.push(a_markers) + } + } //buy sell markery @@ -172,7 +194,7 @@ function transform_data(data) { //marker["shape"] = (trade.order.side == "buy") ? "arrowUp" : "arrowDown" marker["shape"] = (trade.order.side == "buy") ? "arrowUp" : "arrowDown" //marker["text"] = trade.qty + "/" + trade.price - qt_optimized = (trade.qty % 1000 === 0) ? (trade.qty / 1000).toFixed(1) + 'K' : trade.qty + qt_optimized = (trade.order.qty % 1000 === 0) ? (trade.order.qty / 1000).toFixed(1) + 'K' : trade.order.qty if (CHART_SHOW_TEXT) { //včetně qty @@ -215,8 +237,10 @@ function transform_data(data) { markers_line.sort(sorter) avgp_buy_line.sort(sorter) avgp_markers.sort(sorter) + sum_profit_line.sort(sorter) - transformed["avgp_buy_line"] = avgp_buy_line + transformed["avgp_buy_line"] = avgp_buy_line + transformed["sum_profit_line"] = sum_profit_line transformed["avgp_markers"] = avgp_markers transformed["markers"] = markers transformed["markers_line"] = markers_line @@ -366,7 +390,15 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { } container1.append(indbuttonElement); + display_buy_markers(); + //TADY JSEM SKONCIL - toto nize predelat na hide button pro display bar markers + // btnElement = document.getElementById("pricelineButtons") + // var indbuttonElement = populate_indicator_buttons(false); + // if (btnElement) { + // container1.removeChild(btnElement); + // } + //container1.append(indbuttonElement); if (last_range) { chart.timeScale().setVisibleRange(last_range); @@ -604,6 +636,11 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { //displays (redraws) buy markers function display_buy_markers() { + + if (profitLine) { + chart.removeSeries(profitLine) + } + if (avgBuyLine) { chart.removeSeries(avgBuyLine) } @@ -650,10 +687,31 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { slLine.push(slLine_temp) //xx - }) + }) //} + if (transformed_data["sum_profit_line"].length > 0) { + profitLine = chart.addLineSeries({ + // title: "avgpbuyline", + color: '#e8c76d', + // color: 'transparent', + lineWidth: 1, + lastValueVisible: false + }); + + profitLine.applyOptions({ + lastValueVisible: false, + priceLineVisible: false, + priceScaleId: "own" + }); + + + profitLine.setData(transformed_data["sum_profit_line"]); + //profitLine.setMarkers(transformed_data["sum_profit_line_markers"]); + } + + if (transformed_data["avgp_buy_line"].length > 0) { avgBuyLine = chart.addLineSeries({ // title: "avgpbuyline", @@ -714,6 +772,7 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { toolTip.innerHTML = ""; var data = param.seriesData.get(markersLine); var data2 = param.seriesData.get(avgBuyLine); + var profitdata = param.seriesData.get(profitLine); if ((data !== undefined) || (data2 !== undefined)) { //param.seriesData.forEach((value, key) => { //console.log("key",key) @@ -733,9 +792,11 @@ function chart_archived_run(archRecord, data, oneMinuteBars) { buy_price = parseFloat(data2.value).toFixed(3) } - toolTip.innerHTML += `
POS:${tradeDetails.get(param.time).position_qty}/${buy_price}
T:${tradeDetails.get(param.time).qty}/${data.value}
`; - - //inspirace + toolTip.innerHTML += `
POS:${tradeDetails.get(param.time).position_qty}/${buy_price}
T:${tradeDetails.get(param.time).order.qty}/${data.value}
`; + if (profitdata !== undefined) { + toolTip.innerHTML += `
P:${parseFloat(profitdata.value).toFixed(1)}
` + } + //inspirace // toolTip.innerHTML = `
Apple Inc.
// ${Math.round(100 * price) / 100} //
diff --git a/v2realbot/strategy/StrategyClassicSL.py b/v2realbot/strategy/StrategyClassicSL.py index 0668aaa..727210d 100644 --- a/v2realbot/strategy/StrategyClassicSL.py +++ b/v2realbot/strategy/StrategyClassicSL.py @@ -1,7 +1,7 @@ from v2realbot.strategy.base import Strategy from v2realbot.utils.utils import parse_alpaca_timestamp, ltp, AttributeDict,trunc,price2dec, zoneNY, print, json_serial, safe_get, get_tick, send_to_telegram from v2realbot.utils.tlog import tlog, tlog_exception -from v2realbot.enums.enums import Mode, Order, Account, RecordType +from v2realbot.enums.enums import Mode, Order, Account, RecordType, Followup #from alpaca.trading.models import TradeUpdate from v2realbot.common.model import TradeUpdate from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus @@ -25,7 +25,7 @@ class StrategyClassicSL(Strategy): super().__init__(name, symbol, next, init, account, mode, stratvars, open_rush, close_rush, pe, se, runner_id, ilog_save) #zkontroluje zda aktualni profit/loss - nedosahnul limit a pokud ano tak vypne strategii - async def check_max_profit_loss(self): + async def stop_when_max_profit_loss(self): self.state.ilog(e="CHECK MAX PROFIT") max_sum_profit_to_quit = safe_get(self.state.vars, "max_sum_profit_to_quit", None) max_sum_loss_to_quit = safe_get(self.state.vars, "max_sum_loss_to_quit", None) @@ -36,15 +36,19 @@ class StrategyClassicSL(Strategy): self.state.vars.pending = "max_sum_profit_to_quit" send_to_telegram(f"QUITTING MAX SUM PROFIT REACHED {max_sum_profit_to_quit=} {self.state.profit=}") self.se.set() + return True if max_sum_loss_to_quit is not None: if float(self.state.profit) < 0 and float(self.state.profit) <= float(max_sum_loss_to_quit): self.state.ilog(e=f"QUITTING MAX SUM LOSS REACHED {max_sum_loss_to_quit=} {self.state.profit=}") self.state.vars.pending = "max_sum_loss_to_quit" send_to_telegram(f"QUITTING MAX SUM LOSS REACHED {max_sum_loss_to_quit=} {self.state.profit=}") self.se.set() + return True + + return False - async def add_reversal(self, direction: TradeDirection, size: int, signal_name: str): + async def add_followup(self, direction: TradeDirection, size: int, signal_name: str): trade_to_add = Trade( id=uuid4(), last_update=datetime.fromtimestamp(self.state.time).astimezone(zoneNY), @@ -57,9 +61,9 @@ class StrategyClassicSL(Strategy): self.state.vars.prescribedTrades.append(trade_to_add) - self.state.vars.reverse_requested = None + self.state.vars.requested_followup = None - self.state.ilog(e=f"REVERZAL {direction} added to prescr.trades {signal_name=} {size=}", trade=trade_to_add) + self.state.ilog(e=f"FOLLOWUP {direction} added to prescr.trades {signal_name=} {size=}", trade=trade_to_add) async def orderUpdateBuy(self, data: TradeUpdate): o: Order = data.order @@ -92,23 +96,27 @@ class StrategyClassicSL(Strategy): trade.profit_sum = self.state.profit signal_name = trade.generated_by + if data.event == TradeEvent.FILL: #zapsat update profitu do tradeList - for tradeData in self.state.tradeList: - if tradeData.execution_id == data.execution_id: - #pridat jako attribut, aby proslo i na LIVE a PAPPER, kde se bere TradeUpdate z Alpaca - setattr(tradeData, "profit", trade_profit) - setattr(tradeData, "profit_sum", self.state.profit) - setattr(tradeData, "signal_name", signal_name) - #self.state.ilog(f"updatnut tradeList o profit", tradeData=json.loads(json.dumps(tradeData, default=json_serial))) + for tradeData in self.state.tradeList: + if tradeData.execution_id == data.execution_id: + #pridat jako attribut, aby proslo i na LIVE a PAPPER, kde se bere TradeUpdate z Alpaca + setattr(tradeData, "profit", trade_profit) + setattr(tradeData, "profit_sum", self.state.profit) + setattr(tradeData, "signal_name", signal_name) + #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() - - #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=o.qty, signal_name=signal_name) + #test na maximalni profit/loss, pokud vypiname pak uz nedelame pripdany reverzal + if await self.stop_when_max_profit_loss() is False: + #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.requested_followup is not None: + if self.state.vars.requested_followup == Followup.REVERSE: + await self.add_followup(direction=TradeDirection.LONG, size=o.qty, signal_name=signal_name) + elif self.state.vars.requested_followup == Followup.ADD: + #zatim stejna SIZE + await self.add_followup(direction=TradeDirection.SHORT, size=o.qty, signal_name=signal_name) else: #zjistime nazev signalu a updatneme do tradeListu - abychom meli svazano for trade in self.state.vars.prescribedTrades: @@ -161,20 +169,25 @@ class StrategyClassicSL(Strategy): trade.profit_sum = self.state.profit signal_name = trade.generated_by - #zapsat update profitu do tradeList - for tradeData in self.state.tradeList: - if tradeData.execution_id == data.execution_id: - #pridat jako attribut, aby proslo i na LIVE a PAPPER, kde se bere TradeUpdate z Alpaca - setattr(tradeData, "profit", trade_profit) - setattr(tradeData, "profit_sum", self.state.profit) - setattr(tradeData, "signal_name", signal_name) - #self.state.ilog(f"updatnut tradeList o profi {str(tradeData)}") + if data.event == TradeEvent.FILL: + #zapsat update profitu do tradeList + for tradeData in self.state.tradeList: + if tradeData.execution_id == data.execution_id: + #pridat jako attribut, aby proslo i na LIVE a PAPPER, kde se bere TradeUpdate z Alpaca + setattr(tradeData, "profit", trade_profit) + setattr(tradeData, "profit_sum", self.state.profit) + setattr(tradeData, "signal_name", signal_name) + #self.state.ilog(f"updatnut tradeList o profi {str(tradeData)}") - await self.check_max_profit_loss() + if await self.stop_when_max_profit_loss() is False: - #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.order.qty, signal_name=signal_name) + #IF REVERSAL REQUIRED - reverse position is added to prescr.Trades with same signal name + if data.event == TradeEvent.FILL and self.state.vars.requested_followup is not None: + if self.state.vars.requested_followup == Followup.REVERSE: + await self.add_followup(direction=TradeDirection.SHORT, size=data.order.qty, signal_name=signal_name) + elif self.state.vars.requested_followup == Followup.ADD: + #zatim stejna SIZE + await self.add_followup(direction=TradeDirection.LONG, size=data.order.qty, signal_name=signal_name) else: #zjistime nazev signalu a updatneme do tradeListu - abychom meli svazano @@ -206,6 +219,7 @@ class StrategyClassicSL(Strategy): #pri chybe api nechavame puvodni hodnoty if a != -1: self.state.avgp, self.state.positions = a,p + else: self.state.ilog(e=f"Chyba pri dotažení self.interface.pos() {a}") #ic(self.state.avgp, self.state.positions) #this parent method is called by strategy just once before waiting for first data