def executeTrailingStop(self, looper, pos, wait=1200, noMore=5): """Bring an executed position (with tradeID) to be set with the expected trailing-stop parameters""" mtsd = looper.instrument.minimumTrailingStopDistance if (pos.tradeID is not None): distance = pos.trailingStopDesiredDistance if (distance < mtsd): distance = mtsd # price = (pos.trailingStopValue+distance*(1 if(pos.forBUY) else -1)) if(pos.trailingStopValue is not None) else pos.trailingStopTriggerPrice distance = self.nicepadding(distance, looper.displayPrecision) # price = self.nicepadding(price, looper.displayPrecision) tslargs = {"tradeID": str(pos.tradeID), "distance": distance} corelog.debug(tslargs) respTSL = None if (pos.trailingStopLossOrderId is None): respTSL = looper.api.order.trailing_stop_loss( looper.accountId, **tslargs) else: # corelog.debug( "replace order {}", pos.trailingStopLossOrderId) respTSL = looper.api.order.trailing_stop_loss_replace( looper.accountId, pos.trailingStopLossOrderId, **tslargs) #corelog.debug("status code:{}\nbody:{}",respTSL.status, respTSL.body) if (str(respTSL.status) == '201'): time.sleep(float(wait) / 1000.0) looper.refreshPositions(self, True) else: raise RuntimeError( "cannot call executeTrailingStop on a position that has not been entered/traded" )
def getBacktrackingCandles(loopr, highCount, highSlice, lowSlice, lowAheadOfHigh=True): """ get high and low slice candles from OANDA api. This will provide the number of highCount high-slice candles and the low-slice candles in between. See getCachedBacktrackingCandles for details on other parameters""" highKW = {"count": highCount, "price": "BA", "granularity": highSlice} lowKW = {"price": "BA", "granularity": lowSlice} xoff = 1 if (not lowAheadOfHigh) else 0 corelog.debug(highKW) highCandles = getSortedCandles(loopr, highKW) # when not lowAheadOfHigh starts with an empty backtracking list, otherwise starts with single high candle and no low candles backtracking = [(highCandles[0], [])] if (not lowAheadOfHigh) else [] for i in range(len(highCandles) - xoff): a = highCandles[i] lowKW["fromTime"] = a.time if (i + 1 < len(highCandles)): b = highCandles[i + 1] lowKW["toTime"] = b.time corelog.debug(lowKW) lowCandles = getSortedCandles(loopr, lowKW) hc = highCandles[i + xoff] item = (hc, lowCandles) backtracking.append(item) return backtracking
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
def refreshPositions(self, positionFactory, force=False): self.refresh(force) freshPositions = [] for pos in self.findPositionsRaw(): tradeIDs = (pos.long.tradeIDs if(pos.long.tradeIDs is not None)else pos.short.tradeIDs) if(tradeIDs is not None): for tradeID in tradeIDs: ppos = positionFactory.makeFromExistingTrade(self.mkCandlestickTemplate(), self.account, tradeID) freshPositions.append(ppos) else: corelog.debug("(trade less position)") # make sure the position array is sorted by openTime freshPositions.sort(lambda a,b: cmp(a.entryQuote.time, b.entryQuote.time)) self.positions = freshPositions
def executeClose(self, looper, pos, size=None, wait=1200, noMore=5): """ when the close executes, this returns a pair (position, trade-id). If the position is actually closed, then the position is returned as None, so what is returned is (None, trade-id). If the close fails, None is returned """ kwargs = {} if (size is not None): fsize = float(size) if (pos.size > fsize): kwargs["units"] = str(size) else: raise ValueError( "Cannot use executeClose to with a size greater than current position: {} is great than current {}" .format(size, pos.size)) response = looper.api.trade.close(looper.accountId, pos.tradeID, **kwargs) if (response.status.code == "200" or response.status.code == 200): corelog.debug("Closing trade {} with {} was successful".format( pos.tradeID, kwargs)) else: corelog.critical("Unable to close trade: {}\n{}".format( response.status, response.body)) return None myTrade = None while (myTrade is None and noMore > 0): time.sleep(wait / 1000.0) looper.refreshPositions(self, force=True) myTrades = [ t for t in looper.account.positions if t.id == pos.tradeID ] if (len(myTrades) > 0): if (float(myTrades[0].currentUnits) < float(pos.size)): myTrade = myTrades[0] else: if (size is None): # the position is not on the account (not open, but gone, closed) and we didn't pass a size, so it is all good. break else: noMore -= 1 if (myTrade is not None): newPosVersion = self.makeFromExistingTrade(pos.entryQuote, looper.account, myTrade.id) return newPosVersion, myTrade.id else: return None, pos.tradeID