def get_relative_lh_temps(self): """Renvoie les conditions relatives de type temps.""" mask = self.get_temps_cond().values assert sum( mask) == 2, f"mask={mask}, self.cond_frame={self.cond_frame}" sorted_cond = self.cond_frame.loc[mask, "value"].sort_values() low, high = sorted_cond.values return low - now(), high - now()
def position(self, symbol=None): data = { "account": "mlk", "symbol": self.symbol, "currency": self.symbol, "underlying": None, "quoteCurrency": "USD", "commission": None, "initMarginReq": None, "maintMarginReq": None, "riskLimit": None, "leverage": 1, "prevRealisedPnl": None, "prevClosePrice": None, "openingTimestamp": None, "currentTimestamp": None, "timestamp": now(), "avgEntryPrice": None, "lastPrice": self.current_dum_price, "currentCost": "?", "currentQty": 100, "liquidationPrice": "?", "breakEvenPrice": None, } return data
def get_exec_with_(self, srcKey_, minTransacTime, debug_=False): """ Récupère les clOrdID associés à srcKey_ si ils existent. ajoute une condition de temps """ _execution = self.execution() # Ordres exécutés et ordonnés dans l'ordre ascendant exec_orders = (_execution.loc[:, ["clOrdID", "transactTime"]] if len(_execution) else []) if debug_: return _execution.loc[:, EXECOLS] if len(_execution) else [] seenIDs: Set[str] = set() clOrdIDs = [] if len(exec_orders): for i, clID in enumerate(exec_orders["clOrdID"]): clID_not_seen = clID not in seenIDs possible_srID = self.get_srcKey(clID) == srcKey_ in_last_minute = exec_orders.iloc[i].loc["transactTime"] > ( now() - Timedelta("30s")) if clID_not_seen and possible_srID and in_last_minute: clOrdIDs.append(clID) seenIDs |= set([clID]) return clOrdIDs
def fin_essai(self, i, n, close=False, dr_pause=None, dr_essai_theo=None): """ Affiche les infos de finalisation de l'esssai et close quantity close. # on fait varie le temps d'attente entre les essais. # Il dépend du temps de l'essai """ # info sur les résultats if close: # self.brg.cancel_and_close(quantity=close) # pourrai avoir un pb de timed out ici # sleep(randint(1, 3)) pass # revoir le calcul de la balance self.resultats.loc[now(), "balance"] = self.balance() res_delta = self.resultats.iloc[-1] - self.resultats.iloc[-2] self.resultats.iloc[-1].loc["benef"] = res_delta.loc["balance"] # info sur la durée de l'essai self.logger.info(f"#### Fin de l'essai {i+1}/{n}, Résultats:\n" f"{self.resultats.iloc[-1,:]}") # on ne fait une pause que si il reste assez du temps pour le faire if not self.oct.main_oc.timed_out() and i + 1 < n: self.pause(dr_pause, dr_essai_theo)
def pause(self, dr_pause, dr_essai_theo): """ Défini un temps d'attente variable entre les différents essais. - dr_pause est en secondes """ dr_pause = 10 if dr_pause is None else dr_pause try: dr_essai = now() - self.tpsDebEssai dr_delta = dr_essai - dr_essai_theo if dr_delta.seconds > 0: pause = dr_pause else: pause = dr_delta.seconds + dr_pause # attend au moins 10 secondes puis un nb aléatoire # qui suit une loi exponentielle de param dr_pause rnd_wait = np.floor(np.random.exponential(pause)) dr_pause = pd.Timedelta(10 + rnd_wait, unit="s") self.logger.info( f"Temps de l'essai {dr_essai} (theo: {dr_essai_theo})." f" Going to sleep for {dr_pause}.") sleep(dr_pause.seconds) except Exception as e: self.logger.error(f"{e} with dr_pause={dr_pause}, " f"dr_essai_theo={dr_essai_theo} pause={pause}")
def hasbeen_hooked(self): """ Update self.is_hooked. If new hooked update conditions relative to new price and time else return only hook """ self.logger.debug(f"is_hooked={self.is_hooked} and condition,\n " f"{self.condition.__repr__(short=False)}, " f"cond is hooked ? {self.condition.is_hooked()}, " f"hookedSrcID={self.condition.hookedSrcID}") if self.is_hooked: return True if self.condition.is_hooked(): self.logger.info(f"self.condition={self.condition}") self.is_hooked = True self.startTime = now() self.logger.debug(f"* *Before update* * {self.__repr__(False)}") self.update_cond_with_relative_values() self.logger.info( f"{self} is hooking with ID '{self.condition.hookedSrcID}'\n" f"_Newly Updated conditions_:\n " f"- from:\nself.init_cond={self.init_cond_frame}\n" f"- to:\n{self.condition.__repr__(False)}") return None
def update_timeleft(timeout=timeOut, starttime=startTime, now_=None): """ Update time left -now_: is sometime set by default. """ _now = now_ if now_ else now() # convert to seconds +/- _timeleft = (timeout + starttime - _now).delta / 10e8 return _timeleft if _timeleft > 0 else 0
def get_relative(self, genre_, op_): """Renvoie les nouvelles valeurs relative au prix et temps courant.""" relative_values = self.get_relative_values() if genre_ == "price": cPrice = self.get_current_price() return cPrice + relative_values[genre_][op_] elif genre_ == "temps": cTime = now() return cTime + relative_values[genre_][op_]
def evalue_une_condition(self, cond): """ Évalue une condition en fonction du genre. Les plus simple sont prix et temps. """ if cond.genre in self.price_list: current_price = self.brg.prices(cond.genre) return self.evalue(current_price, cond.op, cond.value) elif cond.genre in ["temps"]: return self.evalue(now(), cond.op, cond.value) elif cond.genre == "hook": return self.evalue_un_hook(cond)
def get_current_variation(self): """Renvois le % de var des prix moyen pour la dernière et avant dernière timeBin.""" if self.data is None: return 0, 0, 0 # on suppose que le data à les données nécessaire pour les calculs suivants # définit les mask avec les unité de temps Unit Time (UT) oneUTAgo = now() - pd.Timedelta(self.timeBin, "s") twoUTAgo = now() - pd.Timedelta(2 * self.timeBin, "s") currUT_mask = (self.data.date > oneUTAgo).values prevUT_mask = (self.data.date > twoUTAgo).values & list( not_array(currUT_mask)) mean_curr_price = self.data.loc[currUT_mask].refPrice.mean() mean_prev_price = self.data.loc[prevUT_mask].refPrice.mean() if any(pd.isna([mean_prev_price, mean_curr_price])): return 0, mean_prev_price, mean_curr_price else: return ( (mean_curr_price - mean_prev_price) / mean_prev_price * 100, mean_curr_price, mean_prev_price, )
def get_bucketed_trades( self, startDate=None, endDate=None, binSize="1m", extra_filter=None, count=100, columns=None, reverse=True, ): """Returns historical data bucketed by binSize - columns: timestamp, symbol, open, high, low, close, trades, volume, vwap,\ lastSize, turnover, homeNotional, foreignNotional, - count - option for bin = [1m,5m,1h,1d]. endDate and startDate format 2016-12-27T11:00Z (isoformat)""" if endDate is None: endDate = now().isoformat() if startDate is None: units = multiply_time_unit(24, TC[binSize]) startDate = (pd.Timestamp(dt.datetime.now() - pd.Timedelta(units)).round( TC[binSize]).isoformat()) path = "trade/bucketed" query = { "symbol": self.symbol, "binSize": binSize, "count": count, "startDate": startDate, "endDate": endDate, "reverse": reverse, } if columns is not None: query["columns"] = columns if extra_filter is not None: query["filter"] = extra_filter verb = "GET" if count < 750: sleep(HTTP_SIMPLE_RATE_LIMITE) trades = self._curl_bitmex(path, query=query, verb=verb) else: # paginer avec start et end date sleep(HTTP_SIMPLE_RATE_LIMITE) trades = self._curl_bitmex(path, query=query, verb=verb) return trades
def get_current_prices( self, price, refPrice, refTail=None, stopTail=None, flexTail=None, sOfsD=None, sOfsP=None, fOfsD=None, fScale=None, ): """Renvois les prix actuels pour remplir ou mettre à jour la df""" if refPrice is None: refPrice = self.get_refPrice() if refTail is None: refTail = self.get_refTail(refPrice) if stopTail is None: stopTail = refTail if sOfsD is None: # on pass refPrice car celui de la db is nan at this point sOfsD = self.get_stopTail_offset_delta(refPrice, stopTail) if sOfsP is None: sOfsP = self.get_stopTail_offset_per(refPrice, stopTail) if flexTail is None: flexTail = self.get_flexTail(refPrice, refTail) if fOfsD is None: fOfsD, fScale = self.flexTail_offset_delta(refPrice) current_prices = OrderedDict([ ("date", now()), ("price", price), ("refPrice", refPrice), ("stopTail", stopTail), ("refTail", refTail), ("flexTail", flexTail), ("sOfsD", sOfsD), ("fScale", fScale), ]) # # attention à l'ordre # current_prices = (now(), price, refPrice, stopTail, refTail, flexTail, sOfsD, # fOfsD, sOfsP, fScale) return pd.Series(current_prices)
def __init__( self, postOnly=POST_ONLY, live=False, symbol=SYMBOL, orderIDPrefix=ORDERID_PREFIX, timeout=TIMEOUT, logger=None, dbo=None, ): """Initialisation dbo is a dummy bitMEX object used for testing.""" self.logger = get_logger(logger, name=__name__, sLL="INFO") self.symbol = symbol self.precision = PRICE_PRECISION[symbol] self.last_check_time = now() - Timedelta( 2, unit="D") # on s'assure d'être dans le passé. self.cached_refPrices = None self.live = live if self.live and dbo is None: baseUrl, apiKey, apiSecret = LIVE_URL, LIVE_KEY, LIVE_SECRET else: baseUrl, apiKey, apiSecret = TEST_URL, TEST_KEY, TEST_SECRET if dbo: self.bto = dbo self.dbo = dbo else: # si on ne passe pas l'argument dummy BitMEX object # On en crée un réel self.bto = BitMEX( base_url=baseUrl, symbol=self.symbol, apiKey=apiKey, apiSecret=apiSecret, orderIDPrefix=orderIDPrefix, postOnly=postOnly, timeout=timeout, logger=self.logger, ) self.dbo = None self.logger.info(f"Fini init {self}")
def get_data(self, nomElt, temps="current", default_ret=None): """ Renvois un element du data si il existe. Si refPrice ou date demandé, envois des valeurs par défaut si n'a rien trouvé dans data """ if self.data is not None: elt = self.data.loc[temps].loc[nomElt] if not pd.isna(elt): return elt if nomElt == "refPrice": return self.refPrice elif nomElt == "date": return now() elif nomElt in ["stopTail", "flexTail"]: if default_ret is None: self.logger.warning("Attention default_ret is none and used") return default_ret else: expmsg = f"Check nomElt={nomElt}, temps={temps} et data={self.data}" raise Exception(expmsg)
def __init__( self, send_queue, order, cond, valid_queue=None, logger=None, nameT=None, timeout=None, symbol="XBTUSD", ): """ Une queue, pour passer les ordres, un ordre à passer si la condition est validée. - l'ordre (order) peut être stopé prématurement en mettant stop=True - order est un dict avec keys: side, orderQty.. - un timeout pendant lequel l'ordre (dont l'évaluation de sa condition) est actif def 2 jours - hook : nom du hook, ou de l'abbrevation qui sert de hook. - sLL= debug level -symbol: symbol for this order, def. XBTUSD """ Thread.__init__(self, name=nameT) self.logger = get_logger(logger, sLL="INFO", name=__name__) self.symbol = symbol self.send_queue = send_queue self.valid_queue = valid_queue self.condition = cond self.stop = False self.orderIDPrefix = "mlk_" self.order = order # a dict ex. {'side': 'buy', 'orderQty': 100, 'options'...} self.oclid = newClID(abbv_=nameT) self.order["clOrdID"] = self.oclid # default for timeOut (2 days) # could add a timecond eg cVraieTpsDiffA(timeOut.seconds or delta) self.timeOut = setdef_timedelta(timeout, pd.Timedelta(2, unit="D")) self.startTime = now()
def start_server(self): """Démarre les services.""" # canal échange ordre, serveur dispacheur self.fileDattente: Queue = Queue() # canal échange ordre, serveur dispacheur self.fileDeConfirmation: Queue = Queue() # connexion avec Bitmex self.brg: Bargain = Bargain(live=self.live, logger=self.logger, dbo=self.dbo, symbol=self.symbol) # Serveur dispacheur d'ordre self.chrs: Chronos = Chronos( self.brg, self.fileDattente, self.fileDeConfirmation, logger=self.logger, ) self.chrs.start() # Resultats financiers self.resultats.loc[now(), :] = (self.balance(), np.nan)
def elapsed_time(self): """Return the elapsed time in seconds.""" return now() - self.startTime
def prices(self, typeprice=None, side="buy", symbol_=None): """ Show summary of current prices. typeprice can be 'delta', 'indexPrice', 'market', 'askPrice', 'midPrice', 'ref_delta','market_maker', 'lastMidPrice' or None (for all instrument prices) - symbol if not user bargain symbol but maybe could want other price """ _symbol = self.symbol if symbol_ is None else symbol_ prices = { k: v for (k, v) in self.bto.instrument(_symbol).items() if "rice" in k } # prices.keys = 'maxPrice', 'prevClosePrice', 'prevPrice24h', 'highPrice', # 'lastPrice', 'lastPriceProtected', 'bidPrice', 'midPrice', 'askPrice', # 'impactBidPrice', 'impactMidPrice', 'impactAskPrice', 'markPrice', # 'markPrice', 'indicativeSettlePrice', 'lowPrice', # execInst, MarkPrice, LastPrice, IndexPrice ret = None typeprice = "" if typeprice is None else typeprice if typeprice == "delta": ret = prices["askPrice"] - prices["bidPrice"] elif typeprice.lower() == "indexprice": # S'assurer qu'il n'y a pas deux appels consécutif à moins de x seconde # minisytème de cache jusquà 11s avant nouvel appel au broker timeLaps = now() - self.last_check_time msg = (f'Checking cached price' f' timeLaps={timeLaps}, now={now()},' f' last_check_time={self.last_check_time}' f' self.cached_refPrices={self.cached_refPrices}') if (self.cached_refPrices is None or timeLaps > Timedelta(randint(2, 11), unit="s") or self.bto.dummy): cached_refPrices = self.get_most_recent_settlement_price() self.last_check_time = now() self.cached_refPrices = cached_refPrices msg += f'>>>> New Cached_refPrices={cached_refPrices} <<<<.' self.logger.debug(msg) ret = self.cached_refPrices elif typeprice.lower() == "lastprice": # askPrice > bidPrice ret = prices["askPrice"] if side == "buy" else prices["bidPrice"] elif typeprice == "market_maker": ret = prices["bidPrice"] if side == "buy" else prices["askPrice"] elif typeprice.lower() == "lastmidprice": ret = self.prices("midPrice") # this is close to the last price elif typeprice.lower() in ["market", "market_price", "markprice"]: # fairePrice is the marketPrice dans XBT check markMethod ret = prices["markPrice"] elif typeprice == "ref_delta": ret = self.prices("midPrice") - self.prices("indexPrice") elif typeprice: ret = prices[typeprice] return prices if not ret else round_sprice(ret, self.symbol)
def wait_for_change( self, valconditions=None, rcvload=None, timeout: pd.Timedelta = None, waitstep: float = 1, ): """ Boucle qui attend de recevoir dans order execution from ws. ordertype: orderstatus. - timeout doit être un pd.Timedelta, - waitStep en second ordertype is 'ordStatus our triggered et ordstatus is """ self.logger.debug("Thread started.") # defaults timeOut = setdef_timedelta(timeout, default=pd.Timedelta(60, unit="m")) clOrdID = (self.brg.bto.dummyID if self.brg.bto.dummy else self.get_ID_from(rcvload, "clOrdID")) startTime = now() def update_timeleft(timeout=timeOut, starttime=startTime, now_=None): """ Update time left -now_: is sometime set by default. """ _now = now_ if now_ else now() # convert to seconds +/- _timeleft = (timeout + starttime - _now).delta / 10e8 return _timeleft if _timeleft > 0 else 0 timeLeft = update_timeleft() while timeLeft > 0 and not self.is_changed_(clOrdID, valconditions): sleep(waitstep) timeLeft = update_timeleft() if timeLeft % 298 == 0: # logging every 4:58 self.logger.info(f"timeLeft={timeLeft}s. Still waiting...") sleep(1) # avoid too much logging and throwtlle the system # block until next reply reply = self.wait_for_reply(timeout=timeLeft) # problème avec les reply None if rcvload["order"]["ordType"] == "cancel": self.logger.info(f"ordType is cancel. rcvload={rcvload}") replyID = rcvload["order"]["clOrdID"] else: # mais si ce n'est pas la bonne reply ? replyID = (rcvload["order"]["clOrdID"] if reply is None else self.get_ID_from(reply, "clOrdID")) seenReplyIDs: Dict[Any, Any] = {} while replyID != clOrdID and timeLeft: # saving ID already seen to avoid clutering logs if replyID is not None: seenReplyIDs[replyID] = seenReplyIDs.get(replyID, 0) + 1 if seenReplyIDs.get(replyID, 0) < 2: self.logger.debug(f"*No match ID* for clOrdID={clOrdID}" f" in {trim_dic(reply, trimid=12)}!") if reply: # to be sure not to put Nones back in the loop self.reply_queue.put(reply) try: # can get None here, not good... if timeout.. reply = self.wait_for_reply(timeout=timeLeft) replyID = (clOrdID if reply is None else self.get_ID_from( reply, "clOrdID")) except Exception as e: self.logger.error( f"reply={reply}, timeLeft={timeLeft}, replyID={replyID}" f"clOrdID={clOrdID}") raise (e) timeLeft = update_timeleft() self.logger.debug( f"Getting out of the loop. seenReplyIds={seenReplyIDs}," f" timeLeft={timeLeft}, reply={reply}.") # ici il y a en fait le cas des reply error d'ammending validation = (reply if timeLeft and reply is not None and not reply.get("error", False) else False) self.logger.info( f"Attendu {timeOut - pd.Timedelta(timeLeft, unit='s')}." f"Validation is {bool(validation)}.") self.valid_queue.put({ "brokerReply": reply, "exgLoad": rcvload, "execValidation": validation })
def go( self, tps_run, prix, essais, side, q, tp, atype, oType=False, nameT=None, updatepause=None, logpause=None, dr_pause=None, tType=None, timeout=None, sDelta=None, hook=None, ): """ Loop over three conditions. - times interval in offset from now, - a prix interval in offset from now - and the number of essais. Will try to lauch a trailorder avec les même paramètres utiliser des nombres <0 si nécessaire - atype define if price and quantity are set in %. use the string %p%q to set price and/or q in % sinon le prix est définie en différentiel par rapport au indexPrice price q is in % of available margin in [0,100] - tp peut être en % (defaut), absolue ou différentielle ajouter tA ou tD and les atypes - le temps est en minutes - si présent dr_pause définie le temps d'attendre entre deux ordres - hook: un tuple (srcName: ordre sur lequel se hooker et s rcStatus, le satus que cette ordre doit avoir pour déclencher le hook srcName doit être un nom d'un autre ordre. La fin de src name est -S ou -P pour distinguer les ordres primaire ou secondaire. puis _ et un lettre pour le status eg. src-S_C """ # self.logger.info( # f"#### Go : tps_run={tps_run}, prix={prix}, essais={essais}," # f" side={side}, q={q}, tp={tp}, atype={atype}, oType={oType}," # f" tType={tType}, sDelta={sDelta}, dr_pause={dr_pause}m," # f" timeOut={timeout} m, balance={self.balance()}" # ) _info = { "tps_run": tps_run, "prix": prix, "essais": essais, "side": side, "q": q, "tp": tp, "atype": atype, "oType": oType, "tType": tType, "sDelta": sDelta, "dr_pause": dr_pause, "timeout": f"{timeout}m", "balance": self.balance(), } self.logger.info(f"#### Go with args :\n{pd.Series(_info)}") # Init. des paramètres temps pour la condition de validité de l'ocp self.tpsDeb = now() + pd.Timedelta(tps_run[0], unit="m") self.tpsFin = now() + pd.Timedelta(tps_run[1], unit="m") tfmt = "%Y-%m-%dT%H:%M" repTpsDeb = self.tpsDeb.strftime(tfmt) repTpsFin = self.tpsFin.strftime(tfmt) # #### Setting defaults #### # Infos sur la durée moyenne de l'essai attendue. if pd.isna(dr_pause): dr_pause = 0 dr_moy = (self.tpsFin - self.tpsDeb) / essais else: dr_moy = pd.Timedelta(dr_pause, unit="m") dr_essai_theo = dr_moy + pd.Timedelta(30, unit="s") if pd.isna(timeout): timeOut = (self.tpsFin - self.tpsDeb) / essais else: timeOut = pd.Timedelta(timeout, unit="m") if pd.isna(sDelta): sDelta = 2 # Expanding shortcut for ref price type: opType, ordType, execInst = price_type_trad( oType, side) # decl du main order. tpType, tOrdType, tExecInst = price_type_trad(tType, side) # decl du stop. # #### On commence la boucle qui va gérer le run. # i.e. la relance des ordres pendant la période de validité des conditions i = 0 while i < essais and not self.stop: self.tpsDebEssai = now() # on sauvegarde le temps de départ # Traitement des paramètres de l'ordre suivant atype self.logger.info( f"oType={opType, ordType, execInst}, tType={tpType, tOrdType, tExecInst}" ) oPrices, _q, _tp, tailPrices = set_order_args( prix, q, tp, atype, self.brg, opType, tpType, recompute=True, side=side, symbol=self.symbol, ) _info = { "Balance": f"{self.balance()}$", "tps_run": (repTpsDeb, repTpsFin), "début": self.tpsDebEssai, "pause": dr_moy, "hook": hook, "oPrices": oPrices, "tPrice": tailPrices, "side": side, "q": _q, "tp": round(_tp, 4), "sDelta": sDelta, "opType": (opType, ordType, execInst), "tpType": (tpType, tOrdType, tExecInst), "timeOut": timeOut, } self.logger.info( f"### Essais {i+1}/{essais}, ({nameT}):\n{pd.Series(_info)}") # L'order Price type (déclencheur pour Touched & stop) est déjà dans execInst order = create_order(side, _q, opType, ordType, execInst, oPrices, sDelta) # On initialise les arguments condition pour les ordres principaux kwargs = { "send_queue": self.fileDattente, "order": order, "cond": cVraieTpsDeA(self.brg, self.tpsDeb, self.tpsFin, logger=self.logger), "valid_queue": self.fileDeConfirmation, "logger": self.logger, "nameT": f"{nameT}-PO", "timeout": timeOut, "symbol": self.symbol, } self.logger.debug(f"~~~~ Order = {order}") if hook: _hSrc, _status = hook.split("_") _hStatus = ordStatusTrans[_status] # hook formé du 'nom-S_F' avec code ou nom-P_C eg. # <nom>-<src_secondair|src_principal>_<ordTargetStatus>-<SO|PO>id.. self.ocp = HookOrder( hSrc=_hSrc, hStatus=_hStatus, excludeIDs_=self.hookedIDs, **kwargs, ) # add hook conditions hookCond = cHook(self.brg, _hSrc, _hStatus, exclIDs=self.hookedIDs) self.ocp.add_condition(hookCond) self.ocp.condition.set_excludeClIDs(self.hookedIDs) else: self.ocp = OrderConditionned(**kwargs) self.logger.debug(f"~~~~ Order = {order}") # On ajoute la condition de prix # ou ["LastPrice", 'IndexPrice', 'MarkPrice'] self.ocp.add_condition( cVraiePrixDeA(self.brg, opType, oPrices[0], oPrices[1], logger=self.logger)) msg = f"### OrdrePrincipal défini ### {self.ocp.__repr__(short=False)}" self.logger.info(msg) # on accroche un stop (tail, trail) suiveur à l'ordre principal self.oct: TrailStop = TrailStop( self.ocp, self.brg, pegOffset_perc=_tp, updatepause=updatepause, logger=self.logger, logpause=logpause, nameT=f"{nameT}-SO", refPrice=tpType, execinst=tExecInst, ordtype=tOrdType, ) # on active le tout, à partir de la tail try: self.oct.start() self.oct.join() # il s'agit ensuite de bloquer jusqu'à execution du stoptrace. # cela nécessite, conditions de l'ocp et du stop validées except Exception as e: self.logger.warning("#### Exception %s. Stopping -->\n%s" % (e, self.oct.condition)) self.fin_des_essais("ERROR", close=False) break self.fin_essai( i, essais, close=False, dr_pause=dr_pause, dr_essai_theo=dr_essai_theo, ) # avant d'essaie aussi et s'assurer que le stop a bien été exécuté i += 1 if hook: # On sauvegarde les clOrdID qui on déclenché le hook self.hookedIDs |= set([self.ocp.condition.hookedSrcID]) self.logger.info(f">>>> Updating hookedIDs={self.hookedIDs}") self.fin_des_essais(essais, close=False) sys.exit()