diff --git a/v2realbot/ENTRY_ClassicSL_v01.py b/v2realbot/ENTRY_ClassicSL_v01.py index 006a8d7..d44a04a 100644 --- a/v2realbot/ENTRY_ClassicSL_v01.py +++ b/v2realbot/ENTRY_ClassicSL_v01.py @@ -8,6 +8,7 @@ from v2realbot.indicators.oscillators import rsi from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType from v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, print, safe_get, get_tick, round2five, is_open_rush, is_close_rush, eval_cond_dict, Average, crossed_down, crossed_up, crossed, is_pivot, json_serial from datetime import datetime +from uuid import uuid4 import json #from icecream import install, ic #from rich import print @@ -769,7 +770,7 @@ def next(data, state: StrategyState): curr_price = float(data['close']) state.ilog(e="Eval CLOSE", price=curr_price, pos=state.positions, avgp=state.avgp, pending=state.vars.pending, activeTrade=str(state.vars.activeTrade)) - if int(state.positions) != 0 and float(state.avgp)>0 and state.vars.pending is False: + 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() @@ -790,7 +791,7 @@ def next(data, state: StrategyState): res = state.buy(size=abs(state.positions)) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation STOPLOSS BUY {res}") - state.vars.pending = True + state.vars.pending = state.vars.activeTrade.id state.vars.activeTrade = None return @@ -799,7 +800,7 @@ def next(data, state: StrategyState): res = state.buy(size=abs(state.positions)) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation EXIT COND BUY {res}") - state.vars.pending = True + state.vars.pending = state.vars.activeTrade.id state.vars.activeTrade = None state.ilog(e=f"EXIT COND MET. market BUY was sent {curr_price=}", positions=state.positions, avgp=state.avgp) return @@ -815,7 +816,7 @@ def next(data, state: StrategyState): res = state.buy(size=abs(state.positions)) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation PROFIT BUY {res}") - state.vars.pending = True + state.vars.pending = state.vars.activeTrade.id state.vars.activeTrade = None state.ilog(e=f"PROFIT MET EXIT. market BUY was sent {curr_price=} {max_price_signal=}", positions=state.positions, avgp=state.avgp) return @@ -832,7 +833,7 @@ def next(data, state: StrategyState): res = state.sell(size=state.positions) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation STOPLOSS SELL {res}") - state.vars.pending = True + state.vars.pending = state.vars.activeTrade.id state.vars.activeTrade = None return @@ -840,7 +841,7 @@ def next(data, state: StrategyState): res = state.sell(size=abs(state.positions)) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation EXIT COND SELL {res}") - state.vars.pending = True + state.vars.pending = state.vars.activeTrade.id state.vars.activeTrade = None state.ilog(e=f"EXIT COND MET. market SELL was sent {curr_price=}", positions=state.positions, avgp=state.avgp) return @@ -856,7 +857,7 @@ def next(data, state: StrategyState): res = state.sell(size=state.positions) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation PROFIT SELL {res}") - state.vars.pending = True + state.vars.pending = state.vars.activeTrade.id state.vars.activeTrade = None state.ilog(e=f"PROFIT MET EXIT. market SELL was sent {curr_price=} {max_price_signal=}", positions=state.positions, avgp=state.avgp, sellinprogress=state.vars.sell_in_progress) return @@ -897,7 +898,7 @@ def next(data, state: StrategyState): sl_defvalue_normalized = get_tick(data['close'],sl_defvalue) state.vars.activeTrade.stoploss_value = float(data['close']) - sl_defvalue_normalized state.ilog(e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }") - state.vars.pending = True + 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) @@ -910,7 +911,7 @@ def next(data, state: StrategyState): sl_defvalue_normalized = get_tick(data['close'],sl_defvalue) state.vars.activeTrade.stoploss_value = float(data['close']) + sl_defvalue_normalized state.ilog(e=f"Nastaveno SL na {sl_defvalue}, priced normalized: {sl_defvalue_normalized} price: {state.vars.activeTrade.stoploss_value }") - state.vars.pending = True + state.vars.pending = state.vars.activeTrade.id else: state.ilog(e="unknow direction") state.vars.activeTrade = None @@ -1035,13 +1036,17 @@ def next(data, state: StrategyState): #common signals based on 1) configured signals in stratvars #toto umoznuje jednoduchy prescribed trade bez ceny if conditions_met(signalname=name, direction=TradeDirection.LONG): - state.vars.prescribedTrades.append(Trade(validfrom=datetime.now(tz=zoneNY), + state.vars.prescribedTrades.append(Trade( + id=uuid4(), + validfrom=datetime.now(tz=zoneNY), status=TradeStatus.READY, direction=TradeDirection.LONG, entry_price=None, stoploss_value = None)) elif conditions_met(signalname=name, direction=TradeDirection.SHORT): - state.vars.prescribedTrades.append(Trade(validfrom=datetime.now(tz=zoneNY), + state.vars.prescribedTrades.append(Trade( + id=uuid4(), + validfrom=datetime.now(tz=zoneNY), status=TradeStatus.READY, direction=TradeDirection.SHORT, entry_price=None, @@ -1083,24 +1088,24 @@ def next(data, state: StrategyState): #MAIN LOOP lp = data['close'] - state.ilog(e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),3)} SL:{state.vars.activeTrade.stoploss_value} profit:{round(float(state.profit),2)} Trades:{len(state.tradeList)}", activeTrade=json.loads(json.dumps(state.vars.activeTrade, default=json_serial)), prescribedTrades=json.loads(json.dumps(state.vars.prescribedTrades, default=json_serial)), pending=str(state.vars.pending), last_price=lp, data=data, stratvars=str(state.vars)) + state.ilog(e="ENTRY", msg=f"LP:{lp} P:{state.positions}/{round(float(state.avgp),3)} SL:{state.vars.activeTrade.stoploss_value if state.vars.activeTrade is not None else None} profit:{round(float(state.profit),2)} Trades:{len(state.tradeList)}", activeTrade=json.loads(json.dumps(state.vars.activeTrade, default=json_serial)), prescribedTrades=json.loads(json.dumps(state.vars.prescribedTrades, default=json_serial)), pending=str(state.vars.pending), last_price=lp, data=data, stratvars=str(state.vars)) inds = get_last_ind_vals() state.ilog(e="Indikatory", **inds) #TODO dat do initu inciializaci work directory pro directivy #pokud mame prazdne pozice a neceka se na nic - if state.positions == 0 and state.vars.pending is False: + if state.positions == 0 and state.vars.pending is None: execute_prescribed_trades() #pokud se neaktivoval nejaky trade, poustime signal search - ale jen jednou za bar? #if conf_bar == 1: - if state.vars.pending is False: + if state.vars.pending is None: signal_search() #pro jistotu ihned zpracujeme execute_prescribed_trades() #mame aktivni trade a neceka se nani - elif state.vars.activeTrade and state.vars.pending is False: + elif state.vars.activeTrade and state.vars.pending is None: manage_active_trade() #optimalize, close # - close means change status in prescribed Trends,update profit, delete from activeTrade @@ -1158,7 +1163,7 @@ def init(state: StrategyState): #nove atributy na rizeni tradu #identifikuje provedenou změnu na Tradu (neděláme změny dokud nepřijde potvrzeni z notifikace) - state.vars.pending = False + state.vars.pending = None #obsahuje aktivni Trade a jeho nastaveni state.vars.activeTrade = None #pending/Trade #obsahuje pripravene Trady ve frontě diff --git a/v2realbot/common/PrescribedTradeModel.py b/v2realbot/common/PrescribedTradeModel.py index 84de826..9de7e93 100644 --- a/v2realbot/common/PrescribedTradeModel.py +++ b/v2realbot/common/PrescribedTradeModel.py @@ -2,7 +2,7 @@ from enum import Enum from datetime import datetime from pydantic import BaseModel from typing import Any, Optional, List, Union - +from uuid import UUID class TradeStatus(str, Enum): READY = "ready" ACTIVATED = "activated" @@ -17,6 +17,7 @@ class TradeStoplossType(str, Enum): TRAILING = "trailing" class Trade(BaseModel): + id: UUID validfrom: datetime status: TradeStatus direction: TradeDirection @@ -24,4 +25,5 @@ class Trade(BaseModel): # stoploss_type: TradeStoplossType stoploss_value: Optional[float] = None profit: Optional[float] = None + profit_sum: Optional[float] = None diff --git a/v2realbot/common/__pycache__/model.cpython-310.pyc b/v2realbot/common/__pycache__/model.cpython-310.pyc index a1fbc4b..26abf1a 100644 Binary files a/v2realbot/common/__pycache__/model.cpython-310.pyc and b/v2realbot/common/__pycache__/model.cpython-310.pyc differ diff --git a/v2realbot/common/model.py b/v2realbot/common/model.py index 400b882..8d80166 100644 --- a/v2realbot/common/model.py +++ b/v2realbot/common/model.py @@ -166,6 +166,8 @@ class TradeUpdate(BaseModel): value: Optional[float] cash: Optional[float] pos_avg_price: Optional[float] + profit: Optional[float] + profit_sum: Optional[float] class RunArchiveChange(BaseModel): diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 2243c86..2de8255 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -466,6 +466,12 @@ def populate_metrics_output_directory(strat: StrategyInstance): #filt = max_positions['side'] == 'OrderSide.BUY' res = dict(zip(max_positions['qty'], max_positions['count'])) + #pridani klice obsahujici prescribed trades + try: + res["prescr_trades"]=json.loads(json.dumps(strat.state.vars.prescribedTrades, default=json_serial)) + except NameError: + pass + return res #archives runner and details diff --git a/v2realbot/static/index.html b/v2realbot/static/index.html index 371c9a6..01c8603 100644 --- a/v2realbot/static/index.html +++ b/v2realbot/static/index.html @@ -242,7 +242,7 @@
- +
diff --git a/v2realbot/static/js/archivechart.js b/v2realbot/static/js/archivechart.js index 8a55e54..a42f9f4 100644 --- a/v2realbot/static/js/archivechart.js +++ b/v2realbot/static/js/archivechart.js @@ -5,7 +5,7 @@ var CHART_SHOW_TEXT = false // var volumeSeries = null var markersLine = null var avgBuyLine = null -//TRANSFORM object returned from RESTA PI get_arch_run_detail +//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]...} //output array [{ time: 111, open: 11, high: 33, low: 333, close: 333},..] @@ -100,13 +100,26 @@ function transform_data(data) { marker = {} marker["time"] = timestamp; // marker["position"] = (trade.order.side == "buy") ? "belowBar" : "aboveBar" - marker["position"] = (trade.order.side == "buy") ? "inBar" : "aboveBar" + marker["position"] = (trade.order.side == "buy") ? "aboveBar" : "aboveBar" marker["color"] = (trade.order.side == "buy") ? "#37cade" : "red" //marker["shape"] = (trade.order.side == "buy") ? "arrowUp" : "arrowDown" - marker["shape"] = (trade.order.side == "buy") ? "circle" : "arrowDown" + marker["shape"] = (trade.order.side == "buy") ? "arrowUp" : "arrowDown" //marker["text"] = trade.qty + "/" + trade.price - marker["text"] = CHART_SHOW_TEXT ? trade.qty + "/" + trade.price : trade.qty - marker["text"] = (trade.position_qty == 0) ? "c": marker["text"] + qt_optimized = (trade.qty % 1000 === 0) ? (trade.qty / 1000).toFixed(1) + 'K' : trade.qty + + if (CHART_SHOW_TEXT) { + //včetně qty + //marker["text"] = qt_optimized + "@" + trade.price + + //bez qty + 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 : "" + } + markers.push(marker) //prevedeme iso data na timestampy diff --git a/v2realbot/strategy/StrategyClassicSL.py b/v2realbot/strategy/StrategyClassicSL.py index 949b7e5..1718579 100644 --- a/v2realbot/strategy/StrategyClassicSL.py +++ b/v2realbot/strategy/StrategyClassicSL.py @@ -27,9 +27,7 @@ class StrategyClassicSL(Strategy): o: Order = data.order ##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 o.status == OrderStatus.FILLED or o.status == OrderStatus.CANCELED: - #davame pryc pending - self.state.vars.pending = False + if data.event == TradeEvent.FILL or data.event == TradeEvent.PARTIAL_FILL: @@ -44,9 +42,24 @@ class StrategyClassicSL(Strategy): self.state.ilog(e="ERR: Nemame naklady na PROFIT, AVGP je nula. Zaznamenano jako 0", msg="naklady=utrzena cena. TBD opravit.") avg_costs = bought_amount - trade_profit = (avg_costs-bought_amount) + trade_profit = round((avg_costs-bought_amount),2) self.state.profit += trade_profit self.state.ilog(e=f"BUY notif - SHORT PROFIT:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)}", msg=str(data.event), bought_amount=bought_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: + if trade.id == self.state.vars.pending: + trade.profit = trade_profit + trade.profit_sum = self.state.profit + + #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) + self.state.ilog(f"updatnut tradeList o profi {str(tradeData)}") + else: self.state.ilog(e="BUY: Jde o LONG nakuú nepocitame profit zatim") @@ -56,6 +69,10 @@ class StrategyClassicSL(Strategy): self.state.positions = data.position_qty self.state.avgp, self.state.positions = self.state.interface.pos() + if o.status == OrderStatus.FILLED or o.status == OrderStatus.CANCELED: + #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))) @@ -72,9 +89,24 @@ class StrategyClassicSL(Strategy): self.state.ilog(e="ERR: Nemame naklady na PROFIT, AVGP je nula. Zaznamenano jako 0", msg="naklady=utrzena cena. TBD opravit.") avg_costs = sold_amount - trade_profit = (sold_amount - avg_costs) + trade_profit = round((sold_amount - avg_costs),2) self.state.profit += trade_profit self.state.ilog(e=f"SELL notif - PROFIT:{round(float(trade_profit),3)} celkem:{round(float(self.state.profit),3)}", msg=str(data.event), 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: + if trade.id == self.state.vars.pending: + trade.profit = trade_profit + trade.profit_sum = self.state.profit + + #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) + self.state.ilog(f"updatnut tradeList o profi {str(tradeData)}") + else: self.state.ilog(e="SELL: Jde o SHORT nepocitame profit zatim") @@ -90,7 +122,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.vars.pending = False + self.state.vars.pending = None a,p = self.interface.pos() #pri chybe api nechavame puvodni hodnoty if a != -1: diff --git a/v2realbot/strategy/__pycache__/base.cpython-310.pyc b/v2realbot/strategy/__pycache__/base.cpython-310.pyc index fb8ea99..80e9dcd 100644 Binary files a/v2realbot/strategy/__pycache__/base.cpython-310.pyc and b/v2realbot/strategy/__pycache__/base.cpython-310.pyc differ