Skip to content

Commit

Permalink
TradeManager placing SL and target orders and tracking trades
Browse files Browse the repository at this point in the history
  • Loading branch information
sreenivasdoosa committed May 9, 2021
1 parent 018e1d2 commit a8f091a
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 7 deletions.
4 changes: 2 additions & 2 deletions src/ordermgmt/ZerodhaOrderManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ def modifyOrder(self, order, orderModifyParams):
logging.info('%s Order modify failed: %s', self.broker, str(e))
raise Exception(str(e))

def modifyOrderToMarket(self, order, orderModifyParams):
logging.info('%s: Going to modify order with params %s', self.broker, orderModifyParams)
def modifyOrderToMarket(self, order):
logging.info('%s: Going to modify order with params %s', self.broker)
kite = self.brokerHandle
try:
orderId = kite.modify_order(
Expand Down
7 changes: 7 additions & 0 deletions src/strategies/BaseStrategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,14 @@ def shouldPlaceTrade(self, trade, tick):
if trade == None:
return False
if trade.qty == 0:
TradeManager.disableTrade(trade, 'InvalidQuantity')
return False

now = datetime.now()
if now > self.stopTimestamp:
TradeManager.disableTrade(trade, 'NoNewTradesCutOffTimeReached')
return False

numOfTradesPlaced = TradeManager.getNumberOfTradesPlacedByStrategy(self.getName())
if numOfTradesPlaced >= self.maxTradesPerDay:
TradeManager.disableTrade(trade, 'MaxTradesPerDayReached')
Expand Down
3 changes: 2 additions & 1 deletion src/strategies/SampleStrategy.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def __init__(self):
self.targetPerncetage = 2.2
self.startTimestamp = Utils.getTimeOfToDay(9, 30, 0) # When to start the strategy. Default is Market start time
self.stopTimestamp = Utils.getTimeOfToDay(12, 30, 0) # This is not square off timestamp. This is the timestamp after which no new trades will be placed under this strategy but existing trades continue to be active.
self.squareOfTimestamp = Utils.getTimeOfToDay(15, 0, 0) # Square off time
self.squareOffTimestamp = Utils.getTimeOfToDay(15, 0, 0) # Square off time
self.capital = 3000 # Capital to trade (This is the margin you allocate from your broker account for this strategy)
self.leverage = 2 # 2x, 3x Etc
self.maxTradesPerDay = 3 # Max number of trades per day under this strategy
Expand Down Expand Up @@ -88,6 +88,7 @@ def generateTrade(self, tradingSymbol, direction, breakoutPrice):
else:
trade.target = Utils.roundToNSEPrice(breakoutPrice - breakoutPrice * self.targetPerncetage / 100)

trade.intradaySquareOffTimestamp = self.squareOffTimestamp
# Hand over the trade to TradeManager
TradeManager.addNewTrade(trade)

Expand Down
6 changes: 3 additions & 3 deletions src/trademgmt/Trade.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ def __init__(self, tradingSymbol):
self.isOptions = False # Options trade
self.optionType = None # CE/PE. Applicable only if isOptions is True
self.placeMarketOrder = False # True means place the entry order with Market Order Type
self.noTarget = False # This has to be set to True if target is not applicable for the trade i.e. Square off or SL/Trail SL
self.intradaySquareOffTimestamp = None # Can be strategy specific. Some can square off at 15:00:00 some can at 15:15:00 etc.
self.requestedEntry = 0 # Requested entry
self.entry = 0 # Actual entry. This will be different from requestedEntry if the order placed is Market order
Expand All @@ -33,7 +32,7 @@ def __init__(self, tradingSymbol):
self.createTimestamp = datetime.now() # Timestamp when the trade is created (Not triggered)
self.startTimestamp = None # Timestamp when the trade gets triggered and order placed
self.endTimestamp = None # Timestamp when the trade ended
self.profitLoss = 0 # Profit loss of the trade. If trade is Active this shows the unrealized pnl else realized pnl
self.pnl = 0 # Profit loss of the trade. If trade is Active this shows the unrealized pnl else realized pnl
self.pnlPercentage = 0 # Profit Loss in percentage terms
self.exit = 0 # Exit price of the trade
self.exitReason = None # SL/Target/SquareOff/Any Other
Expand Down Expand Up @@ -69,5 +68,6 @@ def __str__(self):
+ ", strategy=" + self.strategy + ", direction=" + self.direction \
+ ", productType=" + self.productType + ", reqEntry=" + str(self.requestedEntry) \
+ ", stopLoss=" + str(self.stopLoss) + ", target=" + str(self.target) \
+ ", entry=" + str(self.entry) + ", exit=" + str(self.exit)
+ ", entry=" + str(self.entry) + ", exit=" + str(self.exit) \
+ ", profitLoss" + str(self.pnl)

8 changes: 8 additions & 0 deletions src/trademgmt/TradeExitReason.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

class TradeExitReason:
SL_HIT = "SL HIT"
TARGET_HIT = "TARGET HIT"
SQUARE_OFF = "SQUARE OFF"
SL_CANCELLED = "SL CANCELLED"
TARGET_CANCELLED = "TARGET CANCELLED"

188 changes: 187 additions & 1 deletion src/trademgmt/TradeManager.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import logging
import time
from datetime import datetime

from core.Controller import Controller
from ticker.ZerodhaTicker import ZerodhaTicker
from trademgmt.TradeState import TradeState
from trademgmt.TradeExitReason import TradeExitReason
from ordermgmt.ZerodhaOrderManager import ZerodhaOrderManager
from ordermgmt.OrderInputParams import OrderInputParams
from models.OrderType import OrderType
from models.OrderStatus import OrderStatus
from models.Direction import Direction

from utils.Utils import Utils

class TradeManager:
ticker = None
trades = [] # to store all the trades
strategyToInstanceMap = {}
symbolToCMPMap = {}

@staticmethod
def run():
Expand Down Expand Up @@ -79,6 +84,7 @@ def disableTrade(trade, reason):
@staticmethod
def tickerListener(tick):
logging.info('tickerLister: new tick received for %s = %f', tick.tradingSymbol, tick.lastTradedPrice);
TradeManager.symbolToCMPMap[tick.tradingSymbol] = tick.lastTradedPrice # Store the latest tick in map
# On each new tick, get a created trade and call its strategy whether to place trade or not
for strategy in TradeManager.strategyToInstanceMap:
trade = TradeManager.getUntriggeredTrade(tick.tradingSymbol, strategy)
Expand All @@ -91,6 +97,7 @@ def tickerListener(tick):
if isSuccess == True:
# set trade state to ACTIVE
trade.tradeState = TradeState.ACTIVE
trade.startTimestamp = datetime.now()

@staticmethod
def getUntriggeredTrade(tradingSymbol, strategy):
Expand All @@ -111,6 +118,8 @@ def getUntriggeredTrade(tradingSymbol, strategy):
@staticmethod
def executeTrade(trade):
logging.info('TradeManager: Execute trade called for %s', trade)
trade.initialStopLoss = trade.stopLoss
# Create order input params object and place order
oip = OrderInputParams(trade.tradingSymbol)
oip.direction = trade.direction
oip.productType = trade.productType
Expand Down Expand Up @@ -141,7 +150,184 @@ def fetchAndUpdateAllTradeOrders():

@staticmethod
def trackAndUpdateAllTrades():
logging.info('trackAndUpdateAllTrades: To be implemented')
for trade in TradeManager.trades:
if trade.tradeState == TradeState.ACTIVE:
if trade.intradaySquareOffTimestamp != None:
now = datetime.now()
if now >= trade.intradaySquareOffTimestamp:
TradeManager.squareOffTrade(trade, TradeExitReason.SQUARE_OFF)
else:
TradeManager.trackEntryOrder(trade)
TradeManager.trackSLOrder(trade)
TradeManager.trackTargetOrder(trade)

@staticmethod
def trackEntryOrder(trade):
if trade.tradeState != TradeState.ACTIVE:
return

if trade.entryOrder == None:
return

if trade.entryOrder.orderStatus == OrderStatus.CANCELLED or trade.entryOrder.orderStatus == OrderStatus.REJECTED:
trade.tradeState = TradeState.CANCELLED

trade.filledQty = trade.entryOrder.filledQty
if trade.filledQty > 0:
trade.entry = trade.entryOrder.averagePrice
# Update the current market price and calculate pnl
trade.cmp = TradeManager.symbolToCMPMap[trade.tradingSymbol]
Utils.calculateTradePnl(trade)

@staticmethod
def trackSLOrder(trade):
if trade.tradeState != TradeState.ACTIVE:
return
if trade.stopLoss == 0: # Do not place SL order if no stoploss provided
return
if trade.slOrder == None:
# Place SL order
TradeManager.placeSLOrder(trade)
else:
if trade.slOrder.orderStatus == OrderStatus.COMPLETE:
# SL Hit
exit = trade.slOrder.averagePrice
TradeManager.setTradeToCompleted(trade, exit, TradeExitReason.SL_HIT)
# Make sure to cancel target order if exists
TradeManager.cancelTargetOrder(trade)

elif trade.slOrder.orderStatus == OrderStatus.CANCELLED:
# SL order cancelled outside of algo (manually or by broker or by exchange)
logging.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)
exit = TradeManager.symbolToCMPMap[trade.tradingSymbol]
TradeManager.setTradeToCompleted(trade, exit, TradeExitReason.SL_CANCELLED)
# Cancel target order if exists
TradeManager.cancelTargetOrder(trade)

@staticmethod
def trackTargetOrder(trade):
if trade.tradeState != TradeState.ACTIVE:
return
if trade.target == 0: # Do not place Target order if no target provided
return
if trade.targetOrder == None:
# Place Target order
TradeManager.placeTargetOrder(trade)
else:
if trade.targetOrder.orderStatus == OrderStatus.COMPLETE:
# Target Hit
exit = trade.targetOrder.averagePrice
TradeManager.setTradeToCompleted(trade, exit, TradeExitReason.TARGET_HIT)
# Make sure to cancel sl order
TradeManager.cancelSLOrder(trade)

elif trade.targetOrder.orderStatus == OrderStatus.CANCELLED:
# Target order cancelled outside of algo (manually or by broker or by exchange)
logging.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)
exit = TradeManager.symbolToCMPMap[trade.tradingSymbol]
TradeManager.setTradeToCompleted(trade, exit, TradeExitReason.TARGET_CANCELLED)
# Cancel SL order
TradeManager.cancelSLOrder(trade)

@staticmethod
def placeSLOrder(trade):
oip = OrderInputParams(trade.tradingSymbol)
oip.direction = Direction.SHORT if trade.direction == Direction.LONG else Direction.SHORT
oip.productType = trade.productType
oip.orderType = OrderType.SL_MARKET
oip.triggerPrice = trade.stopLoss
oip.qty = trade.qty
try:
trade.slOrder = TradeManager.getOrderManager().placeOrder(oip)
except Exception as e:
logging.exrror('TradeManager: Failed to place SL order for tradeID %s: Error => %s', trade.tradeID, str(e))
return False
logging.info('TradeManager: Successfully placed SL order %s for tradeID %s', trade.slOrder.orderId, trade.tradeID)
return True

@staticmethod
def placeTargetOrder(trade, isMarketOrder = False):
oip = OrderInputParams(trade.tradingSymbol)
oip.direction = Direction.SHORT if trade.direction == Direction.LONG else Direction.SHORT
oip.productType = trade.productType
oip.orderType = OrderType.MARKET if isMarketOrder == True else OrderType.LIMIT
oip.price = 0 if isMarketOrder == True else trade.target
oip.qty = trade.qty
try:
trade.targetOrder = TradeManager.getOrderManager().placeOrder(oip)
except Exception as e:
logging.exrror('TradeManager: Failed to place Target order for tradeID %s: Error => %s', trade.tradeID, str(e))
return False
logging.info('TradeManager: Successfully placed Target order %s for tradeID %s', trade.targetOrder.orderId, trade.tradeID)
return True

@staticmethod
def cancelEntryOrder(trade):
if trade.entryOrder == None:
return
if trade.entryOrder.orderStatus == OrderStatus.CANCELLED:
return
try:
TradeManager.getOrderManager().cancelOrder(trade.entryOrder)
except Exception as e:
logging.exrror('TradeManager: Failed to cancel Entry order %s for tradeID %s: Error => %s', trade.entryOrder.orderId, trade.tradeID, str(e))
logging.info('TradeManager: Successfully cancelled Entry order %s for tradeID %s', trade.entryOrder.orderId, trade.tradeID)

@staticmethod
def cancelSLOrder(trade):
if trade.slOrder == None:
return
if trade.slOrder.orderStatus == OrderStatus.CANCELLED:
return
try:
TradeManager.getOrderManager().cancelOrder(trade.slOrder)
except Exception as e:
logging.exrror('TradeManager: Failed to cancel SL order %s for tradeID %s: Error => %s', trade.slOrder.orderId, trade.tradeID, str(e))
logging.info('TradeManager: Successfully cancelled SL order %s for tradeID %s', trade.slOrder.orderId, trade.tradeID)

@staticmethod
def cancelTargetOrder(trade):
if trade.targetOrder == None:
return
if trade.targetOrder.orderStatus == OrderStatus.CANCELLED:
return
try:
TradeManager.getOrderManager().cancelOrder(trade.targetOrder)
except Exception as e:
logging.exrror('TradeManager: Failed to cancel Target order %s for tradeID %s: Error => %s', trade.targetOrder.orderId, trade.tradeID, str(e))
logging.info('TradeManager: Successfully cancelled Target order %s for tradeID %s', trade.targetOrder.orderId, trade.tradeID)

@staticmethod
def setTradeToCompleted(trade, exit, exitReason = None):
trade.tradeState = TradeState.COMPLETED
trade.exit = exit
trade.exitReason = exitReason if trade.exitReason == None else trade.exitReason
trade = Utils.calculateTradePnl(trade)
logging.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)

@staticmethod
def squareOffTrade(trade, reason = TradeExitReason.SQUARE_OFF):
logging.info('TradeManager: squareOffTrade called for tradeID %s with reason %s', trade.tradeID, reason)
if trade == None or trade.tradeState != TradeState.ACTIVE:
return

trade.exitReason = reason
if trade.entryOrder != None:
if trade.entryOrder.orderStatus == OrderStatus.OPEN:
# Cancel entry order if it is still open (not filled or partially filled case)
TradeManager.cancelEntryOrder(trade)

if trade.slOrder != None:
TradeManager.cancelSLOrder(trade)

if trade.targetOrder != None:
# Change target order type to MARKET to exit position immediately
logging.info('TradeManager: changing target order %s to MARKET to exit position for tradeID %s', trade.targetOrder.orderId, trade.tradeID)
TradeManager.getOrderManager.modifyOrderToMarket(trade.targetOrder)
else:
# Place new target order to exit position
logging.info('TradeManager: placing new target order to exit position for tradeID %s', trade.tradeID)
TradeManager.placeTargetOrder(trade, true)

@staticmethod
def getOrderManager():
Expand Down
20 changes: 20 additions & 0 deletions src/utils/Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from datetime import datetime

from config.Config import getHolidays
from models.Direction import Direction

class Utils:
dateFormat = "%Y-%m-%d"
Expand Down Expand Up @@ -98,3 +99,22 @@ def isTodayHoliday():
def generateTradeID():
return str(uuid.uuid4())

@staticmethod
def calculateTradePnl(trade):
if trade.tradeState == TradeState.ACTIVE:
if trade.cmp > 0:
if trade.direction == Direction.LONG:
trade.pnl = Utils.roundOff(trade.filledQty * (trade.cmp - trade.entry))
else:
trade.pnl = Utils.roundOff(trade.filledQty * (trade.entry - trade.cmp))
else:
if trade.exit > 0:
if trade.direction == Direction.LONG:
trade.pnl = Utils.roundOff(trade.filledQty * (trade.exit - trade.entry))
else:
trade.pnl = Utils.roundOff(trade.filledQty * (trade.entry - trade.exit))
tradeValue = trade.entry * trade.filledQty
if tradeValue > 0:
trade.pnlPercentage = Utils.roundOff(trade.pnl * 100 / tradeValue)
return trade

0 comments on commit a8f091a

Please sign in to comment.