Path: blob/master/src/trademgmt/TradeManager.py
301 views
import os1import logging2import time3import json4from datetime import datetime56from config.Config import getServerConfig7from core.Controller import Controller8from ticker.ZerodhaTicker import ZerodhaTicker9from trademgmt.Trade import Trade10from trademgmt.TradeState import TradeState11from trademgmt.TradeExitReason import TradeExitReason12from trademgmt.TradeEncoder import TradeEncoder13from ordermgmt.ZerodhaOrderManager import ZerodhaOrderManager14from ordermgmt.OrderInputParams import OrderInputParams15from ordermgmt.OrderModifyParams import OrderModifyParams16from ordermgmt.Order import Order17from models.OrderType import OrderType18from models.OrderStatus import OrderStatus19from models.Direction import Direction2021from utils.Utils import Utils2223class TradeManager:24ticker = None25trades = [] # to store all the trades26strategyToInstanceMap = {}27symbolToCMPMap = {}28intradayTradesDir = None29registeredSymbols = []3031@staticmethod32def run():33if Utils.isTodayHoliday():34logging.info("Cannot start TradeManager as Today is Trading Holiday.")35return3637if Utils.isMarketClosedForTheDay():38logging.info("Cannot start TradeManager as Market is closed for the day.")39return4041Utils.waitTillMarketOpens("TradeManager")4243# check and create trades directory for today`s date44serverConfig = getServerConfig()45tradesDir = os.path.join(serverConfig['deployDir'], 'trades')46TradeManager.intradayTradesDir = os.path.join(tradesDir, Utils.getTodayDateStr())47if os.path.exists(TradeManager.intradayTradesDir) == False:48logging.info('TradeManager: Intraday Trades Directory %s does not exist. Hence going to create.', TradeManager.intradayTradesDir)49os.makedirs(TradeManager.intradayTradesDir)5051# start ticker service52brokerName = Controller.getBrokerName()53if brokerName == "zerodha":54TradeManager.ticker = ZerodhaTicker()55#elif brokerName == "fyers" # not implemented56# ticker = FyersTicker()5758TradeManager.ticker.startTicker()59TradeManager.ticker.registerListener(TradeManager.tickerListener)6061# sleep for 2 seconds for ticker connection establishment62time.sleep(2)6364# Load all trades from json files to app memory65TradeManager.loadAllTradesFromFile()6667# track and update trades in a loop68while True:69if Utils.isMarketClosedForTheDay():70logging.info('TradeManager: Stopping TradeManager as market closed.')71break7273try:74# Fetch all order details from broker and update orders in each trade75TradeManager.fetchAndUpdateAllTradeOrders()76# track each trade and take necessary action77TradeManager.trackAndUpdateAllTrades()78except Exception as e:79logging.exception("Exception in TradeManager Main thread")8081# save updated data to json file82TradeManager.saveAllTradesToFile()8384# sleep for 30 seconds and then continue85time.sleep(30)86logging.info('TradeManager: Main thread woke up..')8788@staticmethod89def registerStrategy(strategyInstance):90TradeManager.strategyToInstanceMap[strategyInstance.getName()] = strategyInstance9192@staticmethod93def loadAllTradesFromFile():94tradesFilepath = os.path.join(TradeManager.intradayTradesDir, 'trades.json')95if os.path.exists(tradesFilepath) == False:96logging.warn('TradeManager: loadAllTradesFromFile() Trades Filepath %s does not exist', tradesFilepath)97return98TradeManager.trades = []99tFile = open(tradesFilepath, 'r')100tradesData = json.loads(tFile.read())101for tr in tradesData:102trade = TradeManager.convertJSONToTrade(tr)103logging.info('loadAllTradesFromFile trade => %s', trade)104TradeManager.trades.append(trade)105if trade.tradingSymbol not in TradeManager.registeredSymbols:106# Algo register symbols with ticker107TradeManager.ticker.registerSymbols([trade.tradingSymbol])108TradeManager.registeredSymbols.append(trade.tradingSymbol)109logging.info('TradeManager: Successfully loaded %d trades from json file %s', len(TradeManager.trades), tradesFilepath)110111@staticmethod112def saveAllTradesToFile():113tradesFilepath = os.path.join(TradeManager.intradayTradesDir, 'trades.json')114with open(tradesFilepath, 'w') as tFile:115json.dump(TradeManager.trades, tFile, indent=2, cls=TradeEncoder)116logging.info('TradeManager: Saved %d trades to file %s', len(TradeManager.trades), tradesFilepath)117118@staticmethod119def addNewTrade(trade):120if trade == None:121return122logging.info('TradeManager: addNewTrade called for %s', trade)123for tr in TradeManager.trades:124if tr.equals(trade):125logging.warn('TradeManager: Trade already exists so not adding again. %s', trade)126return127# Add the new trade to the list128TradeManager.trades.append(trade)129logging.info('TradeManager: trade %s added successfully to the list', trade.tradeID)130# Register the symbol with ticker so that we will start getting ticks for this symbol131if trade.tradingSymbol not in TradeManager.registeredSymbols:132TradeManager.ticker.registerSymbols([trade.tradingSymbol])133TradeManager.registeredSymbols.append(trade.tradingSymbol)134# Also add the trade to strategy trades list135strategyInstance = TradeManager.strategyToInstanceMap[trade.strategy]136if strategyInstance != None:137strategyInstance.addTradeToList(trade)138139@staticmethod140def disableTrade(trade, reason):141if trade != None:142logging.info('TradeManager: Going to disable trade ID %s with the reason %s', trade.tradeID, reason)143trade.tradeState = TradeState.DISABLED144145@staticmethod146def tickerListener(tick):147# logging.info('tickerLister: new tick received for %s = %f', tick.tradingSymbol, tick.lastTradedPrice);148TradeManager.symbolToCMPMap[tick.tradingSymbol] = tick.lastTradedPrice # Store the latest tick in map149# On each new tick, get a created trade and call its strategy whether to place trade or not150for strategy in TradeManager.strategyToInstanceMap:151longTrade = TradeManager.getUntriggeredTrade(tick.tradingSymbol, strategy, Direction.LONG)152shortTrade = TradeManager.getUntriggeredTrade(tick.tradingSymbol, strategy, Direction.SHORT)153if longTrade == None and shortTrade == None:154continue155strategyInstance = TradeManager.strategyToInstanceMap[strategy]156if longTrade != None:157if strategyInstance.shouldPlaceTrade(longTrade, tick):158# place the longTrade159isSuccess = TradeManager.executeTrade(longTrade)160if isSuccess == True:161# set longTrade state to ACTIVE162longTrade.tradeState = TradeState.ACTIVE163longTrade.startTimestamp = Utils.getEpoch()164continue165166if shortTrade != None:167if strategyInstance.shouldPlaceTrade(shortTrade, tick):168# place the shortTrade169isSuccess = TradeManager.executeTrade(shortTrade)170if isSuccess == True:171# set shortTrade state to ACTIVE172shortTrade.tradeState = TradeState.ACTIVE173shortTrade.startTimestamp = Utils.getEpoch()174175@staticmethod176def getUntriggeredTrade(tradingSymbol, strategy, direction):177trade = None178for tr in TradeManager.trades:179if tr.tradeState == TradeState.DISABLED:180continue181if tr.tradeState != TradeState.CREATED:182continue183if tr.tradingSymbol != tradingSymbol:184continue185if tr.strategy != strategy:186continue187if tr.direction != direction:188continue189trade = tr190break191return trade192193@staticmethod194def executeTrade(trade):195logging.info('TradeManager: Execute trade called for %s', trade)196trade.initialStopLoss = trade.stopLoss197# Create order input params object and place order198oip = OrderInputParams(trade.tradingSymbol)199oip.direction = trade.direction200oip.productType = trade.productType201oip.orderType = OrderType.MARKET if trade.placeMarketOrder == True else OrderType.LIMIT202oip.price = trade.requestedEntry203oip.qty = trade.qty204if trade.isFutures == True or trade.isOptions == True:205oip.isFnO = True206try:207trade.entryOrder = TradeManager.getOrderManager().placeOrder(oip)208except Exception as e:209logging.error('TradeManager: Execute trade failed for tradeID %s: Error => %s', trade.tradeID, str(e))210return False211212logging.info('TradeManager: Execute trade successful for %s and entryOrder %s', trade, trade.entryOrder)213return True214215@staticmethod216def fetchAndUpdateAllTradeOrders():217allOrders = []218for trade in TradeManager.trades:219if trade.entryOrder != None:220allOrders.append(trade.entryOrder)221if trade.slOrder != None:222allOrders.append(trade.slOrder)223if trade.targetOrder != None:224allOrders.append(trade.targetOrder)225226TradeManager.getOrderManager().fetchAndUpdateAllOrderDetails(allOrders)227228@staticmethod229def trackAndUpdateAllTrades():230for trade in TradeManager.trades:231if trade.tradeState == TradeState.ACTIVE:232TradeManager.trackEntryOrder(trade)233TradeManager.trackSLOrder(trade)234TradeManager.trackTargetOrder(trade)235if trade.intradaySquareOffTimestamp != None:236nowEpoch = Utils.getEpoch()237if nowEpoch >= trade.intradaySquareOffTimestamp:238TradeManager.squareOffTrade(trade, TradeExitReason.SQUARE_OFF)239240@staticmethod241def trackEntryOrder(trade):242if trade.tradeState != TradeState.ACTIVE:243return244245if trade.entryOrder == None:246return247248if trade.entryOrder.orderStatus == OrderStatus.CANCELLED or trade.entryOrder.orderStatus == OrderStatus.REJECTED:249trade.tradeState = TradeState.CANCELLED250251trade.filledQty = trade.entryOrder.filledQty252if trade.filledQty > 0:253trade.entry = trade.entryOrder.averagePrice254# Update the current market price and calculate pnl255trade.cmp = TradeManager.symbolToCMPMap[trade.tradingSymbol]256Utils.calculateTradePnl(trade)257258@staticmethod259def trackSLOrder(trade):260if trade.tradeState != TradeState.ACTIVE:261return262if trade.stopLoss == 0: # Do not place SL order if no stopLoss provided263return264if trade.slOrder == None:265# Place SL order266TradeManager.placeSLOrder(trade)267else:268if trade.slOrder.orderStatus == OrderStatus.COMPLETE:269# SL Hit270exit = trade.slOrder.averagePrice271exitReason = TradeExitReason.SL_HIT if trade.initialStopLoss == trade.stopLoss else TradeExitReason.TRAIL_SL_HIT272TradeManager.setTradeToCompleted(trade, exit, exitReason)273# Make sure to cancel target order if exists274TradeManager.cancelTargetOrder(trade)275276elif trade.slOrder.orderStatus == OrderStatus.CANCELLED:277# SL order cancelled outside of algo (manually or by broker or by exchange)278logging.error('SL order %s for tradeID %s cancelled outside of Algo. Setting the trade as completed with exit price as current market price.', trade.slOrder.orderId, trade.tradeID)279exit = TradeManager.symbolToCMPMap[trade.tradingSymbol]280TradeManager.setTradeToCompleted(trade, exit, TradeExitReason.SL_CANCELLED)281# Cancel target order if exists282TradeManager.cancelTargetOrder(trade)283284else:285TradeManager.checkAndUpdateTrailSL(trade)286287@staticmethod288def checkAndUpdateTrailSL(trade):289# Trail the SL if applicable for the trade290strategyInstance = TradeManager.strategyToInstanceMap[trade.strategy]291if strategyInstance == None:292return293294newTrailSL = strategyInstance.getTrailingSL(trade)295updateSL = False296if newTrailSL > 0:297if trade.direction == Direction.LONG and newTrailSL > trade.stopLoss:298updateSL = True299elif trade.direction == Direction.SHORT and newTrailSL < trade.stopLoss:300updateSL = True301if updateSL == True:302omp = OrderModifyParams()303omp.newTriggerPrice = newTrailSL304try:305oldSL = trade.stopLoss306TradeManager.getOrderManager().modifyOrder(trade.slOrder, omp)307logging.info('TradeManager: Trail SL: Successfully modified stopLoss from %f to %f for tradeID %s', oldSL, newTrailSL, trade.tradeID)308trade.stopLoss = newTrailSL # IMPORTANT: Dont forget to update this on successful modification309except Exception as e:310logging.error('TradeManager: Failed to modify SL order for tradeID %s orderId %s: Error => %s', trade.tradeID, trade.slOrder.orderId, str(e))311312@staticmethod313def trackTargetOrder(trade):314if trade.tradeState != TradeState.ACTIVE:315return316if trade.target == 0: # Do not place Target order if no target provided317return318if trade.targetOrder == None:319# Place Target order320TradeManager.placeTargetOrder(trade)321else:322if trade.targetOrder.orderStatus == OrderStatus.COMPLETE:323# Target Hit324exit = trade.targetOrder.averagePrice325TradeManager.setTradeToCompleted(trade, exit, TradeExitReason.TARGET_HIT)326# Make sure to cancel sl order327TradeManager.cancelSLOrder(trade)328329elif trade.targetOrder.orderStatus == OrderStatus.CANCELLED:330# Target order cancelled outside of algo (manually or by broker or by exchange)331logging.error('Target order %s for tradeID %s cancelled outside of Algo. Setting the trade as completed with exit price as current market price.', trade.targetOrder.orderId, trade.tradeID)332exit = TradeManager.symbolToCMPMap[trade.tradingSymbol]333TradeManager.setTradeToCompleted(trade, exit, TradeExitReason.TARGET_CANCELLED)334# Cancel SL order335TradeManager.cancelSLOrder(trade)336337@staticmethod338def placeSLOrder(trade):339oip = OrderInputParams(trade.tradingSymbol)340oip.direction = Direction.SHORT if trade.direction == Direction.LONG else Direction.LONG341oip.productType = trade.productType342oip.orderType = OrderType.SL_MARKET343oip.triggerPrice = trade.stopLoss344oip.qty = trade.qty345if trade.isFutures == True or trade.isOptions == True:346oip.isFnO = True347try:348trade.slOrder = TradeManager.getOrderManager().placeOrder(oip)349except Exception as e:350logging.error('TradeManager: Failed to place SL order for tradeID %s: Error => %s', trade.tradeID, str(e))351return False352logging.info('TradeManager: Successfully placed SL order %s for tradeID %s', trade.slOrder.orderId, trade.tradeID)353return True354355@staticmethod356def placeTargetOrder(trade, isMarketOrder = False):357oip = OrderInputParams(trade.tradingSymbol)358oip.direction = Direction.SHORT if trade.direction == Direction.LONG else Direction.LONG359oip.productType = trade.productType360oip.orderType = OrderType.MARKET if isMarketOrder == True else OrderType.LIMIT361oip.price = 0 if isMarketOrder == True else trade.target362oip.qty = trade.qty363if trade.isFutures == True or trade.isOptions == True:364oip.isFnO = True365try:366trade.targetOrder = TradeManager.getOrderManager().placeOrder(oip)367except Exception as e:368logging.error('TradeManager: Failed to place Target order for tradeID %s: Error => %s', trade.tradeID, str(e))369return False370logging.info('TradeManager: Successfully placed Target order %s for tradeID %s', trade.targetOrder.orderId, trade.tradeID)371return True372373@staticmethod374def cancelEntryOrder(trade):375if trade.entryOrder == None:376return377if trade.entryOrder.orderStatus == OrderStatus.CANCELLED:378return379try:380TradeManager.getOrderManager().cancelOrder(trade.entryOrder)381except Exception as e:382logging.error('TradeManager: Failed to cancel Entry order %s for tradeID %s: Error => %s', trade.entryOrder.orderId, trade.tradeID, str(e))383logging.info('TradeManager: Successfully cancelled Entry order %s for tradeID %s', trade.entryOrder.orderId, trade.tradeID)384385@staticmethod386def cancelSLOrder(trade):387if trade.slOrder == None:388return389if trade.slOrder.orderStatus == OrderStatus.CANCELLED:390return391try:392TradeManager.getOrderManager().cancelOrder(trade.slOrder)393except Exception as e:394logging.error('TradeManager: Failed to cancel SL order %s for tradeID %s: Error => %s', trade.slOrder.orderId, trade.tradeID, str(e))395logging.info('TradeManager: Successfully cancelled SL order %s for tradeID %s', trade.slOrder.orderId, trade.tradeID)396397@staticmethod398def cancelTargetOrder(trade):399if trade.targetOrder == None:400return401if trade.targetOrder.orderStatus == OrderStatus.CANCELLED:402return403try:404TradeManager.getOrderManager().cancelOrder(trade.targetOrder)405except Exception as e:406logging.error('TradeManager: Failed to cancel Target order %s for tradeID %s: Error => %s', trade.targetOrder.orderId, trade.tradeID, str(e))407logging.info('TradeManager: Successfully cancelled Target order %s for tradeID %s', trade.targetOrder.orderId, trade.tradeID)408409@staticmethod410def setTradeToCompleted(trade, exit, exitReason = None):411trade.tradeState = TradeState.COMPLETED412trade.exit = exit413trade.exitReason = exitReason if trade.exitReason == None else trade.exitReason414trade.endTimestamp = Utils.getEpoch()415trade = Utils.calculateTradePnl(trade)416logging.info('TradeManager: setTradeToCompleted strategy = %s, symbol = %s, qty = %d, entry = %f, exit = %f, pnl = %f, exit reason = %s', trade.strategy, trade.tradingSymbol, trade.filledQty, trade.entry, trade.exit, trade.pnl, trade.exitReason)417418@staticmethod419def squareOffTrade(trade, reason = TradeExitReason.SQUARE_OFF):420logging.info('TradeManager: squareOffTrade called for tradeID %s with reason %s', trade.tradeID, reason)421if trade == None or trade.tradeState != TradeState.ACTIVE:422return423424trade.exitReason = reason425if trade.entryOrder != None:426if trade.entryOrder.orderStatus == OrderStatus.OPEN:427# Cancel entry order if it is still open (not filled or partially filled case)428TradeManager.cancelEntryOrder(trade)429430if trade.slOrder != None:431TradeManager.cancelSLOrder(trade)432433if trade.targetOrder != None:434# Change target order type to MARKET to exit position immediately435logging.info('TradeManager: changing target order %s to MARKET to exit position for tradeID %s', trade.targetOrder.orderId, trade.tradeID)436TradeManager.getOrderManager().modifyOrderToMarket(trade.targetOrder)437else:438# Place new target order to exit position439logging.info('TradeManager: placing new target order to exit position for tradeID %s', trade.tradeID)440TradeManager.placeTargetOrder(trade, True)441442@staticmethod443def getOrderManager():444orderManager = None445brokerName = Controller.getBrokerName()446if brokerName == "zerodha":447orderManager = ZerodhaOrderManager()448#elif brokerName == "fyers": # Not implemented449return orderManager450451@staticmethod452def getNumberOfTradesPlacedByStrategy(strategy):453count = 0454for trade in TradeManager.trades:455if trade.strategy != strategy:456continue457if trade.tradeState == TradeState.CREATED or trade.tradeState == TradeState.DISABLED:458continue459# consider active/completed/cancelled trades as trades placed460count += 1461return count462463@staticmethod464def getAllTradesByStrategy(strategy):465tradesByStrategy = []466for trade in TradeManager.trades:467if trade.strategy == strategy:468tradesByStrategy.append(trade)469return tradesByStrategy470471@staticmethod472def convertJSONToTrade(jsonData):473trade = Trade(jsonData['tradingSymbol'])474trade.tradeID = jsonData['tradeID']475trade.strategy = jsonData['strategy']476trade.direction = jsonData['direction']477trade.productType = jsonData['productType']478trade.isFutures = jsonData['isFutures']479trade.isOptions = jsonData['isOptions']480trade.optionType = jsonData['optionType']481trade.placeMarketOrder = jsonData['placeMarketOrder']482trade.intradaySquareOffTimestamp = jsonData['intradaySquareOffTimestamp']483trade.requestedEntry = jsonData['requestedEntry']484trade.entry = jsonData['entry']485trade.qty = jsonData['qty']486trade.filledQty = jsonData['filledQty']487trade.initialStopLoss = jsonData['initialStopLoss']488trade.stopLoss = jsonData['stopLoss']489trade.target = jsonData['target']490trade.cmp = jsonData['cmp']491trade.tradeState = jsonData['tradeState']492trade.timestamp = jsonData['timestamp']493trade.createTimestamp = jsonData['createTimestamp']494trade.startTimestamp = jsonData['startTimestamp']495trade.endTimestamp = jsonData['endTimestamp']496trade.pnl = jsonData['pnl']497trade.pnlPercentage = jsonData['pnlPercentage']498trade.exit = jsonData['exit']499trade.exitReason = jsonData['exitReason']500trade.exchange = jsonData['exchange']501trade.entryOrder = TradeManager.convertJSONToOrder(jsonData['entryOrder'])502trade.slOrder = TradeManager.convertJSONToOrder(jsonData['slOrder'])503trade.targetOrder = TradeManager.convertJSONToOrder(jsonData['targetOrder'])504return trade505506@staticmethod507def convertJSONToOrder(jsonData):508if jsonData == None:509return None510order = Order()511order.tradingSymbol = jsonData['tradingSymbol']512order.exchange = jsonData['exchange']513order.productType = jsonData['productType']514order.orderType = jsonData['orderType']515order.price = jsonData['price']516order.triggerPrice = jsonData['triggerPrice']517order.qty = jsonData['qty']518order.orderId = jsonData['orderId']519order.orderStatus = jsonData['orderStatus']520order.averagePrice = jsonData['averagePrice']521order.filledQty = jsonData['filledQty']522order.pendingQty = jsonData['pendingQty']523order.orderPlaceTimestamp = jsonData['orderPlaceTimestamp']524order.lastOrderUpdateTimestamp = jsonData['lastOrderUpdateTimestamp']525order.message = jsonData['message']526return order527528@staticmethod529def getLastTradedPrice(tradingSymbol):530return TradeManager.symbolToCMPMap[tradingSymbol]531532533