def cHook(brg, hSrc, srcStatus="Filled", exclIDs=[]): """ Créer la condtion Hook. le genre hook, hSrcva servir à récupérer le clOrdID au moment de l'éval status est l'état cherché pour le hook """ _cond = Condition(brg, ("hook", hSrc, srcStatus)) # ajoute la liste des ids à exclure _cond = _cond.set_excludeClIDs(exclIDs) _cond.excludeIDs = exclIDs return _cond
def place_tailstop(self): """ Appelé une fois l'ordre principal déclanché. On enclenche alors une trace par rapport à un prix de référence. On crée pour cela un objet prix qui suivra l'évolution via le broker. On envois l'ordre (le stop) au broker et on le met à jour ensuite un tailstop c'est : - ordType: un parmi 'Stop', 'StopLimit', 'MarketIfTouched', 'LimitIfTouched', - orderQty - stopPx - execInst: parmi ParticipateDoNotInitiate; MarkPrice, LastPrice, IndexPrice; ReduceOnly; Close. Renvois le résultat de OC.send_order """ mPrice = self.brg.prices(self.mPrice_type, self.side) refPrice = self.brg.prices(self.refPrice_type, self.side) # l'offset du prix au départ est nulle self.PO = PriceObj( mPrice, refPrice, tail_perct_init=self.set_tail_strategy(), head=toggle_sides(self.side), updatepause=self.updatePause, timeBin=self.timeBin, logger=self.logger, symbol=self.symbol, ) # on met à jour la condition car déjà crée dans OCT self.tailStop_triggerCond = Condition( self.brg, (self.refPrice_type, self.op, self.PO.data.stopTail.current)) # Nous avons juste besoin de mettre à jour le stopPx que nous ne connaissions pas # au moment de l'initialisation if "Limit" in self.order["ordType"]: # on passe aussi un prix self.order["price"] = self.PO.data.stopTail.current self.order["stopPx"] = setdef_stopPrice(self.order["price"], self.order["side"], absdelta=1) else: self.order["stopPx"] = self.PO.data.stopTail.current self.logger.info( f"Sending {self.__repr__(short=False, cond=True)}\n{self.PO}") return self.send_order()
def cVraieTpsDeA(brg, tpsDeb, tpsFin, logger=None): """Condition vraie pour un temps compris de tpsDeb A tpsFin.""" if tpsDeb > tpsFin: raise Exception("tps départ(%s) > tps d'arrivé (%s)" % (tpsDeb, tpsFin)) return Condition( brg, (("temps", ">", tpsDeb), ("temps", "<", tpsFin)), logger=logger )
def cVraieTpsDiffDeA(brg, x, y): """Condition vraie pour un temps entre maintenant +x min et maintenant + y min.""" tpsDeb = tpsDans(x) tpsFin = tpsDans(y) if tpsDeb > tpsFin: raise Exception("tps départ(%s) > tps d'arrivé (%s)" % (tpsDeb, tpsFin)) return Condition(brg, (("temps", ">", tpsDeb), ("temps", "<", tpsFin)))
def cVraiePrixDeA(brg, price_type, prixInf, prixSup, logger=None): """Condition vraie pour un prix compris entre x usd et y usd.""" assert prixInf < prixSup, f"prix départ({prixInf}) > prix d'arrivé ({prixSup})" # logging.warning(f'Setting Price type={price_type}, inf {prixInf}, sup {prixSup}') return Condition( brg, ((price_type, "<", prixSup), (price_type, ">", prixInf)), logger=None )
def cVraiePrixDiffDeA(brg, price_type, x, y): """ Condition vraie pour un prix entre market +x et market + y. x et y peuvent être <0 """ basePrice = brg.prices(price_type) prixInf = basePrice + x prixSup = basePrice + y if prixInf > prixSup: raise Exception("prix départ(%s) > prix d'arrivé (%s)" % (prixInf, prixSup)) return Condition(brg, ((price_type, "<", prixSup), (price_type, ">", prixInf)))
def __init__( self, main_oc, brg, pegOffset_perc=0.5, updatepause=None, logger=None, logpause=60, nameT=None, refPrice=None, execinst=None, ordtype=None, ): """Une queue, pour passer les ordres, un ordre à passer si la cond validée. - l'ordre peut être stopé prématurement en mettant stop=True - l'ordre doit être un buy ou sell at market - pegOffset_perc in [0, 100] % du prix de ref pour la trace - updatepause, c'est le temps moyen à attendre entre deux mise-à-jour """ self.logger = get_logger(logger, sLL="INFO", name=__name__) self.main_oc = main_oc self.brg = brg self.pegOffset_perc = pegOffset_perc self.ordType = ordtype self.symbol = self.main_oc.symbol # trouver une façon de définir ça on the fly basé la volatilité self.timeBin = 361 # le temps en seconde à attendre (approx.) avant de logger les prix # def. log les prix toutes les minutes self.logPause = 60 if logpause is None else logpause self.updatePause = 0.2 if updatepause is None else updatepause assert ( self.logPause >= self.updatePause ), f"Trying to log ({self.logPause}) more often than update ({self.updatePause})" # Initialisation de la condition de trace self.mPrice_type = "LastPrice" # markPRICE IS THE FAIRE PRICE != marketPrice (~lastprice) # refPrice contient 'IndexPrice', 'MarkPrice' ou 'LastPrice' if not refPrice or refPrice == "lastMidPrice": self.refPrice_type = "LastPrice" else: self.refPrice_type = refPrice # On définie la vente ou l'achat en fonction du prix. # La trace est aussi un ordre Conditionné mais de type Stop. # On l'initialise. self.execInst = opt_add_to_(execinst, self.refPrice_type) self.side = toggle_sides(self.main_oc.order["side"]) self.init_order = { "orderQty": self.main_oc.order["orderQty"], "side": self.side, "execInst": self.execInst, "ordType": self.ordType, } # Création d'une condtion stop pour le suivi du stop, # même si le déclenchement se fera automatiquement sur le marché if "sell" in self.side: self.op = "<" args = (self.refPrice_type, "<", -1) else: self.op = ">" args = (self.refPrice_type, ">", 1e9) self.tailStop_triggerCond = Condition(self.brg, args, logger=self.logger) OrderConditionned.__init__( self, # s'assurer que si l'op n'est plus, la queue reste send_queue=self.main_oc.send_queue, order=self.init_order, cond=self.main_oc.condition, valid_queue=self.main_oc.valid_queue, logger=self.logger, nameT=nameT, symbol=self.main_oc.symbol, )
class TrailStop(OrderConditionned): """ Object TrailStop. Permet de faire un ordre avec trace, et lancement conditionné ance un order conditionné simple puis lorsqu'éxécuté lance sa trace, tail ou tail, met à jour la condition pour la trace """ def __init__( self, main_oc, brg, pegOffset_perc=0.5, updatepause=None, logger=None, logpause=60, nameT=None, refPrice=None, execinst=None, ordtype=None, ): """Une queue, pour passer les ordres, un ordre à passer si la cond validée. - l'ordre peut être stopé prématurement en mettant stop=True - l'ordre doit être un buy ou sell at market - pegOffset_perc in [0, 100] % du prix de ref pour la trace - updatepause, c'est le temps moyen à attendre entre deux mise-à-jour """ self.logger = get_logger(logger, sLL="INFO", name=__name__) self.main_oc = main_oc self.brg = brg self.pegOffset_perc = pegOffset_perc self.ordType = ordtype self.symbol = self.main_oc.symbol # trouver une façon de définir ça on the fly basé la volatilité self.timeBin = 361 # le temps en seconde à attendre (approx.) avant de logger les prix # def. log les prix toutes les minutes self.logPause = 60 if logpause is None else logpause self.updatePause = 0.2 if updatepause is None else updatepause assert ( self.logPause >= self.updatePause ), f"Trying to log ({self.logPause}) more often than update ({self.updatePause})" # Initialisation de la condition de trace self.mPrice_type = "LastPrice" # markPRICE IS THE FAIRE PRICE != marketPrice (~lastprice) # refPrice contient 'IndexPrice', 'MarkPrice' ou 'LastPrice' if not refPrice or refPrice == "lastMidPrice": self.refPrice_type = "LastPrice" else: self.refPrice_type = refPrice # On définie la vente ou l'achat en fonction du prix. # La trace est aussi un ordre Conditionné mais de type Stop. # On l'initialise. self.execInst = opt_add_to_(execinst, self.refPrice_type) self.side = toggle_sides(self.main_oc.order["side"]) self.init_order = { "orderQty": self.main_oc.order["orderQty"], "side": self.side, "execInst": self.execInst, "ordType": self.ordType, } # Création d'une condtion stop pour le suivi du stop, # même si le déclenchement se fera automatiquement sur le marché if "sell" in self.side: self.op = "<" args = (self.refPrice_type, "<", -1) else: self.op = ">" args = (self.refPrice_type, ">", 1e9) self.tailStop_triggerCond = Condition(self.brg, args, logger=self.logger) OrderConditionned.__init__( self, # s'assurer que si l'op n'est plus, la queue reste send_queue=self.main_oc.send_queue, order=self.init_order, cond=self.main_oc.condition, valid_queue=self.main_oc.valid_queue, logger=self.logger, nameT=nameT, symbol=self.main_oc.symbol, ) def __repr__(self, tracing=False, short=True, cond=False): ret = f"----TrailStop" ret += f"{OrderConditionned.__repr__(self, short=short, suffix=' of ')}" if cond: ret += f"\n----TailTrigger{self.tailStop_triggerCond}" if tracing: ret += f"\n----Tracing: {self.main_oc}" return ret def run(self): """Tourne jusqu'à un stop explicite où la condition se réalise.""" self.logger.info(f"#### Starting {self}") try: # on s'assure que le main order est bien démarré self.main_oc.start() except RuntimeError as re: # Il se peut qu'il soit déjà démarré # cas d'un rattachement à un ordre existant if "can only" in re.__repr__(): self.logger.exception( f"{self.main_oc} already started... continue") pass # On bloque jusqu'à la fin de l'exécution du main_oc. # note: il est peut être déjà fini self.logger.info( f"Waiting for {self} to effectively join{self.main_oc}") try: self.main_oc.join() except Exception: self.logger.exception(f"Joined, {self.main_oc} was already done") # Ici main_oc doit avoir été correctement executé ou canceled (check Chronos) if self.main_oc.timed_out(): self.logger.info( f">>>> {self.main_oc} Timed out. This cancels the tail <<<<") else: # on met en place la condition pour la tail et on lance l'update self.logger.info(f"#### Placing stop {self}") reply = self.place_tailstop() orderRef = get_order_from(reply) sleep(0.12) # laisser le temps de se mettre à jour? self.logger.info(f"Tail{self.main_oc} en place! Reply: {orderRef}") i = 0 while not self.stop: # on met à jour la condition de tail_oc # si la mise à jour (amend) échoue, newOrder sera False newOrder = self.amend_stop_price( reply["orderID"]) if reply else reply # Si condition sortir de la boucle. if not newOrder or self.tailStop_triggerCond.is_(True): # Chronos vérifie l'execution logmsg = ( f"Fin de Trace: newOrder ({newOrder}) is False " f"_ou_\n{self.tailStop_triggerCond.__repr__(short=False)}" ) self.logger.info(logmsg) break if self.PO.new_current_stopTail(): reply = newOrder # On attend un nombre variable de seconds # ne pas descendre sous 6s sinon risque d'être rejeté par le broker pause = self.updatePause + rnd.random() * 4 - 2 pause = pause if pause > 0 else 0 sleep(pause) i += 1 # si int(60), log environs toutes les i * logPause if i % int(self.logPause / self.updatePause) == 0: self.logger.info(f"{self.__repr__(cond=True)}\n{self.PO}") # On sort de la boucle si stop et true ou condition met # et finalise (..méthode héritée) self.finalise(False) def place_tailstop(self): """ Appelé une fois l'ordre principal déclanché. On enclenche alors une trace par rapport à un prix de référence. On crée pour cela un objet prix qui suivra l'évolution via le broker. On envois l'ordre (le stop) au broker et on le met à jour ensuite un tailstop c'est : - ordType: un parmi 'Stop', 'StopLimit', 'MarketIfTouched', 'LimitIfTouched', - orderQty - stopPx - execInst: parmi ParticipateDoNotInitiate; MarkPrice, LastPrice, IndexPrice; ReduceOnly; Close. Renvois le résultat de OC.send_order """ mPrice = self.brg.prices(self.mPrice_type, self.side) refPrice = self.brg.prices(self.refPrice_type, self.side) # l'offset du prix au départ est nulle self.PO = PriceObj( mPrice, refPrice, tail_perct_init=self.set_tail_strategy(), head=toggle_sides(self.side), updatepause=self.updatePause, timeBin=self.timeBin, logger=self.logger, symbol=self.symbol, ) # on met à jour la condition car déjà crée dans OCT self.tailStop_triggerCond = Condition( self.brg, (self.refPrice_type, self.op, self.PO.data.stopTail.current)) # Nous avons juste besoin de mettre à jour le stopPx que nous ne connaissions pas # au moment de l'initialisation if "Limit" in self.order["ordType"]: # on passe aussi un prix self.order["price"] = self.PO.data.stopTail.current self.order["stopPx"] = setdef_stopPrice(self.order["price"], self.order["side"], absdelta=1) else: self.order["stopPx"] = self.PO.data.stopTail.current self.logger.info( f"Sending {self.__repr__(short=False, cond=True)}\n{self.PO}") return self.send_order() def amend_stop_price(self, orderRefID): """ Mise à jour du prix pour la condition de déclenchement (ie la trace). nécessite l'ID de l'ordre à changer (orderRefID) """ # maj des tails en fonction des stratégies # self.PO.tail_perct = self.PO.set_tail_strategy() # maj des prix self.PO.update_to( price=self.brg.prices(self.mPrice_type, self.side), # mPrice refPrice=self.brg.prices(self.refPrice_type, self.side), ) # refPrice # maj de la condition # self.logger.debug(f'theoretical stopPx={self.refPrice_type, self.PO.data.flexTail.current}') self.tailStop_triggerCond.set_price_val(self.refPrice_type, self.PO.data.stopTail.current) # on ne met à jour le StopPx que s'il y a un changement try: if self.PO.new_current_stopTail(): # il y a un amend (modif) à faire on envois l'ordre à chronos self.order["orderID"] = orderRefID self.order["newPrice"] = self.PO.data.stopTail.current self.order["ordType"] = self.amend_order_type(self.ordType) self.logger.info( f"*** *New stopTail: {self.PO.data.stopTail.previous}" f" --> {self.PO.data.stopTail.current}* ***\n" f"Order to amend is {self.order}\n{self.PO}") return self.send_order() else: # sinon pas de changement, on continue de suivre orderRefID return {"orderID": orderRefID} except Exception as e: # doit être géré par chronos # 'message': 'Invalid ordStatus # requests.exceptions.HTTPError self.logger.error( f"amend_stop_price raise {e.__repr__()}, type:{type(e)}") if "Service Unavailable" in e.__repr__(): sleep(API_ERROR_INTERVAL) return True elif "Invalid ordStatus" in e.__repr__(): # c'est probablement que l'on veut modifier un stop # qui s'est déclanché et qui n'existe plus. # on doit sortir de la boucle return False else: raise (e) def amend_order_type(self, ordertype): """Add amend prefix to ordtype if not already there""" return ordertype if ordertype.startswith( "amend") else f"amend{ordertype}" def set_tail_strategy(self): """Définie la longueur de la trace en pourcentage. Utilise pour cela la position des prix du marché (midPrice) par rapport à ceux de référecence (indexPrice). L'hypothèse est que le marché tends vers le prix de ref. Aussi il s'agit de détecter les contre courant. Nous suivant le prix de réf (indexPrice) donc lui a tendance à aller vers le marché 1) Si je suis à l'achat mais que le indexPrice est > au marché alors on allonge la trace du pourcentage la différence |midprice - indexPrice| par rapport au indexPrice. Sinon en garde le pourcentage d'initialisation 2) A contrario si je suis en vente mais que le indexPrice est < au marché alors on alonge la trace du pourcentage la différence |midprice - indexPrice| par rapport au maché. Sinon status quo.""" return self.pegOffset_perc
def cVraiePrixInf(brg, price_type, prixSup): """Condition vraie pour un prix inférieur à prixSup.""" return Condition(brg, (price_type, "<", prixSup))
def cVraiePrixSup(brg, price_type, prixInf): """Condition vraie pour un prix supérieur à prixInf.""" return Condition(brg, (price_type, ">", prixInf))
def cVraiePrixDiffInf(brg, price_type, x): """Condition vraie pour un prix inférieur à market + x. x peut être négatif.""" basePrice = brg.prices(price_type) prixSup = basePrice + x return Condition(brg, (price_type, "<", prixSup))
def cVraieTpsDiffDe(brg, x): """Condition Vraie pour un temps de > now +x.""" return Condition(brg, ("temps", ">", tpsDans(x)))
def cVraiePrixDiffSup(brg, price_type, x): """Condition vraie pour un prix supérieur à market +x. x peut être négatif.""" basePrice = brg.prices(price_type) prixInf = basePrice + x return Condition(brg, (price_type, ">", prixInf))
def cVraieTpsA(brg, tpsFin): """Condition Vraie un temps jusqu'à < tpsFin.""" return Condition(brg, ("temps", "<", tpsFin))
def cVraieTpsDiffA(brg, x): """Condition Vraie un temps jusqu'à < now +x.""" return Condition(brg, ("temps", "<", tpsDans(x)))
def cVraieTpsDe(brg, tpsDeb): """Condition Vraie pour un temps de > tpsDeb.""" return Condition(brg, ("temps", ">", tpsDeb))