From 4c861c59a1a58c8c9bd96eb2962fb7b2d3fb3eac Mon Sep 17 00:00:00 2001 From: David Brazda Date: Mon, 28 Aug 2023 20:22:37 +0200 Subject: [PATCH] pridany metriky a bugfixy pro live run --- v2realbot/ENTRY_ClassicSL_v01.py | 76 +++++++++++++----- v2realbot/common/PrescribedTradeModel.py | 6 +- v2realbot/controller/services.py | 39 ++++++++- v2realbot/static/index.html | 2 +- v2realbot/strategy/StrategyClassicSL.py | 10 ++- .../strategy/__pycache__/base.cpython-310.pyc | Bin 14370 -> 14405 bytes v2realbot/strategy/base.py | 2 + .../utils/__pycache__/utils.cpython-310.pyc | Bin 11905 -> 12120 bytes v2realbot/utils/utils.py | 6 +- 9 files changed, 112 insertions(+), 29 deletions(-) diff --git a/v2realbot/ENTRY_ClassicSL_v01.py b/v2realbot/ENTRY_ClassicSL_v01.py index d44a04a..6e7cb54 100644 --- a/v2realbot/ENTRY_ClassicSL_v01.py +++ b/v2realbot/ENTRY_ClassicSL_v01.py @@ -6,7 +6,7 @@ from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Orde from v2realbot.indicators.indicators import ema 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 v2realbot.utils.utils import ltp, isrising, isfalling,trunc,AttributeDict, zoneNY, price2dec, print, safe_get, 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 @@ -173,13 +173,13 @@ def next(data, state: StrategyState): #vrati true pokud dany indikator prekrocil threshold dolu def buy_if_crossed_down(indicator, value): res = crossed_down(threshold=value, list=get_source_or_MA(indicator)) - state.ilog(e=f"buy_if_crossed_down {indicator} {value} {res}") + state.ilog(e=f"signal_if_crossed_down {indicator} {value} {res}") return res #vrati true pokud dany indikator prekrocil threshold nahoru def buy_if_crossed_up(indicator, value): res = crossed_up(threshold=value, list=get_source_or_MA(indicator)) - state.ilog(e=f"buy_if_crossed_up {indicator} {value} {res}") + state.ilog(e=f"signal_if_crossed_up {indicator} {value} {res}") return res def populate_cbar_tick_price_indicator(): @@ -625,6 +625,36 @@ def next(data, state: StrategyState): # state.ilog(e=f"SELL_PROTECTION {conditions_met} enabled") # return result + def normalize_tick(tick: float, price: float = None, return_two_decimals: bool = False): + """ + Pokud je nastaveno v direktive: + #zda normalizovat vsechyn ticky (tzn. profit, maxprofit, SL atp.) + Normalize_ticks= true + Normalized Tick base price = 30 + + prevede normalizovany tick na tick odpovidajici vstupni cene + vysledek je zaokoruhleny na 2 des.mista + + u cen pod 30, vrací 0.01. U cen nad 30 vrací pomerne zvetsene, + + """ + #nemusime dodavat cenu, bereme aktualni + if price is None: + price = data["close"] + + normalize_ticks = safe_get(state.vars, "normalize_ticks",False) + normalized_base_price = safe_get(state.vars, "normalized_base_price",30) + if normalize_ticks: + if price 0 else price2dec(cena-get_tick(cena,float(state.vars.profit)),3) + return price2dec(cena+normalize_tick(float(state.vars.profit)),3) if int(state.positions) > 0 else price2dec(cena-normalize_tick(float(state.vars.profit)),3) def get_max_profit_price(): max_profit = float(safe_get(state.vars, "max_profit",0.03)) cena = float(state.avgp) - return price2dec(cena+get_tick(cena,max_profit),3) if state.positions > 0 else price2dec(cena-get_tick(cena,max_profit),3) + return price2dec(cena+normalize_tick(max_profit),3) if int(state.positions) > 0 else price2dec(cena-normalize_tick(max_profit),3) #TBD pripadne opet dat parsovani pole do INITu @@ -668,7 +698,7 @@ def next(data, state: StrategyState): 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(state.positions) + 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) @@ -747,20 +777,22 @@ def next(data, state: StrategyState): #pokud je pozadovan trail jen do breakeven a uz prekroceno if (direction == TradeDirection.LONG and stop_breakeven and state.vars.activeTrade.stoploss_value >= float(state.avgp)) or (direction == TradeDirection.SHORT and stop_breakeven and state.vars.activeTrade.stoploss_value <= float(state.avgp)): - state.ilog(e=f"SL trail stop at breakeven {str(smer)} SL {state.vars.activeTrade.stoploss_value} UNCHANGED", stop_breakeven=stop_breakeven) + state.ilog(e=f"SL trail stop at breakeven {str(smer)} SL:{state.vars.activeTrade.stoploss_value} UNCHANGED", stop_breakeven=stop_breakeven) return #IDEA: Nyni posouvame SL o offset, mozna ji posunout jen o direktivu step ? - offset_normalized = get_tick(data['close'],offset) #to ticks and from options - def_SL_normalized = get_tick(data['close'],def_SL) + offset_normalized = normalize_tick(offset) #to ticks and from options + def_SL_normalized = normalize_tick(def_SL) if direction == TradeDirection.LONG: move_SL_threshold = state.vars.activeTrade.stoploss_value + offset_normalized + def_SL_normalized + state.ilog(e=f"SL trailing EVAL {smer} SL:{state.vars.activeTrade.stoploss_value} MOVETHRESHOLD:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, def_SL_normalized=def_SL_normalized) if (move_SL_threshold) < data['close']: state.vars.activeTrade.stoploss_value += offset_normalized state.ilog(e=f"SL TRAIL TH {smer} reached {move_SL_threshold} SL moved to {state.vars.activeTrade.stoploss_value}", offset_normalized=offset_normalized, def_SL_normalized=def_SL_normalized) elif direction == TradeDirection.SHORT: move_SL_threshold = state.vars.activeTrade.stoploss_value - offset_normalized - def_SL_normalized + state.ilog(e=f"SL trailing EVAL {smer} SL:{state.vars.activeTrade.stoploss_value} MOVETHRESHOLD:{move_SL_threshold}", def_SL=def_SL, offset=offset, offset_normalized=offset_normalized, def_SL_normalized=def_SL_normalized) if (move_SL_threshold) > data['close']: state.vars.activeTrade.stoploss_value -= offset_normalized state.ilog(e=f"SL TRAIL TH {smer} reached {move_SL_threshold} SL moved to {state.vars.activeTrade.stoploss_value}", offset_normalized=offset_normalized, def_SL_normalized=def_SL_normalized) @@ -788,7 +820,7 @@ def next(data, state: StrategyState): #SL - execution if curr_price > state.vars.activeTrade.stoploss_value: state.ilog(e=f"STOPLOSS reached on SHORT", curr_price=curr_price, trade=state.vars.activeTrade) - res = state.buy(size=abs(state.positions)) + res = state.buy(size=abs(int(state.positions))) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation STOPLOSS BUY {res}") state.vars.pending = state.vars.activeTrade.id @@ -797,7 +829,7 @@ def next(data, state: StrategyState): #CLOSING BASED ON EXIT CONDITIONS if exit_conditions_met(TradeDirection.SHORT): - res = state.buy(size=abs(state.positions)) + res = state.buy(size=abs(int(state.positions))) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation EXIT COND BUY {res}") state.vars.pending = state.vars.activeTrade.id @@ -813,7 +845,7 @@ def next(data, state: StrategyState): max_price_signal = curr_price<=max_price #OPTIMALIZACE pri stoupajícím angle if max_price_signal or sell_protection_enabled() is False: - res = state.buy(size=abs(state.positions)) + res = state.buy(size=abs(int(state.positions))) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation PROFIT BUY {res}") state.vars.pending = state.vars.activeTrade.id @@ -838,7 +870,7 @@ def next(data, state: StrategyState): return if exit_conditions_met(TradeDirection.LONG): - res = state.sell(size=abs(state.positions)) + res = state.sell(size=abs(int(state.positions))) if isinstance(res, int) and res < 0: raise Exception(f"error in required operation EXIT COND SELL {res}") state.vars.pending = state.vars.activeTrade.id @@ -872,6 +904,7 @@ def next(data, state: StrategyState): for trade in state.vars.prescribedTrades: if trade.status == TradeStatus.READY and trade.direction == TradeDirection.LONG and (trade.entry_price is None or trade.entry_price >= data['close']): trade.status = TradeStatus.ACTIVATED + trade.last_update = datetime.fromtimestamp(state.time).astimezone(zoneNY) state.ilog(e=f"evaluated SHORT {str(trade)}", prescrTrades=json.loads(json.dumps(state.vars.prescribedTrades, default=json_serial))) state.vars.activeTrade = trade break @@ -881,6 +914,7 @@ def next(data, state: StrategyState): if trade.status == TradeStatus.READY and trade.direction == TradeDirection.SHORT and (trade.entry_price is None or trade.entry_price <= data['close']): state.ilog(e=f"evaluaed SHORT {str(trade)}", prescTrades=json.loads(json.dumps(state.vars.prescribedTrades, default=json_serial))) trade.status = TradeStatus.ACTIVATED + trade.last_update = datetime.fromtimestamp(state.time).astimezone(zoneNY) state.vars.activeTrade = trade break @@ -895,7 +929,7 @@ def next(data, state: StrategyState): if state.vars.activeTrade.stoploss_value is None: sl_defvalue = get_default_sl_value(direction=state.vars.activeTrade.direction) #normalizuji dle aktualni ceny - sl_defvalue_normalized = get_tick(data['close'],sl_defvalue) + sl_defvalue_normalized = normalize_tick(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 = state.vars.activeTrade.id @@ -908,7 +942,7 @@ def next(data, state: StrategyState): if state.vars.activeTrade.stoploss_value is None: sl_defvalue = get_default_sl_value(direction=state.vars.activeTrade.direction) #normalizuji dle aktualni ceny - sl_defvalue_normalized = get_tick(data['close'],sl_defvalue) + sl_defvalue_normalized = normalize_tick(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 = state.vars.activeTrade.id @@ -1028,25 +1062,27 @@ def next(data, state: StrategyState): recurring = safe_get(options, "reccurring", False) on_confirmed_only = safe_get(options, 'on_confirmed_only', False) plugin = safe_get(options, 'plugin', None) + short_enabled = safe_get(state.vars, "short_enabled",True) + long_enabled = safe_get(state.vars, "long_enabled",True) #pokud je plugin True, spusti se kod if plugin: execute_signal_generator_plugin(name) else: #common signals based on 1) configured signals in stratvars - #toto umoznuje jednoduchy prescribed trade bez ceny - if conditions_met(signalname=name, direction=TradeDirection.LONG): + #toto umoznuje jednoduchy prescribed trade bez ceny + if long_enabled and conditions_met(signalname=name, direction=TradeDirection.LONG): state.vars.prescribedTrades.append(Trade( id=uuid4(), - validfrom=datetime.now(tz=zoneNY), + last_update=datetime.fromtimestamp(state.time).astimezone(zoneNY), status=TradeStatus.READY, direction=TradeDirection.LONG, entry_price=None, stoploss_value = None)) - elif conditions_met(signalname=name, direction=TradeDirection.SHORT): + elif short_enabled and conditions_met(signalname=name, direction=TradeDirection.SHORT): state.vars.prescribedTrades.append(Trade( id=uuid4(), - validfrom=datetime.now(tz=zoneNY), + last_update=datetime.fromtimestamp(state.time).astimezone(zoneNY), status=TradeStatus.READY, direction=TradeDirection.SHORT, entry_price=None, diff --git a/v2realbot/common/PrescribedTradeModel.py b/v2realbot/common/PrescribedTradeModel.py index 9de7e93..464191c 100644 --- a/v2realbot/common/PrescribedTradeModel.py +++ b/v2realbot/common/PrescribedTradeModel.py @@ -18,12 +18,12 @@ class TradeStoplossType(str, Enum): class Trade(BaseModel): id: UUID - validfrom: datetime + last_update: datetime status: TradeStatus direction: TradeDirection entry_price: Optional[float] = None # stoploss_type: TradeStoplossType stoploss_value: Optional[float] = None - profit: Optional[float] = None - profit_sum: Optional[float] = None + profit: Optional[float] = 0 + profit_sum: Optional[float] = 0 diff --git a/v2realbot/controller/services.py b/v2realbot/controller/services.py index 2de8255..f88898e 100644 --- a/v2realbot/controller/services.py +++ b/v2realbot/controller/services.py @@ -9,6 +9,7 @@ from v2realbot.enums.enums import RecordType, StartBarAlign, Mode, Account, Orde from v2realbot.common.model import StrategyInstance, Runner, RunRequest, RunArchive, RunArchiveDetail, RunArchiveChange, Bar, TradeEvent from v2realbot.utils.utils import AttributeDict, zoneNY, dict_replace_value, Store, parse_toml_string, json_serial, is_open_hours, send_to_telegram from v2realbot.utils.ilog import delete_logs +from v2realbot.common.PrescribedTradeModel import Trade, TradeDirection, TradeStatus, TradeStoplossType from datetime import datetime from threading import Thread, current_thread, Event, enumerate from v2realbot.config import STRATVARS_UNCHANGEABLES, ACCOUNT1_LIVE_API_KEY, ACCOUNT1_LIVE_SECRET_KEY, DATA_DIR,BT_FILL_CONS_TRADES_REQUIRED,BT_FILL_LOG_SURROUNDING_TRADES,BT_FILL_CONDITION_BUY_LIMIT,BT_FILL_CONDITION_SELL_LIMIT, GROUP_TRADES_WITH_TIMESTAMP_LESS_THAN @@ -466,9 +467,45 @@ 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 + #metrikz z prescribedTrades, pokud existuji try: + long_profit = 0 + short_profit = 0 + long_losses = 0 + short_losses = 0 + long_wins = 0 + short_wins = 0 + max_profit = 0 + max_profit_time = None + for trade in strat.state.vars.prescribedTrades: + if trade.profit_sum > max_profit: + max_profit = trade.profit_sum + max_profit_time = trade.last_update + if trade.status == TradeStatus.ACTIVATED and trade.direction == TradeDirection.LONG: + if trade.profit is not None: + long_profit += trade.profit + if trade.profit < 0: + long_losses += trade.profit + if trade.profit > 0: + long_wins += trade.profit + if trade.status == TradeStatus.ACTIVATED and trade.direction == TradeDirection.SHORT: + if trade.profit is not None: + short_profit += trade.profit + if trade.profit < 0: + short_losses += trade.profit + if trade.profit > 0: + short_wins += trade.profit + res["long_profit"] = round(long_profit,2) + res["short_profit"] = round(short_profit,2) + res["long_losses"] = round(long_losses,2) + res["short_losses"] = round(short_losses,2) + res["long_wins"] = round(long_wins,2) + res["short_wins"] = round(short_wins,2) + res["max_profit"] = round(max_profit,2) + res["max_profit_time"] = str(max_profit_time) + #vlozeni celeho listu res["prescr_trades"]=json.loads(json.dumps(strat.state.vars.prescribedTrades, default=json_serial)) + except NameError: pass diff --git a/v2realbot/static/index.html b/v2realbot/static/index.html index 01c8603..dd60301 100644 --- a/v2realbot/static/index.html +++ b/v2realbot/static/index.html @@ -351,7 +351,7 @@
- +
diff --git a/v2realbot/strategy/StrategyClassicSL.py b/v2realbot/strategy/StrategyClassicSL.py index 1718579..87e80b3 100644 --- a/v2realbot/strategy/StrategyClassicSL.py +++ b/v2realbot/strategy/StrategyClassicSL.py @@ -2,10 +2,12 @@ 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 from v2realbot.utils.tlog import tlog, tlog_exception from v2realbot.enums.enums import Mode, Order, Account, RecordType -from alpaca.trading.models import TradeUpdate +#from alpaca.trading.models import TradeUpdate +from v2realbot.common.model import TradeUpdate from alpaca.trading.enums import TradeEvent, OrderStatus from v2realbot.indicators.indicators import ema import json +from datetime import datetime #from rich import print from random import randrange from alpaca.common.exceptions import APIError @@ -49,7 +51,8 @@ class StrategyClassicSL(Strategy): #zapsat profit do prescr.trades for trade in self.state.vars.prescribedTrades: if trade.id == self.state.vars.pending: - trade.profit = trade_profit + trade.last_update = datetime.fromtimestamp(self.state.time).astimezone(zoneNY) + trade.profit += trade_profit trade.profit_sum = self.state.profit #zapsat update profitu do tradeList @@ -96,7 +99,8 @@ class StrategyClassicSL(Strategy): #zapsat profit do prescr.trades for trade in self.state.vars.prescribedTrades: if trade.id == self.state.vars.pending: - trade.profit = trade_profit + trade.last_update = datetime.fromtimestamp(self.state.time).astimezone(zoneNY) + trade.profit += trade_profit trade.profit_sum = self.state.profit #zapsat update profitu do tradeList diff --git a/v2realbot/strategy/__pycache__/base.cpython-310.pyc b/v2realbot/strategy/__pycache__/base.cpython-310.pyc index 80e9dcda05a20ea03d52eda691a06cc49156efed..0b5dbbe6bf88bc4f3d605ceead277195d3f3783a 100644 GIT binary patch delta 355 zcmZ2faI}CopO=@50SFBCyh(Yxk@trT7b}o=7>J8cP3D)Csh2HbS-`rGVFBAhhFYc) z_7s*(h8o5N919tiF!nLlGUl<=Fl2FNan&*bY3?kZ6vh(X8m4AOafTG8UPeZS6225> zAfJhmp@v}r|3U^J&6p)nBA6wV%~a$B)+4+?q=s=JP%ThKw3fMsIZLcWJWB#7A6&y= zvOp3f>B*47-~rMMk(2^TCIbzWMwUxq2nMPOX3%7vd`8xYy~q#f2~C+vaw=?!Kvt3a zWCginJPJT2Q-cH}52Fav$ooTviv`F#48+AdCiBb6)Qgm`EMQ&8uz+nLLoHJt zOASL7dlpA66OiW2;!0sG;jUq7W)x>gVd`aMWGLZDVFvP<7#V697Vs`)0Md+Ed?ox@ z0@+MOmS8=C3xsMI7XsA+WrS;)YnZb{N<_27fbw2IJ>noaPlgl*50GApmTzO+GkKPL8sq-S zj0*c14^KX*P|bK^vaMnh>VtE>bH1Oe6Km&EVKSMB zIUKES`VS91O+9LyyNy^ZK@O+GTBu20RFRpzAgLm>5+x%jTMG4z2vki@8dWG}`>1H7 z7UVR6EJun2l|#QA>Ux*2v@9A`sZvE!WVA4HTB##j0rim4f4rh$@{FF9|I`qw&OAI#7^wG8OL+`xj7H4+M$*O-~+#DeTh$eYxfo?0&|_aw8aU4HJ0(W=Satz;1Yl7 VUB?Z6yT6Kgd+Goh(9HS%!9Q&epppOp delta 243 zcmVZw(C!00000#2Myf&M>hKrU?qV0000AARr(haFdk^`~e8Fk_%!3 z0brB$4xa&*la&wl0hzOm5T^kFsIxQ@jRFC*v%(Zh0Rhsp4i=UH0p63P7rz1IlVuo{ z0o#-N7@Ps@lb9L$0rQiS8s-7|lX@HM0Rod}9D@M}v+EqE0RarNlO8q!0T{E&AJPE< zB(t3%6afK0ld>XZ0XvfzBbEV2ldmI40Yj7cBcB0Elb9rZ0Zo$@C4vD_vzaAP0s&r= t>nCdgRI^toW&r_cv%V<@0RecE#wy#>GAS?j`0S}WRFE*2eQ~>}0 diff --git a/v2realbot/utils/utils.py b/v2realbot/utils/utils.py index ceb8735..4db727a 100644 --- a/v2realbot/utils/utils.py +++ b/v2realbot/utils/utils.py @@ -80,7 +80,11 @@ def crossed(threshold, list): def get_tick(price: float, normalized_ticks: float = 0.01): """ - prevede normalizovany tick na tick odpovidajici vstupni cene + Pozor existuje varianta "normalize_tick", ktera je lepsi a podporuje direktivy ve strategii: + Normalize_ticks= true + Normalized Tick base price = 30 + Tahle verze pracuje s globalnim nastavenim. + Prevede normalizovany tick na tick odpovidajici vstupni cene vysledek je zaokoruhleny na 2 des.mista u cen pod 30, vrací 0.01. U cen nad 30 vrací pomerne zvetsene,