def updateTrailingStopValue(self, currentQuote): """Based on current trailing stop value, and on trailing stop distance, this may change the trailing stop value according to the current-quote. Example: trailing stop is at 100, trailing stop distance is 10, on a BUY order, then the stop value should increase as soon the current-quote goes abouve 110. This is intended to simulate the change in trailing-stop value that the Broker is supposed to maintain. """ avgPrice = self.relevantPrice(currentQuote) if (self.forBUY): if (self.trailingStopValue is not None and self.trailingStopValue + self.trailingStopDistance < avgPrice): self.trailingStopValue = avgPrice - self.trailingStopDistance corelog.info( "updated trailing stop value to %f with distance %f", self.trailingStopValue, self.trailingStopDistance) else: if (self.trailingStopValue is not None and self.trailingStopValue - self.trailingStopDistance > avgPrice): self.trailingStopValue = avgPrice + self.trailingStopDistance corelog.info( "updated trailing stop value to %f with distance %f", self.trailingStopValue, self.trailingStopDistance)
def trailSpecShrink(self, entryQuote, currentQuote, trailSpecs): if(trailSpecs is None or len(trailSpecs)==0): return trailSpecs # do nothing when there is nothing to do eqt = dateutil.parser.parse(entryQuote.time) cqt = dateutil.parser.parse(currentQuote.time) deltaT = cqt-eqt periods = deltaT.total_seconds() / self.frequency if(periods>1): periods = round(periods) af = self.factor ** periods import math afTrailSpecs = map(lambda s: (math.ceil(af*s[0]), math.ceil(af*s[1]) ), trailSpecs) afTrailSpecs.sort(lambda x,y: cmp(x[0],y[0])) # print(afTrailSpecs) ntrailSpecs = [ afTrailSpecs[0] ] for n in range(len(afTrailSpecs)): if(n>0): cc = afTrailSpecs[n] ll = ntrailSpecs[-1] if(ll[0] == cc[0]): # same trigger, choose the bigger distance if(ll[1]< cc[1]): ntrailSpecs[-1] = cc elif(ll[0] > cc[0]): #later trigger is smaller, not possible, do nothing pass elif(ll[0] < cc[0]): if(ll[1]>cc[1]): ntrailSpecs.append(cc) corelog.info("trailSpecs shrunk with factor " + str(af)) return ntrailSpecs else: return trailSpecs
def calibrateTrailingStopLossDesireForSteppedSpecs(self, currentQuote, trailSpecs, mspread, minimumTrailingStopDistance, trailingShrinker = None): """ calibrate the Position trailing stop details for the new current quote. This may change the trigger price, and desired distance based on current-quote (latest reading from the market) and the trailing-stop-specc/steps, also based on current median-spread. If a change is deemed necessary, it is made and the property trailingStopNeedsReplacement is set to True. This does NOT change the trailingStopValue (it is changed by 'updateTrailingStopValue') TrailSpecs and mspread can be passed as None if you just want to use the latest calibrated values for trailSpecs and mspread currentquote can be passed as None to calibrate the position at the very beginning.""" if(currentQuote is None and (trailSpecs is None or mspread is None)): raise ValueError("need either curentQuote or trailSpecs + mspread") if(mspread is None): mspread = self.medianSpread else: self.medianSpread = mspread self.minimumTrailingStopDistance = minimumTrailingStopDistance plusMinus = 1.0 if(self.forBUY) else -1.0 if(currentQuote is None): self.trailingStopDesiredDistance = mspread*trailSpecs[0][1] self.trailingStopTriggerPrice = mspread*trailSpecs[0][0]* plusMinus + (self.entryPrice()) self.trailSpecs = trailSpecs return if(trailSpecs is None): trailSpecs = self.trailSpecs currentDistance = self.trailingStopDistance unitProfit = self.quoteProfit(currentQuote, True) if(unitProfit>0.0): #print("unitProfit: " + str(unitProfit) + " = " + str(unitProfit/mspread) + " x median-spread") if(trailingShrinker is not None): trailSpecs = trailingShrinker(self.entryQuote, currentQuote, trailSpecs) okSpec = None for spec in trailSpecs: if(unitProfit>mspread*spec[0] and (currentDistance is None or mspread*spec[1]<currentDistance)): okSpec = spec elif(okSpec is not None): break if(okSpec is None): return newDesiredDistance = round(mspread*okSpec[1],7) if(newDesiredDistance<minimumTrailingStopDistance): newDesiredDistance = minimumTrailingStopDistance if(newDesiredDistance < self.trailingStopDesiredDistance or self.trailingStopDesiredDistance<=0): corelog.info("new desired distance {} spreads = {}".format(okSpec[1], newDesiredDistance)) self.trailingStopDesiredDistance = newDesiredDistance self.trailingStopNeedsReplacement = True if(self.trailingStopDistance is not None): # then trailing stop is already engage, note a needed replacement only if trailingStopDistance is different than new distance, the moving stop value is taken care by the broker self.trailingStopNeedsReplacement = (newDesiredDistance != round(self.trailingStopDistance,7)) corelog.info("current trailing stop distance:%s, new desired distance:%s, replacement-needed:%s", self.trailingStopDistance, newDesiredDistance, self.trailingStopNeedsReplacement) return
def executeTrade(self, looper, pos,wait=1200,noMore=5): """Execute a trade for a position, and a looper. On success, returns a 2-tuple: position object identified after successful trade, trade id When trade-id is None, the position object is the same as originally thought, the trade hasn't happened yet On failure, the 2-tuple is not returned, but None is returned. Some other issue at the broker (or with the order) have occurred""" kwargs = {} kwargs['instrument'] = looper.instrumentName # kwargs['price']=pos.entryQuote.ask.o if(pos.forBUY) else pos.entryQuote.bid.o kwargs['units']=(pos.size if(pos.forBUY) else -pos.size) #kwargs['timeInForce']='GTC' # saveLoss / takeProfit - user minimal minimumTrailingStopDistance to not annoy the broker mtsd = looper.instrument.minimumTrailingStopDistance sl = pos.saveLoss;tp=pos.takeProfit def nicepadding(x,prec): x = str(x) if(x.find(".")>0): x += "00000" return x[0:(x.index(".")+1+prec)] return x kwargs['stopLossOnFill'] = {"price": nicepadding(sl, looper.displayPrecision)} kwargs['takeProfitOnFill'] = {"price": nicepadding(tp, looper.displayPrecision)} corelog.debug(kwargs) response = looper.api.order.market( looper.accountId, **kwargs ) if(not(response.status == 201 or response.status == '201')): corelog.critical( "Position / Trade could not be executed...") corelog.critical(response.body) else: newTrades =[] prevTradeIDs = map(lambda t: t.id, looper.account.trades) while(len(newTrades)==0): time.sleep(wait/1000.0) looper.refreshPositions(self, force=True) newTrades = [ t for t in looper.account.trades if t.id not in prevTradeIDs and t.instrument == looper.instrumentName ] if(len(newTrades)==0): noMore -= 1 if(noMore>0): corelog.info("new trade not executed yet - waiting again...") else: corelog.warning("new trade not executed yet - but continuing...") return pos, None newPos = self.makeFromExistingTrade(pos.entryQuote, looper.account, newTrades[0].id) # if(pos.trailingSpecs is not None): # newPos.calibrateTrailingStopLossDesireForSteppedSpecs(pos.traillingSpecs, return newPos, newTrades[0].id return None