diff --git a/v2realbot/common/model.py b/v2realbot/common/model.py index 06e2590..a6bda55 100644 --- a/v2realbot/common/model.py +++ b/v2realbot/common/model.py @@ -414,6 +414,8 @@ class SLHistory(BaseModel): id: Optional[UUID] = None time: datetime sl_val: float + direction: TradeDirection + account: Account #Contains archive of running strategies (runner) - detail data class RunArchiveDetail(BaseModel): diff --git a/v2realbot/static/js/archivechart.js b/v2realbot/static/js/archivechart.js index f48c595..454716f 100644 --- a/v2realbot/static/js/archivechart.js +++ b/v2realbot/static/js/archivechart.js @@ -11,6 +11,44 @@ var markersLine = null var avgBuyLine = null var profitLine = null var slLine = [] + +//create function which for each ACCOUNT1, ACCOUNT2 or ACCOUNT3 returns color for buy and color for sell - which can be strings representing color +//HELPERS FUNCTION - will go to utils +/** + * Returns an object containing the colors for buy and sell for the specified account. + * + * Parameters: + * account (string): The account for which to retrieve the colors (ACCOUNT1, ACCOUNT2, or ACCOUNT3). + * + * Returns: + * object: An object with 'buy' and 'sell' properties containing the corresponding color strings. + * + * Account 1: +#FF6B6B, #FF9999 +Account 2: +#4ECDC4, #83E8E1 +Account 3: +#FFD93D, #FFE787 +Account 4: +#6C5CE7, #A29BFE +Another option for colors: + +#1F77B4 (Entry) and #AEC7E8 (Exit) +#FF7F0E (Entry) and #FFBB78 (Exit) +#2CA02C (Entry) and #98DF8A (Exit) +#D62728 (Entry) and #FF9896 (Exit) + */ +function getAccountColors(account) { + const accountColors = { + ACCOUNT1: { accid: 'A1', buy: '#FF7F0E', sell: '#FFBB78' }, + ACCOUNT2: { accid: 'A2',buy: '#1F77B4', sell: '#AEC7E8' }, + ACCOUNT3: { accid: 'A3',buy: '#2CA02C', sell: '#98DF8A' }, + ACCOUNT4: { accid: 'A4',buy: '#D62728', sell: '#FF9896' }, + ACCOUNT5: { accid: 'A5',buy: 'purple', sell: 'orange' } + }; + return accountColors[account] || { buy: '#37cade', sell: 'red' }; +} + //TRANSFORM object returned from REST API get_arch_run_detail //to series and markers required by lightweigth chart //input array object bars = { high: [1,2,3], time: [1,2,3], close: [2,2,2]...} @@ -34,6 +72,11 @@ function transform_data(data) { //cas of first record, nekdy jsou stejny - musim pridat setinku prev_cas = 0 if ((data.ext_data !== null) && (data.ext_data.sl_history)) { + ///sort sl_history according to order id string - i need all same order id together + data.ext_data.sl_history.sort(function (a, b) { + return a.id.localeCompare(b.id); + }); + data.ext_data.sl_history.forEach((histRecord, index, array) => { //console.log("plnime") @@ -48,6 +91,7 @@ function transform_data(data) { //init nova sada sl_line_sada = [] sl_line_markers_sada = [] + sline_color = "#f5aa42" } prev_id = histRecord.id @@ -65,12 +109,21 @@ function transform_data(data) { sline = {} sline["time"] = cas sline["value"] = histRecord.sl_val + if (histRecord.account) { + const accColors = getAccountColors(histRecord.account) + sline_color = histRecord.direction == "long" ? accColors.buy : accColors.sell //idealne + sline["color"] = sline_color + } + sl_line_sada.push(sline) + //ZDE JSEM SKONCIL + //COLOR SE NASTAVUJE V SERIES OPTIONS POZDEJI - nejak vymyslet + sline_markers = {} sline_markers["time"] = cas sline_markers["position"] = "inBar" - sline_markers["color"] = "#f5aa42" + sline_markers["color"] = sline_color //sline_markers["shape"] = "circle" //console.log("SHOW_SL_DIGITS",SHOW_SL_DIGITS) sline_markers["text"] = SHOW_SL_DIGITS ? histRecord.sl_val.toFixed(3) : "" @@ -239,31 +292,33 @@ function transform_data(data) { // //a_markers["text"] = CHART_SHOW_TEXT ? trade.position_qty + "/" + parseFloat(trade.pos_avg_price).toFixed(3) :trade.position_qty // avgp_markers.push(a_markers) } - } - + } + + const { accid: accountId,buy: buyColor, sell: sellColor } = getAccountColors(trade.account); //buy sell markery marker = {} marker["time"] = timestamp; // marker["position"] = (trade.order.side == "buy") ? "belowBar" : "aboveBar" marker["position"] = (trade.order.side == "buy") ? "aboveBar" : "aboveBar" - marker["color"] = (trade.order.side == "buy") ? "#37cade" : "red" + marker["color"] = (trade.order.side == "buy") ? buyColor : sellColor //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.order.qty % 1000 === 0) ? (trade.order.qty / 1000).toFixed(1) + 'K' : trade.order.qty + marker["text"] = accountId + " " //account shortcut if (CHART_SHOW_TEXT) { //včetně qty //marker["text"] = qt_optimized + "@" + trade.price //bez qty - marker["text"] = trade.price + marker["text"] += trade.price closed_trade_marker_and_profit = (trade.profit) ? "c" + trade.profit.toFixed(1) + "/" + trade.profit_sum.toFixed(1) : "c" marker["text"] += (trade.position_qty == 0) ? closed_trade_marker_and_profit : "" } else { closed_trade_marker_and_profit = (trade.profit) ? "c" + trade.profit.toFixed(1) + "/" + trade.profit_sum.toFixed(1) : "c" - marker["text"] = (trade.position_qty == 0) ? closed_trade_marker_and_profit : trade.price.toFixed(3) + marker["text"] += (trade.position_qty == 0) ? closed_trade_marker_and_profit : trade.price.toFixed(3) } markers.push(marker) @@ -844,7 +899,7 @@ function display_buy_markers(data) { //console.log("uvnitr") slLine_temp = chart.addLineSeries({ // title: "avgpbuyline", - color: '#e4c76d', + color: slRecord[0]["color"] ? slRecord[0]["color"] : '#e4c76d', // color: 'transparent', lineWidth: 1, lastValueVisible: false diff --git a/v2realbot/strategy/StrategyClassicSL.py b/v2realbot/strategy/StrategyClassicSL.py index 477e658..1455911 100644 --- a/v2realbot/strategy/StrategyClassicSL.py +++ b/v2realbot/strategy/StrategyClassicSL.py @@ -344,7 +344,7 @@ class StrategyClassicSL(Strategy): self.state.vars["transferables"]["martingale"]["cont_loss_series_cnt"] = 0 if rel_profit > 0 else self.state.vars["transferables"]["martingale"]["cont_loss_series_cnt"]+1 self.state.ilog(lvl=1, e=f"update cont_loss_series_cnt na {self.state.vars['transferables']['martingale']['cont_loss_series_cnt']}") - self.state.ilog(e=f"SELL notif {account}- LONG PROFIT {partial_exit=} {partial_last=}:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)} rel:{float(rel_profit)} rel_cum:{round(rel_profit_cum_calculated,7)}", msg=str(data.event), rel_profit_cum = str(self.state.rel_profit_cum), sold_amount=sold_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id)) + self.state.ilog(e=f"SELL notif {account.name}- LONG PROFIT {partial_exit=} {partial_last=}:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)} rel:{float(rel_profit)} rel_cum:{round(rel_profit_cum_calculated,7)}", msg=str(data.event), rel_profit_cum = str(self.state.rel_profit_cum), sold_amount=sold_amount, avg_costs=avg_costs, trade_qty=data.qty, trade_price=data.price, orderid=str(data.order.id)) #zapsat profit do prescr.trades for trade in self.state.vars.prescribedTrades: @@ -431,7 +431,7 @@ class StrategyClassicSL(Strategy): if data.event == TradeEvent.FILL or data.event == TradeEvent.CANCELED: print("Příchozí SELL notifikace - complete FILL nebo CANCEL", data.event) self.state.account_variables[account.name].pending = None - a,p = self.interface[account.name].pos() + a,p = self.interface[account.name].pos() #TBD maybe optimize for speed #pri chybe api nechavame puvodni hodnoty if a != -1: self.state.account_variables[account.name].avgp, self.state.account_variables[account.name].positions = a,p diff --git a/v2realbot/strategyblocks/activetrade/close/close_position.py b/v2realbot/strategyblocks/activetrade/close/close_position.py index 666a43e..5bcad79 100644 --- a/v2realbot/strategyblocks/activetrade/close/close_position.py +++ b/v2realbot/strategyblocks/activetrade/close/close_position.py @@ -26,20 +26,20 @@ def close_position(state: StrategyState, activeTrade: Trade, data, direction: Tr positions = state.account_variables[activeTrade.account.name].positions state.ilog(lvl=1,e=f"CLOSING TRADE {followup_text} {reason} {str(direction)}", curr_price=data["close"], trade=activeTrade) if direction == TradeDirection.SHORT: - res = state.buy(size=abs(int(positions))) + res = state.buy(account=activeTrade.account, size=abs(int(positions))) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation {reason} {res}") elif direction == TradeDirection.LONG: - res = state.sell(size=positions) + res = state.sell(account=activeTrade.account, size=positions) if isinstance(res, int) and res < 0: - raise Exception(f"error in required operation STOPLOSS SELL {res}") + raise Exception(f"error in required operation STOPLOSS SELL {res}") #TBD error handling else: raise Exception(f"unknow TradeDirection in close_position") #pri uzavreni tradu zapisujeme SL history - lepsi zorbazeni v grafu - insert_SL_history(state) + insert_SL_history(state, activeTrade) state.account_variables[activeTrade.account.name].pending = activeTrade.id state.account_variables[activeTrade.account.name].activeTrade = None #state.account_variables[activeTrade.account.name].last_exit_index = data["index"] @@ -56,19 +56,19 @@ def close_position_partial(state, activeTrade: Trade,data, direction: TradeDirec size_abs = abs(int(int(positions)*size)) state.ilog(lvl=1,e=f"CLOSING TRADE PART: {size_abs} {size} {reason} {str(direction)}", curr_price=data["close"], trade=activeTrade) if direction == TradeDirection.SHORT: - res = state.buy(size=size_abs) + res = state.buy(account=activeTrade.account, size=size_abs) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation STOPLOSS PARTIAL BUY {reason} {res}") elif direction == TradeDirection.LONG: - res = state.sell(size=size_abs) + res = state.sell(account=activeTrade.account, size=size_abs) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation STOPLOSS PARTIAL SELL {res}") else: raise Exception(f"unknow TradeDirection in close_position") #pri uzavreni tradu zapisujeme SL history - lepsi zorbazeni v grafu - insert_SL_history(state) + insert_SL_history(state, activeTrade) state.account_variables[activeTrade.account.name].pending = activeTrade.id state.account_variables[activeTrade.account.name].activeTrade = None state.account_variables[activeTrade.account.name].dont_exit_already_activated = False diff --git a/v2realbot/strategyblocks/activetrade/close/evaluate_close.py b/v2realbot/strategyblocks/activetrade/close/evaluate_close.py index 5543ed3..788b18b 100644 --- a/v2realbot/strategyblocks/activetrade/close/evaluate_close.py +++ b/v2realbot/strategyblocks/activetrade/close/evaluate_close.py @@ -40,9 +40,9 @@ def eval_close_position(state: StrategyState, accountsWithActiveTrade, data): if activeTrade.goal_price is not None: goal_price = activeTrade.goal_price else: - goal_price = get_profit_target_price(state, data, TradeDirection.SHORT, activeTrade) + goal_price = get_profit_target_price(state, data, activeTrade, TradeDirection.SHORT) - max_price = get_max_profit_price(state, data, TradeDirection.SHORT, activeTrade) + max_price = get_max_profit_price(state, activeTrade, data, TradeDirection.SHORT) state.ilog(lvl=1,e=f"Def Goal price {str(TradeDirection.SHORT)} {goal_price} max price {max_price}") #SL OPTIMALIZATION - PARTIAL EXIT @@ -63,7 +63,7 @@ def eval_close_position(state: StrategyState, accountsWithActiveTrade, data): if reverse_for_SL_exit == "always": followup_action = Followup.REVERSE elif reverse_for_SL_exit == "cond": - followup_action = Followup.REVERSE if keyword_conditions_met(state, data=data, activeTrade=activeTrade.generated_by, direction=TradeDirection.SHORT, keyword=KW.slreverseonly, skip_conf_validation=True) else None + followup_action = Followup.REVERSE if keyword_conditions_met(state, data=data, activeTrade=activeTrade, direction=TradeDirection.SHORT, keyword=KW.slreverseonly, skip_conf_validation=True) else None else: followup_action = None close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT, reason="SL REACHED", followup=followup_action) @@ -71,28 +71,28 @@ def eval_close_position(state: StrategyState, accountsWithActiveTrade, data): #REVERSE BASED ON REVERSE CONDITIONS - if keyword_conditions_met(state, data, activeTrade=activeTrade.generated_by, direction=TradeDirection.SHORT, keyword=KW.reverse): + if keyword_conditions_met(state, data, activeTrade=activeTrade, direction=TradeDirection.SHORT, keyword=KW.reverse): close_position(state=state, activeTrade=activeTrade,data=data, direction=TradeDirection.SHORT, reason="REVERSE COND MET", followup=Followup.REVERSE) return #EXIT ADD CONDITIONS MET (exit and add) - if keyword_conditions_met(state, data, activeTrade=activeTrade.generated_by, direction=TradeDirection.SHORT, keyword=KW.exitadd): + if keyword_conditions_met(state, data, activeTrade=activeTrade, direction=TradeDirection.SHORT, keyword=KW.exitadd): close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT, reason="EXITADD COND MET", followup=Followup.ADD) return #CLOSING BASED ON EXIT CONDITIONS - if exit_conditions_met(state, data, TradeDirection.SHORT): + if exit_conditions_met(state, activeTrade, data, TradeDirection.SHORT): directive_name = 'reverse_for_cond_exit_short' - reverse_for_cond_exit_short = get_signal_section_directive(state=state, signal_name=activeTrade.signal_name, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) + reverse_for_cond_exit_short = get_signal_section_directive(state=state, signal_name=activeTrade.generated_by, 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_signal_section_directive(state=state, signal_name=activeTrade.signal_name, directive_name=directive_name, default_value=safe_get(state.vars, directive_name, False)) + add_for_cond_exit_short = get_signal_section_directive(state=state, signal_name=activeTrade.generated_by, 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(state=state, data=data, direction=TradeDirection.SHORT, reason="EXIT COND MET", followup=followup_action) + close_position(state=state, activeTrae=activeTrade, data=data, direction=TradeDirection.SHORT, reason="EXIT COND MET", followup=followup_action) return #PROFIT @@ -102,7 +102,7 @@ def eval_close_position(state: StrategyState, accountsWithActiveTrade, data): #TODO pripadne pokud dosahne TGTBB prodat ihned max_price_signal = curr_price<=max_price #OPTIMALIZACE pri stoupajícím angle - if max_price_signal or dontexit_protection_met(state=state, data=data,direction=TradeDirection.SHORT) is False: + if max_price_signal or dontexit_protection_met(state=state, activeTrade=activeTrade, data=data,direction=TradeDirection.SHORT) is False: close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.SHORT, reason=f"PROFIT or MAXPROFIT REACHED {max_price_signal=}") return #pokud je cena horsi, ale byla uz dont exit aktivovany - pak prodavame také @@ -137,7 +137,7 @@ def eval_close_position(state: StrategyState, accountsWithActiveTrade, data): position = positions * exit_adjustment state.ilog(lvl=1,e=f"SL OPTIMIZATION ENGAGED {str(TradeDirection.LONG)} {position=} {level_met=} {exit_adjustment}", initial_levels=str(state.sl_optimizer_long.get_initial_abs_levels(state, activeTrade)), rem_levels=str(state.sl_optimizer_long.get_remaining_abs_levels(state, activeTrade)), exit_levels=str(state.sl_optimizer_long.exit_levels), exit_sizes=str(state.sl_optimizer_long.exit_sizes)) printanyway(f"SL OPTIMIZATION ENGAGED {str(TradeDirection.LONG)} {position=} {level_met=} {exit_adjustment}") - close_position_partial(state=state, data=data, direction=TradeDirection.LONG, reason=f"SL OPT LEVEL {level_met} REACHED", size=exit_adjustment) + close_position_partial(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason=f"SL OPT LEVEL {level_met} REACHED", size=exit_adjustment) return #SL FULL execution @@ -190,7 +190,7 @@ def eval_close_position(state: StrategyState, accountsWithActiveTrade, data): #TODO pripadne pokud dosahne TGTBB prodat ihned max_price_signal = curr_price>=max_price #OPTIMALIZACE pri stoupajícím angle - if max_price_signal or dontexit_protection_met(state, data, direction=TradeDirection.LONG) is False: + if max_price_signal or dontexit_protection_met(state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG) is False: close_position(state=state, activeTrade=activeTrade, data=data, direction=TradeDirection.LONG, reason=f"PROFIT or MAXPROFIT REACHED {max_price_signal=}") return #pokud je cena horsi, ale byl uz dont exit aktivovany - pak prodavame také diff --git a/v2realbot/strategyblocks/activetrade/helpers.py b/v2realbot/strategyblocks/activetrade/helpers.py index fa994c5..08c58fd 100644 --- a/v2realbot/strategyblocks/activetrade/helpers.py +++ b/v2realbot/strategyblocks/activetrade/helpers.py @@ -111,7 +111,7 @@ def keyword_conditions_met(state, data, activeTrade: Trade, direction: TradeDire #mozna do SL helpers tuto def insert_SL_history(state, activeTrade: Trade): #insert stoploss history as key sl_history into runner archive extended data - state.extData["sl_history"].append(SLHistory(id=activeTrade.id, time=state.time, sl_val=activeTrade.stoploss_value)) + state.extData["sl_history"].append(SLHistory(id=activeTrade.id, time=state.time, sl_val=activeTrade.stoploss_value, direction=activeTrade.direction, account=activeTrade.account)) def get_default_sl_value(state, signal_name, direction: TradeDirection): diff --git a/v2realbot/strategyblocks/activetrade/sl/trailsl.py b/v2realbot/strategyblocks/activetrade/sl/trailsl.py index 2264e83..479118a 100644 --- a/v2realbot/strategyblocks/activetrade/sl/trailsl.py +++ b/v2realbot/strategyblocks/activetrade/sl/trailsl.py @@ -87,12 +87,12 @@ def trail_SL_management(state: StrategyState, accountsWithActiveTrade, data): state.ilog(lvl=1,e=f"SL TRAIL EVAL {smer} SL:{round(activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) if (move_SL_threshold) < data['close']: activeTrade.stoploss_value += step_normalized - insert_SL_history(state) + insert_SL_history(state, activeTrade) state.ilog(lvl=1,e=f"SL TRAIL TH {smer} reached {move_SL_threshold} SL moved to {activeTrade.stoploss_value}", offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) elif direction == TradeDirection.SHORT: move_SL_threshold = activeTrade.stoploss_value - offset_normalized - def_SL_normalized state.ilog(lvl=0,e=f"SL TRAIL EVAL {smer} SL:{round(activeTrade.stoploss_value,3)} TRAILGOAL:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) if (move_SL_threshold) > data['close']: activeTrade.stoploss_value -= step_normalized - insert_SL_history(state) + insert_SL_history(state, activeTrade) state.ilog(lvl=1,e=f"SL TRAIL GOAL {smer} reached {move_SL_threshold} SL moved to {activeTrade.stoploss_value}", offset_normalized=offset_normalized, step_normalized=step_normalized, def_SL_normalized=def_SL_normalized) diff --git a/v2realbot/strategyblocks/newtrade/prescribedtrades.py b/v2realbot/strategyblocks/newtrade/prescribedtrades.py index ae66360..df964d9 100644 --- a/v2realbot/strategyblocks/newtrade/prescribedtrades.py +++ b/v2realbot/strategyblocks/newtrade/prescribedtrades.py @@ -19,7 +19,8 @@ def execute_prescribed_trades(state: StrategyState, data): accountsWithNoActiveTrade = gaka(state.account_variables, "activeTrade", None, lambda x: x is None) if len(accountsWithNoActiveTrade.values()) == 0: - print("active trades on all accounts") + #print("active trades on all accounts") + return #returns true if all values are not None #all(v is not None for v in d.keys()) diff --git a/v2realbot/strategyblocks/newtrade/signals.py b/v2realbot/strategyblocks/newtrade/signals.py index 4cc4ef5..8180588 100644 --- a/v2realbot/strategyblocks/newtrade/signals.py +++ b/v2realbot/strategyblocks/newtrade/signals.py @@ -34,7 +34,7 @@ def signal_search(state: StrategyState, data): accountsWithNoActiveTrade = gaka(state.account_variables, "activeTrade", None, lambda x: x is None) if len(accountsWithNoActiveTrade.values()) == 0: - print("active trades on all accounts") + #print("active trades on all accounts") return for signalname, signalsettings in state.vars.signals.items():