Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sreenivasdoosa
GitHub Repository: sreenivasdoosa/sdoosa-algo-trade-python
Path: blob/master/src/trademgmt/TradeManager.py
301 views
1
import os
2
import logging
3
import time
4
import json
5
from datetime import datetime
6
7
from config.Config import getServerConfig
8
from core.Controller import Controller
9
from ticker.ZerodhaTicker import ZerodhaTicker
10
from trademgmt.Trade import Trade
11
from trademgmt.TradeState import TradeState
12
from trademgmt.TradeExitReason import TradeExitReason
13
from trademgmt.TradeEncoder import TradeEncoder
14
from ordermgmt.ZerodhaOrderManager import ZerodhaOrderManager
15
from ordermgmt.OrderInputParams import OrderInputParams
16
from ordermgmt.OrderModifyParams import OrderModifyParams
17
from ordermgmt.Order import Order
18
from models.OrderType import OrderType
19
from models.OrderStatus import OrderStatus
20
from models.Direction import Direction
21
22
from utils.Utils import Utils
23
24
class TradeManager:
25
ticker = None
26
trades = [] # to store all the trades
27
strategyToInstanceMap = {}
28
symbolToCMPMap = {}
29
intradayTradesDir = None
30
registeredSymbols = []
31
32
@staticmethod
33
def run():
34
if Utils.isTodayHoliday():
35
logging.info("Cannot start TradeManager as Today is Trading Holiday.")
36
return
37
38
if Utils.isMarketClosedForTheDay():
39
logging.info("Cannot start TradeManager as Market is closed for the day.")
40
return
41
42
Utils.waitTillMarketOpens("TradeManager")
43
44
# check and create trades directory for today`s date
45
serverConfig = getServerConfig()
46
tradesDir = os.path.join(serverConfig['deployDir'], 'trades')
47
TradeManager.intradayTradesDir = os.path.join(tradesDir, Utils.getTodayDateStr())
48
if os.path.exists(TradeManager.intradayTradesDir) == False:
49
logging.info('TradeManager: Intraday Trades Directory %s does not exist. Hence going to create.', TradeManager.intradayTradesDir)
50
os.makedirs(TradeManager.intradayTradesDir)
51
52
# start ticker service
53
brokerName = Controller.getBrokerName()
54
if brokerName == "zerodha":
55
TradeManager.ticker = ZerodhaTicker()
56
#elif brokerName == "fyers" # not implemented
57
# ticker = FyersTicker()
58
59
TradeManager.ticker.startTicker()
60
TradeManager.ticker.registerListener(TradeManager.tickerListener)
61
62
# sleep for 2 seconds for ticker connection establishment
63
time.sleep(2)
64
65
# Load all trades from json files to app memory
66
TradeManager.loadAllTradesFromFile()
67
68
# track and update trades in a loop
69
while True:
70
if Utils.isMarketClosedForTheDay():
71
logging.info('TradeManager: Stopping TradeManager as market closed.')
72
break
73
74
try:
75
# Fetch all order details from broker and update orders in each trade
76
TradeManager.fetchAndUpdateAllTradeOrders()
77
# track each trade and take necessary action
78
TradeManager.trackAndUpdateAllTrades()
79
except Exception as e:
80
logging.exception("Exception in TradeManager Main thread")
81
82
# save updated data to json file
83
TradeManager.saveAllTradesToFile()
84
85
# sleep for 30 seconds and then continue
86
time.sleep(30)
87
logging.info('TradeManager: Main thread woke up..')
88
89
@staticmethod
90
def registerStrategy(strategyInstance):
91
TradeManager.strategyToInstanceMap[strategyInstance.getName()] = strategyInstance
92
93
@staticmethod
94
def loadAllTradesFromFile():
95
tradesFilepath = os.path.join(TradeManager.intradayTradesDir, 'trades.json')
96
if os.path.exists(tradesFilepath) == False:
97
logging.warn('TradeManager: loadAllTradesFromFile() Trades Filepath %s does not exist', tradesFilepath)
98
return
99
TradeManager.trades = []
100
tFile = open(tradesFilepath, 'r')
101
tradesData = json.loads(tFile.read())
102
for tr in tradesData:
103
trade = TradeManager.convertJSONToTrade(tr)
104
logging.info('loadAllTradesFromFile trade => %s', trade)
105
TradeManager.trades.append(trade)
106
if trade.tradingSymbol not in TradeManager.registeredSymbols:
107
# Algo register symbols with ticker
108
TradeManager.ticker.registerSymbols([trade.tradingSymbol])
109
TradeManager.registeredSymbols.append(trade.tradingSymbol)
110
logging.info('TradeManager: Successfully loaded %d trades from json file %s', len(TradeManager.trades), tradesFilepath)
111
112
@staticmethod
113
def saveAllTradesToFile():
114
tradesFilepath = os.path.join(TradeManager.intradayTradesDir, 'trades.json')
115
with open(tradesFilepath, 'w') as tFile:
116
json.dump(TradeManager.trades, tFile, indent=2, cls=TradeEncoder)
117
logging.info('TradeManager: Saved %d trades to file %s', len(TradeManager.trades), tradesFilepath)
118
119
@staticmethod
120
def addNewTrade(trade):
121
if trade == None:
122
return
123
logging.info('TradeManager: addNewTrade called for %s', trade)
124
for tr in TradeManager.trades:
125
if tr.equals(trade):
126
logging.warn('TradeManager: Trade already exists so not adding again. %s', trade)
127
return
128
# Add the new trade to the list
129
TradeManager.trades.append(trade)
130
logging.info('TradeManager: trade %s added successfully to the list', trade.tradeID)
131
# Register the symbol with ticker so that we will start getting ticks for this symbol
132
if trade.tradingSymbol not in TradeManager.registeredSymbols:
133
TradeManager.ticker.registerSymbols([trade.tradingSymbol])
134
TradeManager.registeredSymbols.append(trade.tradingSymbol)
135
# Also add the trade to strategy trades list
136
strategyInstance = TradeManager.strategyToInstanceMap[trade.strategy]
137
if strategyInstance != None:
138
strategyInstance.addTradeToList(trade)
139
140
@staticmethod
141
def disableTrade(trade, reason):
142
if trade != None:
143
logging.info('TradeManager: Going to disable trade ID %s with the reason %s', trade.tradeID, reason)
144
trade.tradeState = TradeState.DISABLED
145
146
@staticmethod
147
def tickerListener(tick):
148
# logging.info('tickerLister: new tick received for %s = %f', tick.tradingSymbol, tick.lastTradedPrice);
149
TradeManager.symbolToCMPMap[tick.tradingSymbol] = tick.lastTradedPrice # Store the latest tick in map
150
# On each new tick, get a created trade and call its strategy whether to place trade or not
151
for strategy in TradeManager.strategyToInstanceMap:
152
longTrade = TradeManager.getUntriggeredTrade(tick.tradingSymbol, strategy, Direction.LONG)
153
shortTrade = TradeManager.getUntriggeredTrade(tick.tradingSymbol, strategy, Direction.SHORT)
154
if longTrade == None and shortTrade == None:
155
continue
156
strategyInstance = TradeManager.strategyToInstanceMap[strategy]
157
if longTrade != None:
158
if strategyInstance.shouldPlaceTrade(longTrade, tick):
159
# place the longTrade
160
isSuccess = TradeManager.executeTrade(longTrade)
161
if isSuccess == True:
162
# set longTrade state to ACTIVE
163
longTrade.tradeState = TradeState.ACTIVE
164
longTrade.startTimestamp = Utils.getEpoch()
165
continue
166
167
if shortTrade != None:
168
if strategyInstance.shouldPlaceTrade(shortTrade, tick):
169
# place the shortTrade
170
isSuccess = TradeManager.executeTrade(shortTrade)
171
if isSuccess == True:
172
# set shortTrade state to ACTIVE
173
shortTrade.tradeState = TradeState.ACTIVE
174
shortTrade.startTimestamp = Utils.getEpoch()
175
176
@staticmethod
177
def getUntriggeredTrade(tradingSymbol, strategy, direction):
178
trade = None
179
for tr in TradeManager.trades:
180
if tr.tradeState == TradeState.DISABLED:
181
continue
182
if tr.tradeState != TradeState.CREATED:
183
continue
184
if tr.tradingSymbol != tradingSymbol:
185
continue
186
if tr.strategy != strategy:
187
continue
188
if tr.direction != direction:
189
continue
190
trade = tr
191
break
192
return trade
193
194
@staticmethod
195
def executeTrade(trade):
196
logging.info('TradeManager: Execute trade called for %s', trade)
197
trade.initialStopLoss = trade.stopLoss
198
# Create order input params object and place order
199
oip = OrderInputParams(trade.tradingSymbol)
200
oip.direction = trade.direction
201
oip.productType = trade.productType
202
oip.orderType = OrderType.MARKET if trade.placeMarketOrder == True else OrderType.LIMIT
203
oip.price = trade.requestedEntry
204
oip.qty = trade.qty
205
if trade.isFutures == True or trade.isOptions == True:
206
oip.isFnO = True
207
try:
208
trade.entryOrder = TradeManager.getOrderManager().placeOrder(oip)
209
except Exception as e:
210
logging.error('TradeManager: Execute trade failed for tradeID %s: Error => %s', trade.tradeID, str(e))
211
return False
212
213
logging.info('TradeManager: Execute trade successful for %s and entryOrder %s', trade, trade.entryOrder)
214
return True
215
216
@staticmethod
217
def fetchAndUpdateAllTradeOrders():
218
allOrders = []
219
for trade in TradeManager.trades:
220
if trade.entryOrder != None:
221
allOrders.append(trade.entryOrder)
222
if trade.slOrder != None:
223
allOrders.append(trade.slOrder)
224
if trade.targetOrder != None:
225
allOrders.append(trade.targetOrder)
226
227
TradeManager.getOrderManager().fetchAndUpdateAllOrderDetails(allOrders)
228
229
@staticmethod
230
def trackAndUpdateAllTrades():
231
for trade in TradeManager.trades:
232
if trade.tradeState == TradeState.ACTIVE:
233
TradeManager.trackEntryOrder(trade)
234
TradeManager.trackSLOrder(trade)
235
TradeManager.trackTargetOrder(trade)
236
if trade.intradaySquareOffTimestamp != None:
237
nowEpoch = Utils.getEpoch()
238
if nowEpoch >= trade.intradaySquareOffTimestamp:
239
TradeManager.squareOffTrade(trade, TradeExitReason.SQUARE_OFF)
240
241
@staticmethod
242
def trackEntryOrder(trade):
243
if trade.tradeState != TradeState.ACTIVE:
244
return
245
246
if trade.entryOrder == None:
247
return
248
249
if trade.entryOrder.orderStatus == OrderStatus.CANCELLED or trade.entryOrder.orderStatus == OrderStatus.REJECTED:
250
trade.tradeState = TradeState.CANCELLED
251
252
trade.filledQty = trade.entryOrder.filledQty
253
if trade.filledQty > 0:
254
trade.entry = trade.entryOrder.averagePrice
255
# Update the current market price and calculate pnl
256
trade.cmp = TradeManager.symbolToCMPMap[trade.tradingSymbol]
257
Utils.calculateTradePnl(trade)
258
259
@staticmethod
260
def trackSLOrder(trade):
261
if trade.tradeState != TradeState.ACTIVE:
262
return
263
if trade.stopLoss == 0: # Do not place SL order if no stopLoss provided
264
return
265
if trade.slOrder == None:
266
# Place SL order
267
TradeManager.placeSLOrder(trade)
268
else:
269
if trade.slOrder.orderStatus == OrderStatus.COMPLETE:
270
# SL Hit
271
exit = trade.slOrder.averagePrice
272
exitReason = TradeExitReason.SL_HIT if trade.initialStopLoss == trade.stopLoss else TradeExitReason.TRAIL_SL_HIT
273
TradeManager.setTradeToCompleted(trade, exit, exitReason)
274
# Make sure to cancel target order if exists
275
TradeManager.cancelTargetOrder(trade)
276
277
elif trade.slOrder.orderStatus == OrderStatus.CANCELLED:
278
# SL order cancelled outside of algo (manually or by broker or by exchange)
279
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)
280
exit = TradeManager.symbolToCMPMap[trade.tradingSymbol]
281
TradeManager.setTradeToCompleted(trade, exit, TradeExitReason.SL_CANCELLED)
282
# Cancel target order if exists
283
TradeManager.cancelTargetOrder(trade)
284
285
else:
286
TradeManager.checkAndUpdateTrailSL(trade)
287
288
@staticmethod
289
def checkAndUpdateTrailSL(trade):
290
# Trail the SL if applicable for the trade
291
strategyInstance = TradeManager.strategyToInstanceMap[trade.strategy]
292
if strategyInstance == None:
293
return
294
295
newTrailSL = strategyInstance.getTrailingSL(trade)
296
updateSL = False
297
if newTrailSL > 0:
298
if trade.direction == Direction.LONG and newTrailSL > trade.stopLoss:
299
updateSL = True
300
elif trade.direction == Direction.SHORT and newTrailSL < trade.stopLoss:
301
updateSL = True
302
if updateSL == True:
303
omp = OrderModifyParams()
304
omp.newTriggerPrice = newTrailSL
305
try:
306
oldSL = trade.stopLoss
307
TradeManager.getOrderManager().modifyOrder(trade.slOrder, omp)
308
logging.info('TradeManager: Trail SL: Successfully modified stopLoss from %f to %f for tradeID %s', oldSL, newTrailSL, trade.tradeID)
309
trade.stopLoss = newTrailSL # IMPORTANT: Dont forget to update this on successful modification
310
except Exception as e:
311
logging.error('TradeManager: Failed to modify SL order for tradeID %s orderId %s: Error => %s', trade.tradeID, trade.slOrder.orderId, str(e))
312
313
@staticmethod
314
def trackTargetOrder(trade):
315
if trade.tradeState != TradeState.ACTIVE:
316
return
317
if trade.target == 0: # Do not place Target order if no target provided
318
return
319
if trade.targetOrder == None:
320
# Place Target order
321
TradeManager.placeTargetOrder(trade)
322
else:
323
if trade.targetOrder.orderStatus == OrderStatus.COMPLETE:
324
# Target Hit
325
exit = trade.targetOrder.averagePrice
326
TradeManager.setTradeToCompleted(trade, exit, TradeExitReason.TARGET_HIT)
327
# Make sure to cancel sl order
328
TradeManager.cancelSLOrder(trade)
329
330
elif trade.targetOrder.orderStatus == OrderStatus.CANCELLED:
331
# Target order cancelled outside of algo (manually or by broker or by exchange)
332
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)
333
exit = TradeManager.symbolToCMPMap[trade.tradingSymbol]
334
TradeManager.setTradeToCompleted(trade, exit, TradeExitReason.TARGET_CANCELLED)
335
# Cancel SL order
336
TradeManager.cancelSLOrder(trade)
337
338
@staticmethod
339
def placeSLOrder(trade):
340
oip = OrderInputParams(trade.tradingSymbol)
341
oip.direction = Direction.SHORT if trade.direction == Direction.LONG else Direction.LONG
342
oip.productType = trade.productType
343
oip.orderType = OrderType.SL_MARKET
344
oip.triggerPrice = trade.stopLoss
345
oip.qty = trade.qty
346
if trade.isFutures == True or trade.isOptions == True:
347
oip.isFnO = True
348
try:
349
trade.slOrder = TradeManager.getOrderManager().placeOrder(oip)
350
except Exception as e:
351
logging.error('TradeManager: Failed to place SL order for tradeID %s: Error => %s', trade.tradeID, str(e))
352
return False
353
logging.info('TradeManager: Successfully placed SL order %s for tradeID %s', trade.slOrder.orderId, trade.tradeID)
354
return True
355
356
@staticmethod
357
def placeTargetOrder(trade, isMarketOrder = False):
358
oip = OrderInputParams(trade.tradingSymbol)
359
oip.direction = Direction.SHORT if trade.direction == Direction.LONG else Direction.LONG
360
oip.productType = trade.productType
361
oip.orderType = OrderType.MARKET if isMarketOrder == True else OrderType.LIMIT
362
oip.price = 0 if isMarketOrder == True else trade.target
363
oip.qty = trade.qty
364
if trade.isFutures == True or trade.isOptions == True:
365
oip.isFnO = True
366
try:
367
trade.targetOrder = TradeManager.getOrderManager().placeOrder(oip)
368
except Exception as e:
369
logging.error('TradeManager: Failed to place Target order for tradeID %s: Error => %s', trade.tradeID, str(e))
370
return False
371
logging.info('TradeManager: Successfully placed Target order %s for tradeID %s', trade.targetOrder.orderId, trade.tradeID)
372
return True
373
374
@staticmethod
375
def cancelEntryOrder(trade):
376
if trade.entryOrder == None:
377
return
378
if trade.entryOrder.orderStatus == OrderStatus.CANCELLED:
379
return
380
try:
381
TradeManager.getOrderManager().cancelOrder(trade.entryOrder)
382
except Exception as e:
383
logging.error('TradeManager: Failed to cancel Entry order %s for tradeID %s: Error => %s', trade.entryOrder.orderId, trade.tradeID, str(e))
384
logging.info('TradeManager: Successfully cancelled Entry order %s for tradeID %s', trade.entryOrder.orderId, trade.tradeID)
385
386
@staticmethod
387
def cancelSLOrder(trade):
388
if trade.slOrder == None:
389
return
390
if trade.slOrder.orderStatus == OrderStatus.CANCELLED:
391
return
392
try:
393
TradeManager.getOrderManager().cancelOrder(trade.slOrder)
394
except Exception as e:
395
logging.error('TradeManager: Failed to cancel SL order %s for tradeID %s: Error => %s', trade.slOrder.orderId, trade.tradeID, str(e))
396
logging.info('TradeManager: Successfully cancelled SL order %s for tradeID %s', trade.slOrder.orderId, trade.tradeID)
397
398
@staticmethod
399
def cancelTargetOrder(trade):
400
if trade.targetOrder == None:
401
return
402
if trade.targetOrder.orderStatus == OrderStatus.CANCELLED:
403
return
404
try:
405
TradeManager.getOrderManager().cancelOrder(trade.targetOrder)
406
except Exception as e:
407
logging.error('TradeManager: Failed to cancel Target order %s for tradeID %s: Error => %s', trade.targetOrder.orderId, trade.tradeID, str(e))
408
logging.info('TradeManager: Successfully cancelled Target order %s for tradeID %s', trade.targetOrder.orderId, trade.tradeID)
409
410
@staticmethod
411
def setTradeToCompleted(trade, exit, exitReason = None):
412
trade.tradeState = TradeState.COMPLETED
413
trade.exit = exit
414
trade.exitReason = exitReason if trade.exitReason == None else trade.exitReason
415
trade.endTimestamp = Utils.getEpoch()
416
trade = Utils.calculateTradePnl(trade)
417
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)
418
419
@staticmethod
420
def squareOffTrade(trade, reason = TradeExitReason.SQUARE_OFF):
421
logging.info('TradeManager: squareOffTrade called for tradeID %s with reason %s', trade.tradeID, reason)
422
if trade == None or trade.tradeState != TradeState.ACTIVE:
423
return
424
425
trade.exitReason = reason
426
if trade.entryOrder != None:
427
if trade.entryOrder.orderStatus == OrderStatus.OPEN:
428
# Cancel entry order if it is still open (not filled or partially filled case)
429
TradeManager.cancelEntryOrder(trade)
430
431
if trade.slOrder != None:
432
TradeManager.cancelSLOrder(trade)
433
434
if trade.targetOrder != None:
435
# Change target order type to MARKET to exit position immediately
436
logging.info('TradeManager: changing target order %s to MARKET to exit position for tradeID %s', trade.targetOrder.orderId, trade.tradeID)
437
TradeManager.getOrderManager().modifyOrderToMarket(trade.targetOrder)
438
else:
439
# Place new target order to exit position
440
logging.info('TradeManager: placing new target order to exit position for tradeID %s', trade.tradeID)
441
TradeManager.placeTargetOrder(trade, True)
442
443
@staticmethod
444
def getOrderManager():
445
orderManager = None
446
brokerName = Controller.getBrokerName()
447
if brokerName == "zerodha":
448
orderManager = ZerodhaOrderManager()
449
#elif brokerName == "fyers": # Not implemented
450
return orderManager
451
452
@staticmethod
453
def getNumberOfTradesPlacedByStrategy(strategy):
454
count = 0
455
for trade in TradeManager.trades:
456
if trade.strategy != strategy:
457
continue
458
if trade.tradeState == TradeState.CREATED or trade.tradeState == TradeState.DISABLED:
459
continue
460
# consider active/completed/cancelled trades as trades placed
461
count += 1
462
return count
463
464
@staticmethod
465
def getAllTradesByStrategy(strategy):
466
tradesByStrategy = []
467
for trade in TradeManager.trades:
468
if trade.strategy == strategy:
469
tradesByStrategy.append(trade)
470
return tradesByStrategy
471
472
@staticmethod
473
def convertJSONToTrade(jsonData):
474
trade = Trade(jsonData['tradingSymbol'])
475
trade.tradeID = jsonData['tradeID']
476
trade.strategy = jsonData['strategy']
477
trade.direction = jsonData['direction']
478
trade.productType = jsonData['productType']
479
trade.isFutures = jsonData['isFutures']
480
trade.isOptions = jsonData['isOptions']
481
trade.optionType = jsonData['optionType']
482
trade.placeMarketOrder = jsonData['placeMarketOrder']
483
trade.intradaySquareOffTimestamp = jsonData['intradaySquareOffTimestamp']
484
trade.requestedEntry = jsonData['requestedEntry']
485
trade.entry = jsonData['entry']
486
trade.qty = jsonData['qty']
487
trade.filledQty = jsonData['filledQty']
488
trade.initialStopLoss = jsonData['initialStopLoss']
489
trade.stopLoss = jsonData['stopLoss']
490
trade.target = jsonData['target']
491
trade.cmp = jsonData['cmp']
492
trade.tradeState = jsonData['tradeState']
493
trade.timestamp = jsonData['timestamp']
494
trade.createTimestamp = jsonData['createTimestamp']
495
trade.startTimestamp = jsonData['startTimestamp']
496
trade.endTimestamp = jsonData['endTimestamp']
497
trade.pnl = jsonData['pnl']
498
trade.pnlPercentage = jsonData['pnlPercentage']
499
trade.exit = jsonData['exit']
500
trade.exitReason = jsonData['exitReason']
501
trade.exchange = jsonData['exchange']
502
trade.entryOrder = TradeManager.convertJSONToOrder(jsonData['entryOrder'])
503
trade.slOrder = TradeManager.convertJSONToOrder(jsonData['slOrder'])
504
trade.targetOrder = TradeManager.convertJSONToOrder(jsonData['targetOrder'])
505
return trade
506
507
@staticmethod
508
def convertJSONToOrder(jsonData):
509
if jsonData == None:
510
return None
511
order = Order()
512
order.tradingSymbol = jsonData['tradingSymbol']
513
order.exchange = jsonData['exchange']
514
order.productType = jsonData['productType']
515
order.orderType = jsonData['orderType']
516
order.price = jsonData['price']
517
order.triggerPrice = jsonData['triggerPrice']
518
order.qty = jsonData['qty']
519
order.orderId = jsonData['orderId']
520
order.orderStatus = jsonData['orderStatus']
521
order.averagePrice = jsonData['averagePrice']
522
order.filledQty = jsonData['filledQty']
523
order.pendingQty = jsonData['pendingQty']
524
order.orderPlaceTimestamp = jsonData['orderPlaceTimestamp']
525
order.lastOrderUpdateTimestamp = jsonData['lastOrderUpdateTimestamp']
526
order.message = jsonData['message']
527
return order
528
529
@staticmethod
530
def getLastTradedPrice(tradingSymbol):
531
return TradeManager.symbolToCMPMap[tradingSymbol]
532
533