├── .gitignore ├── README.md ├── config ├── brokerapp.json ├── holidays.json ├── server.json ├── system.json └── telegram.json └── src ├── Test.py ├── config └── Config.py ├── core ├── Algo.py ├── Controller.py └── Quotes.py ├── instruments └── Instruments.py ├── loginmgmt ├── BaseLogin.py └── ZerodhaLogin.py ├── main.py ├── models ├── BrokerAppDetails.py ├── Direction.py ├── OptionBuying.py ├── OrderStatus.py ├── OrderType.py ├── ProductType.py ├── Quote.py ├── Segment.py └── TickData.py ├── ordermgmt ├── BaseOrderManager.py ├── Order.py ├── OrderInputParams.py ├── OrderModifyParams.py └── ZerodhaOrderManager.py ├── restapis ├── BrokerLoginAPI.py ├── HoldingsAPI.py ├── HomeAPI.py ├── PositionsAPI.py └── StartAlgoAPI.py ├── strategies ├── BNFORB30Min.py ├── BaseStrategy.py ├── OptionBuyingStrategy.py ├── OptionSelling.py ├── SampleStrategy.py └── ShortStraddleBNF.py ├── templates ├── index.html ├── index_algostarted.html └── index_loggedin.html ├── ticker ├── BaseTicker.py └── ZerodhaTicker.py ├── trademgmt ├── Trade.py ├── TradeEncoder.py ├── TradeExitReason.py ├── TradeManager.py └── TradeState.py └── utils └── Utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # algo_tradiing 2 | This is Algo trading written in Python 3 | 4 | - Required Module 5 | - pip install flask 6 | - pip install --upgrade kiteconnect 7 | 8 | Local setup 9 | - Create a folder for deploy and logs ( Folder names should be defined in /config/server.json ) 10 | - Update clientID,appKey and appSecret in /config/brokerapp.json 11 | - Make sure to define the trading holidays in /config.holidays.json 12 | -------------------------------------------------------------------------------- /config/brokerapp.json: -------------------------------------------------------------------------------- 1 | { 2 | "broker": "zerodha", 3 | "clientID": "dummy", 4 | "appKey": "dummy", 5 | "appSecret": "dummy", 6 | "redirectUrl": "http://localhost:8080/apis/broker/login/zerodha" 7 | } -------------------------------------------------------------------------------- /config/holidays.json: -------------------------------------------------------------------------------- 1 | [ 2 | "2021-01-26", 3 | "2021-03-11", 4 | "2021-03-29", 5 | "2021-04-02", 6 | "2021-04-14", 7 | "2021-04-21", 8 | "2021-05-13", 9 | "2021-07-21", 10 | "2021-08-19", 11 | "2021-09-10", 12 | "2021-10-15", 13 | "2021-11-04", 14 | "2021-11-05", 15 | "2021-11-19" 16 | ] -------------------------------------------------------------------------------- /config/server.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": 8080, 3 | "enableSSL": false, 4 | "sslPort": 8443, 5 | "deployDir": "G:/Algo Trading/python/git/python-deploy", 6 | "logFileDir": "G:/Algo Trading/python/git/python-deploy/logs" 7 | } -------------------------------------------------------------------------------- /config/system.json: -------------------------------------------------------------------------------- 1 | { 2 | "homeUrl": "http://localhost:8080" 3 | } -------------------------------------------------------------------------------- /config/telegram.json: -------------------------------------------------------------------------------- 1 | { 2 | "bot_token":"dummy", 3 | "bot_chat_id":"dummy" 4 | } -------------------------------------------------------------------------------- /src/Test.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | 4 | from core.Controller import Controller 5 | from ticker.ZerodhaTicker import ZerodhaTicker 6 | from ordermgmt.ZerodhaOrderManager import ZerodhaOrderManager 7 | from ordermgmt.Order import Order 8 | from core.Quotes import Quotes 9 | from utils.Utils import Utils 10 | 11 | class Test: 12 | def testTicker(): 13 | ticker = ZerodhaTicker() 14 | ticker.startTicker() 15 | ticker.registerListener(Test.tickerListener) 16 | 17 | # sleep for 5 seconds and register trading symbols to receive ticks 18 | time.sleep(5) 19 | ticker.registerSymbols(['SBIN', 'RELIANCE']) 20 | 21 | # wait for 60 seconds and stop ticker service 22 | time.sleep(60) 23 | logging.info('Going to stop ticker') 24 | ticker.stopTicker() 25 | 26 | def tickerListener(tick): 27 | logging.info('tickerLister: onNewTick %s', vars(tick)); 28 | 29 | def testOrders(): 30 | orderManager = ZerodhaOrderManager() 31 | exchange = 'NSE'; 32 | tradingSymbol = 'SBIN' 33 | lastTradedPrice = Quotes.getCMP(exchange + ':' + tradingSymbol) 34 | logging.info(tradingSymbol + ' CMP = %f', lastTradedPrice) 35 | 36 | limitPrice = lastTradedPrice - lastTradedPrice * 1 / 100 37 | limitPrice = Utils.roundToNSEPrice(limitPrice) 38 | qty = 1 39 | direction = 'LONG' 40 | 41 | # place order 42 | origOrderId = orderManager.placeOrder(tradingSymbol, limitPrice, qty, direction) 43 | logging.info('Original order Id %s', origOrderId) 44 | 45 | # sleep for 10 seconds then modify order 46 | time.sleep(10) 47 | newPrice = lastTradedPrice 48 | if origOrderId: 49 | orderManager.modifyOrder(origOrderId, newPrice) 50 | 51 | # sleep for 10 seconds and then place SL order 52 | time.sleep(10) 53 | slPrice = newPrice - newPrice * 1 / 100 54 | slPrice = Utils.roundToNSEPrice(slPrice) 55 | slDirection = 'SHORT' if direction == 'LONG' else 'LONG' 56 | slOrderId = orderManager.placeSLOrder(tradingSymbol, slPrice, qty, slDirection) 57 | logging.info('SL order Id %s', slOrderId) 58 | 59 | # sleep for 10 seconds and then place target order 60 | time.sleep(10) 61 | targetPrice = newPrice + newPrice * 2 / 100 62 | targetPrice = Utils.roundToNSEPrice(targetPrice) 63 | targetDirection = 'SHORT' if direction == 'LONG' else 'LONG' 64 | targetOrderId = orderManager.placeOrder(tradingSymbol, targetPrice, qty, targetDirection) 65 | logging.info('Target order Id %s', targetOrderId) 66 | 67 | # sleep for 10 seconds and cancel target order 68 | time.sleep(10) 69 | if targetOrderId: 70 | orderManager.cancelOrder(targetOrderId) 71 | logging.info('Cancelled Target order Id %s', targetOrderId) 72 | 73 | logging.info("Algo done executing all orders. Check ur orders and positions in broker terminal.") 74 | 75 | def testMisc(): 76 | orderManager = ZerodhaOrderManager() 77 | sampleOrder = Order(orderInputParams=None) 78 | sampleOrder.orderId='210505200078243' 79 | orders = [] 80 | orders.append(sampleOrder) 81 | orderManager.fetchAndUpdateAllOrderDetails(orders) -------------------------------------------------------------------------------- /src/config/Config.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | def getServerConfig(): 5 | with open('../config/server.json', 'r') as server: 6 | jsonServerData = json.load(server) 7 | return jsonServerData 8 | 9 | def getSystemConfig(): 10 | with open('../config/system.json', 'r') as system: 11 | jsonSystemData = json.load(system) 12 | return jsonSystemData 13 | 14 | def getBrokerAppConfig(): 15 | with open('../config/brokerapp.json', 'r') as brokerapp: 16 | jsonUserData = json.load(brokerapp) 17 | return jsonUserData 18 | 19 | def getTelegramAppConfig(): 20 | with open('../config/telegram.json', 'r') as telegramapp: 21 | jsonUserData = json.load(telegramapp) 22 | return jsonUserData 23 | 24 | def getHolidays(): 25 | with open('../config/holidays.json', 'r') as holidays: 26 | holidaysData = json.load(holidays) 27 | return holidaysData 28 | 29 | def getTimestampsData(): 30 | serverConfig = getServerConfig() 31 | timestampsFilePath = os.path.join(serverConfig['deployDir'], 'timestamps.json') 32 | if os.path.exists(timestampsFilePath) == False: 33 | return {} 34 | timestampsFile = open(timestampsFilePath, 'r') 35 | timestamps = json.loads(timestampsFile.read()) 36 | return timestamps 37 | 38 | def saveTimestampsData(timestamps = {}): 39 | serverConfig = getServerConfig() 40 | timestampsFilePath = os.path.join(serverConfig['deployDir'], 'timestamps.json') 41 | with open(timestampsFilePath, 'w') as timestampsFile: 42 | json.dump(timestamps, timestampsFile, indent=2) 43 | print("saved timestamps data to file " + timestampsFilePath) 44 | -------------------------------------------------------------------------------- /src/core/Algo.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import threading 3 | import time 4 | 5 | from instruments.Instruments import Instruments 6 | 7 | from trademgmt.TradeManager import TradeManager 8 | 9 | from strategies.SampleStrategy import SampleStrategy 10 | from strategies.BNFORB30Min import BNFORB30Min 11 | from strategies.OptionSelling import OptionSelling 12 | from strategies.ShortStraddleBNF import ShortStraddleBNF 13 | from strategies.OptionBuyingStrategy import OptionBuyingStrategy 14 | 15 | #from Test import Test 16 | 17 | class Algo: 18 | isAlgoRunning = None 19 | 20 | @staticmethod 21 | def startAlgo(): 22 | if Algo.isAlgoRunning == True: 23 | logging.info("Algo has already started..") 24 | return 25 | 26 | logging.info("Starting Algo...") 27 | Instruments.fetchInstruments() 28 | 29 | # start trade manager in a separate thread 30 | tm = threading.Thread(target=TradeManager.run) 31 | tm.start() 32 | 33 | # sleep for 2 seconds for TradeManager to get initialized 34 | time.sleep(2) 35 | 36 | # start running strategies: Run each strategy in a separate thread 37 | #threading.Thread(target=SampleStrategy.getInstance().run).start() 38 | #threading.Thread(target=BNFORB30Min.getInstance().run).start() 39 | #threading.Thread(target=OptionSelling.getInstance().run).start() 40 | threading.Thread(target=ShortStraddleBNF.getInstance().run).start() 41 | #threading.Thread(target=OptionBuyingStrategy.getInstance().run).start() 42 | #threading.Thread(target=TestStrategy.getInstance().run).start() 43 | 44 | Algo.isAlgoRunning = True 45 | logging.info("Algo started.") 46 | -------------------------------------------------------------------------------- /src/core/Controller.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from config.Config import getBrokerAppConfig 4 | from models.BrokerAppDetails import BrokerAppDetails 5 | from loginmgmt.ZerodhaLogin import ZerodhaLogin 6 | 7 | class Controller: 8 | brokerLogin = None # static variable 9 | brokerName = None # static variable 10 | 11 | def handleBrokerLogin(args): 12 | brokerAppConfig = getBrokerAppConfig() 13 | 14 | brokerAppDetails = BrokerAppDetails(brokerAppConfig['broker']) 15 | brokerAppDetails.setClientID(brokerAppConfig['clientID']) 16 | brokerAppDetails.setAppKey(brokerAppConfig['appKey']) 17 | brokerAppDetails.setAppSecret(brokerAppConfig['appSecret']) 18 | 19 | logging.info('handleBrokerLogin appKey %s', brokerAppDetails.appKey) 20 | Controller.brokerName = brokerAppDetails.broker 21 | if Controller.brokerName == 'zerodha': 22 | Controller.brokerLogin = ZerodhaLogin(brokerAppDetails) 23 | # Other brokers - not implemented 24 | #elif Controller.brokerName == 'fyers': 25 | #Controller.brokerLogin = FyersLogin(brokerAppDetails) 26 | 27 | redirectUrl = Controller.brokerLogin.login(args) 28 | return redirectUrl 29 | 30 | def getBrokerLogin(): 31 | return Controller.brokerLogin 32 | 33 | def getBrokerName(): 34 | return Controller.brokerName 35 | -------------------------------------------------------------------------------- /src/core/Quotes.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from core.Controller import Controller 4 | from models.Quote import Quote 5 | from models.OptionBuying import OptionBuying 6 | 7 | class Quotes: 8 | @staticmethod 9 | def getQuote(tradingSymbol, isFnO = False): 10 | broker = Controller.getBrokerName() 11 | brokerHandle = Controller.getBrokerLogin().getBrokerHandle() 12 | quote = None 13 | if broker == "zerodha": 14 | key = ('NFO:' + tradingSymbol) if isFnO == True else ('NSE:' + tradingSymbol) 15 | bQuoteResp = brokerHandle.quote(key) 16 | bQuote = bQuoteResp[key] 17 | # convert broker quote to our system quote 18 | quote = Quote(tradingSymbol) 19 | quote.tradingSymbol = tradingSymbol 20 | quote.lastTradedPrice = bQuote['last_price'] 21 | quote.lastTradedQuantity = bQuote['last_quantity'] 22 | quote.avgTradedPrice = bQuote['average_price'] 23 | quote.volume = bQuote['volume'] 24 | quote.totalBuyQuantity = bQuote['buy_quantity'] 25 | quote.totalSellQuantity = bQuote['sell_quantity'] 26 | ohlc = bQuote['ohlc'] 27 | quote.open = ohlc['open'] 28 | quote.high = ohlc['high'] 29 | quote.low = ohlc['low'] 30 | quote.close = ohlc['close'] 31 | quote.change = bQuote['net_change'] 32 | quote.oiDayHigh = bQuote['oi_day_high'] 33 | quote.oiDayLow = bQuote['oi_day_low'] 34 | quote.lowerCiruitLimit = bQuote['lower_circuit_limit'] 35 | quote.upperCircuitLimit = bQuote['upper_circuit_limit'] 36 | else: 37 | # The logic may be different for other brokers 38 | quote = None 39 | return quote 40 | 41 | @staticmethod 42 | def getCMP(tradingSymbol): 43 | quote = Quotes.getQuote(tradingSymbol) 44 | if quote: 45 | return quote.lastTradedPrice 46 | else: 47 | return 0 48 | 49 | @staticmethod 50 | def getStrikePrice(tradingSymbol): 51 | broker = Controller.getBrokerName() 52 | brokerHandle = Controller.getBrokerLogin().getBrokerHandle() 53 | quote = None 54 | if broker == "zerodha": 55 | key = 'NSE:' + tradingSymbol 56 | bQuoteResp = brokerHandle.quote(key) 57 | quote = bQuoteResp[key] 58 | if quote: 59 | return quote['last_price'] 60 | else: 61 | return 0 62 | else: 63 | # The logic may be different for other brokers 64 | quote = None 65 | return quote 66 | 67 | @staticmethod 68 | def getOptionBuyingQuote(tradingSymbol,isFnO): 69 | quote = Quotes.getQuote(tradingSymbol,isFnO) 70 | if quote: 71 | # convert quote to Option buying details 72 | optionBuying = OptionBuying(tradingSymbol) 73 | optionBuying.lastTradedPrice = quote.lastTradedPrice 74 | optionBuying.high = quote.high 75 | optionBuying.low = quote.low 76 | optionBuying.entryPrice= (quote.low*1.8) 77 | optionBuying.stopLoss=(quote.low*1.8)-20 78 | optionBuying.target=(quote.low*1.8)+40 79 | optionBuying.isTradeLive=False 80 | else: 81 | optionBuying= None 82 | return optionBuying -------------------------------------------------------------------------------- /src/instruments/Instruments.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import json 4 | 5 | from config.Config import getServerConfig, getTimestampsData, saveTimestampsData 6 | from core.Controller import Controller 7 | from utils.Utils import Utils 8 | 9 | class Instruments: 10 | instrumentsList = None 11 | symbolToInstrumentMap = None 12 | tokenToInstrumentMap = None 13 | 14 | @staticmethod 15 | def shouldFetchFromServer(): 16 | timestamps = getTimestampsData() 17 | if 'instrumentsLastSavedAt' not in timestamps: 18 | return True 19 | lastSavedTimestamp = timestamps['instrumentsLastSavedAt'] 20 | nowEpoch = Utils.getEpoch() 21 | if nowEpoch - lastSavedTimestamp >= 24 * 60* 60: 22 | logging.info("Instruments: shouldFetchFromServer() returning True as its been 24 hours since last fetch.") 23 | return True 24 | return False 25 | 26 | @staticmethod 27 | def updateLastSavedTimestamp(): 28 | timestamps = getTimestampsData() 29 | timestamps['instrumentsLastSavedAt'] = Utils.getEpoch() 30 | saveTimestampsData(timestamps) 31 | 32 | @staticmethod 33 | def loadInstruments(): 34 | serverConfig = getServerConfig() 35 | instrumentsFilepath = os.path.join(serverConfig['deployDir'], 'instruments.json') 36 | if os.path.exists(instrumentsFilepath) == False: 37 | logging.warn('Instruments: instrumentsFilepath %s does not exist', instrumentsFilepath) 38 | return [] # returns empty list 39 | 40 | isdFile = open(instrumentsFilepath, 'r') 41 | instruments = json.loads(isdFile.read()) 42 | logging.info('Instruments: loaded %d instruments from file %s', len(instruments), instrumentsFilepath) 43 | return instruments 44 | 45 | @staticmethod 46 | def saveInstruments(instruments = []): 47 | serverConfig = getServerConfig() 48 | instrumentsFilepath = os.path.join(serverConfig['deployDir'], 'instruments.json') 49 | with open(instrumentsFilepath, 'w') as isdFile: 50 | json.dump(instruments, isdFile, indent=2, default=str) 51 | logging.info('Instruments: Saved %d instruments to file %s', len(instruments), instrumentsFilepath) 52 | # Update last save timestamp 53 | Instruments.updateLastSavedTimestamp() 54 | 55 | @staticmethod 56 | def fetchInstrumentsFromServer(): 57 | instrumentsList = [] 58 | try: 59 | brokerHandle = Controller.getBrokerLogin().getBrokerHandle() 60 | logging.info('Going to fetch instruments from server...') 61 | instrumentsList = brokerHandle.instruments('NSE') 62 | instrumentsListFnO = brokerHandle.instruments('NFO') 63 | # Add FnO instrument list to the main list 64 | instrumentsList.extend(instrumentsListFnO) 65 | logging.info('Fetched %d instruments from server.', len(instrumentsList)) 66 | except Exception as e: 67 | logging.exception("Exception while fetching instruments from server") 68 | return instrumentsList 69 | 70 | @staticmethod 71 | def fetchInstruments(): 72 | if Instruments.instrumentsList: 73 | return Instruments.instrumentsList 74 | 75 | instrumentsList = Instruments.loadInstruments() 76 | if len(instrumentsList) == 0 or Instruments.shouldFetchFromServer() == True: 77 | instrumentsList = Instruments.fetchInstrumentsFromServer() 78 | # Save instruments to file locally 79 | if len(instrumentsList) > 0: 80 | Instruments.saveInstruments(instrumentsList) 81 | 82 | if len(instrumentsList) == 0: 83 | print("Could not fetch/load instruments data. Hence exiting the app.") 84 | logging.error("Could not fetch/load instruments data. Hence exiting the app."); 85 | exit(-2) 86 | 87 | Instruments.symbolToInstrumentMap = {} 88 | Instruments.tokenToInstrumentMap = {} 89 | for isd in instrumentsList: 90 | tradingSymbol = isd['tradingsymbol'] 91 | instrumentToken = isd['instrument_token'] 92 | # logging.info('%s = %d', tradingSymbol, instrumentToken) 93 | Instruments.symbolToInstrumentMap[tradingSymbol] = isd 94 | Instruments.tokenToInstrumentMap[instrumentToken] = isd 95 | 96 | logging.info('Fetching instruments done. Instruments count = %d', len(instrumentsList)) 97 | Instruments.instrumentsList = instrumentsList # assign the list to static variable 98 | return instrumentsList 99 | 100 | @staticmethod 101 | def getInstrumentDataBySymbol(tradingSymbol): 102 | return Instruments.symbolToInstrumentMap[tradingSymbol] 103 | 104 | @staticmethod 105 | def getInstrumentDataByToken(instrumentToken): 106 | return Instruments.tokenToInstrumentMap[instrumentToken] 107 | -------------------------------------------------------------------------------- /src/loginmgmt/BaseLogin.py: -------------------------------------------------------------------------------- 1 | 2 | class BaseLogin: 3 | 4 | def __init__(self, brokerAppDetails): 5 | self.brokerAppDetails = brokerAppDetails 6 | self.broker = brokerAppDetails.broker 7 | 8 | # Derived class should implement login function and return redirect url 9 | def login(self, args): 10 | pass 11 | 12 | def setBrokerHandle(self, brokerHandle): 13 | self.brokerHandle = brokerHandle 14 | 15 | def setAccessToken(self, accessToken): 16 | self.accessToken = accessToken 17 | 18 | def getBrokerAppDetails(self): 19 | return self.brokerAppDetails 20 | 21 | def getAccessToken(self): 22 | return self.accessToken 23 | 24 | def getBrokerHandle(self): 25 | return self.brokerHandle -------------------------------------------------------------------------------- /src/loginmgmt/ZerodhaLogin.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from kiteconnect import KiteConnect 3 | 4 | from config.Config import getSystemConfig 5 | from loginmgmt.BaseLogin import BaseLogin 6 | 7 | class ZerodhaLogin(BaseLogin): 8 | def __init__(self, brokerAppDetails): 9 | BaseLogin.__init__(self, brokerAppDetails) 10 | 11 | def login(self, args): 12 | logging.info('==> ZerodhaLogin .args => %s', args); 13 | systemConfig = getSystemConfig() 14 | brokerHandle = KiteConnect(api_key=self.brokerAppDetails.appKey) 15 | redirectUrl = None 16 | if 'request_token' in args: 17 | requestToken = args['request_token'] 18 | logging.info('Zerodha requestToken = %s', requestToken) 19 | session = brokerHandle.generate_session(requestToken, api_secret=self.brokerAppDetails.appSecret) 20 | 21 | accessToken = session['access_token'] 22 | accessToken = accessToken 23 | logging.info('Zerodha accessToken = %s', accessToken) 24 | brokerHandle.set_access_token(accessToken) 25 | 26 | logging.info('Zerodha Login successful. accessToken = %s', accessToken) 27 | 28 | # set broker handle and access token to the instance 29 | self.setBrokerHandle(brokerHandle) 30 | self.setAccessToken(accessToken) 31 | 32 | # redirect to home page with query param loggedIn=true 33 | homeUrl = systemConfig['homeUrl'] + '?loggedIn=true' 34 | logging.info('Zerodha Redirecting to home page %s', homeUrl) 35 | redirectUrl = homeUrl 36 | else: 37 | loginUrl = brokerHandle.login_url() 38 | logging.info('Redirecting to zerodha login url = %s', loginUrl) 39 | redirectUrl = loginUrl 40 | 41 | return redirectUrl 42 | 43 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from flask import Flask 4 | 5 | from config.Config import getBrokerAppConfig, getServerConfig, getSystemConfig 6 | from restapis.HomeAPI import HomeAPI 7 | from restapis.BrokerLoginAPI import BrokerLoginAPI 8 | from restapis.StartAlgoAPI import StartAlgoAPI 9 | from restapis.PositionsAPI import PositionsAPI 10 | from restapis.HoldingsAPI import HoldingsAPI 11 | 12 | app = Flask(__name__) 13 | app.config['DEBUG'] = True 14 | 15 | app.add_url_rule("/", view_func=HomeAPI.as_view("home_api")) 16 | app.add_url_rule("/apis/broker/login/zerodha", view_func=BrokerLoginAPI.as_view("broker_login_api")) 17 | app.add_url_rule("/apis/algo/start", view_func=StartAlgoAPI.as_view("start_algo_api")) 18 | app.add_url_rule("/positions", view_func=PositionsAPI.as_view("positions_api")) 19 | app.add_url_rule("/holdings", view_func=HoldingsAPI.as_view("holdings_api")) 20 | 21 | def initLoggingConfg(filepath): 22 | format = "%(asctime)s: %(message)s" 23 | logging.basicConfig(filename=filepath, format=format, level=logging.INFO, datefmt="%Y-%m-%d %H:%M:%S") 24 | 25 | # Execution starts here 26 | serverConfig = getServerConfig() 27 | 28 | deployDir = serverConfig['deployDir'] 29 | if os.path.exists(deployDir) == False: 30 | print("Deploy Directory " + deployDir + " does not exist. Exiting the app.") 31 | exit(-1) 32 | 33 | logFileDir = serverConfig['logFileDir'] 34 | if os.path.exists(logFileDir) == False: 35 | print("LogFile Directory " + logFileDir + " does not exist. Exiting the app.") 36 | exit(-1) 37 | 38 | print("Deploy Directory = " + deployDir) 39 | print("LogFile Directory = " + logFileDir) 40 | initLoggingConfg(logFileDir + "/app.log") 41 | 42 | logging.info('serverConfig => %s', serverConfig) 43 | 44 | brokerAppConfig = getBrokerAppConfig() 45 | #logging.info('brokerAppConfig => %s', brokerAppConfig) 46 | 47 | port = serverConfig['port'] 48 | 49 | app.run('localhost', port) -------------------------------------------------------------------------------- /src/models/BrokerAppDetails.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | class BrokerAppDetails: 4 | def __init__(self, broker): 5 | self.broker = broker 6 | self.appKey = None 7 | self.appSecret = None 8 | 9 | def setClientID(self, clientID): 10 | self.clientID = clientID 11 | 12 | def setAppKey(self, appKey): 13 | self.appKey = appKey 14 | 15 | def setAppSecret(self, appSecret): 16 | self.appSecret = appSecret 17 | 18 | -------------------------------------------------------------------------------- /src/models/Direction.py: -------------------------------------------------------------------------------- 1 | 2 | class Direction: 3 | LONG = "LONG" 4 | SHORT = "SHORT" 5 | -------------------------------------------------------------------------------- /src/models/OptionBuying.py: -------------------------------------------------------------------------------- 1 | 2 | class OptionBuying: 3 | def __init__(self, tradingSymbol): 4 | self.tradingSymbol = tradingSymbol 5 | self.lastTradedPrice = 0 6 | self.high = 0 7 | self.low = 0 8 | self.entryPrice=0 9 | self.stopLoss=0 10 | self.target=0 11 | self.isTradeLive=False 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/models/OrderStatus.py: -------------------------------------------------------------------------------- 1 | 2 | class OrderStatus: 3 | OPEN = "OPEN" 4 | COMPLETE = "COMPLETE" 5 | OPEN_PENDING = "OPEN PENDING" 6 | VALIDATION_PENDING = "VALIDATION PENDING" 7 | PUT_ORDER_REQ_RECEIVED = "PUT ORDER REQ RECEIVED" 8 | TRIGGER_PENDING = "TRIGGER PENDING" 9 | REJECTED = "REJECTED" 10 | CANCELLED = "CANCELLED" 11 | -------------------------------------------------------------------------------- /src/models/OrderType.py: -------------------------------------------------------------------------------- 1 | 2 | class OrderType: 3 | LIMIT = "LIMIT" 4 | MARKET = "MARKET" 5 | SL_MARKET = "SL_MARKET" 6 | SL_LIMIT = "SL_LIMIT" 7 | -------------------------------------------------------------------------------- /src/models/ProductType.py: -------------------------------------------------------------------------------- 1 | 2 | class ProductType: 3 | MIS = "MIS" 4 | NRML = "NRML" 5 | CNC = "CNC" -------------------------------------------------------------------------------- /src/models/Quote.py: -------------------------------------------------------------------------------- 1 | 2 | class Quote: 3 | def __init__(self, tradingSymbol): 4 | self.tradingSymbol = tradingSymbol 5 | self.lastTradedPrice = 0 6 | self.lastTradedQuantity = 0 7 | self.avgTradedPrice = 0 8 | self.volume = 0 9 | self.totalBuyQuantity = 0 10 | self.totalSellQuantity = 0 11 | self.open = 0 12 | self.high = 0 13 | self.low = 0 14 | self.close = 0 15 | self.change = 0 16 | self.oiDayHigh = 0 17 | self.oiDayLow = 0 18 | self.lowerCiruitLimit = 0 19 | self.upperCircuitLimit = 0 -------------------------------------------------------------------------------- /src/models/Segment.py: -------------------------------------------------------------------------------- 1 | 2 | class Segment: 3 | EQUITY = "EQUITY" 4 | FNO = "FNO" 5 | CURRENCY = "CURRENCY" 6 | COMMADITY = "COMMADITY" 7 | -------------------------------------------------------------------------------- /src/models/TickData.py: -------------------------------------------------------------------------------- 1 | 2 | class TickData: 3 | def __init__(self, tradingSymbol): 4 | self.tradingSymbol = tradingSymbol 5 | self.lastTradedPrice = 0 6 | self.lastTradedQuantity = 0 7 | self.avgTradedPrice = 0 8 | self.volume = 0 9 | self.totalBuyQuantity = 0 10 | self.totalSellQuantity = 0 11 | self.open = 0 12 | self.high = 0 13 | self.low = 0 14 | self.close = 0 15 | self.change = 0 16 | -------------------------------------------------------------------------------- /src/ordermgmt/BaseOrderManager.py: -------------------------------------------------------------------------------- 1 | 2 | from core.Controller import Controller 3 | 4 | class BaseOrderManager: 5 | def __init__(self, broker): 6 | self.broker = broker 7 | self.brokerHandle = Controller.getBrokerLogin().getBrokerHandle() 8 | 9 | def placeOrder(self, orderInputParams): 10 | pass 11 | 12 | def modifyOrder(self, order, orderModifyParams): 13 | pass 14 | 15 | def modifyOrderToMarket(self, order): 16 | pass 17 | 18 | def cancelOrder(self, order): 19 | pass 20 | 21 | def fetchAndUpdateAllOrderDetails(self, orders): 22 | pass 23 | 24 | def convertToBrokerProductType(self, productType): 25 | return productType 26 | 27 | def convertToBrokerOrderType(self, orderType): 28 | return orderType 29 | 30 | def convertToBrokerDirection(self, direction): 31 | return direction 32 | -------------------------------------------------------------------------------- /src/ordermgmt/Order.py: -------------------------------------------------------------------------------- 1 | 2 | class Order: 3 | def __init__(self, orderInputParams = None): 4 | self.tradingSymbol = orderInputParams.tradingSymbol if orderInputParams != None else "" 5 | self.exchange = orderInputParams.exchange if orderInputParams != None else "NSE" 6 | self.productType = orderInputParams.productType if orderInputParams != None else "" 7 | self.orderType = orderInputParams.orderType if orderInputParams != None else "" # LIMIT/MARKET/SL-LIMIT/SL-MARKET 8 | self.price = orderInputParams.price if orderInputParams != None else 0 9 | self.triggerPrice = orderInputParams.triggerPrice if orderInputParams != None else 0 # Applicable in case of SL orders 10 | self.qty = orderInputParams.qty if orderInputParams != None else 0 11 | self.orderId = None # The order id received from broker after placing the order 12 | self.orderStatus = None # One of the status defined in ordermgmt.OrderStatus 13 | self.averagePrice = 0 # Average price at which the order is filled 14 | self.filledQty = 0 # Filled quantity 15 | self.pendingQty = 0 # Qty - Filled quantity 16 | self.orderPlaceTimestamp = None # Timestamp when the order is placed 17 | self.lastOrderUpdateTimestamp = None # Applicable if you modify the order Ex: Trailing SL 18 | self.message = None # In case any order rejection or any other error save the response from broker in this field 19 | 20 | def __str__(self): 21 | return "orderId=" + str(self.orderId) + ", orderStatus=" + str(self.orderStatus) \ 22 | + ", symbol=" + str(self.tradingSymbol) + ", productType=" + str(self.productType) \ 23 | + ", orderType=" + str(self.orderType) + ", price=" + str(self.price) \ 24 | + ", triggerPrice=" + str(self.triggerPrice) + ", qty=" + str(self.qty) \ 25 | + ", filledQty=" + str(self.filledQty) + ", pendingQty=" + str(self.pendingQty) \ 26 | + ", averagePrice=" + str(self.averagePrice) 27 | -------------------------------------------------------------------------------- /src/ordermgmt/OrderInputParams.py: -------------------------------------------------------------------------------- 1 | 2 | from models.Segment import Segment 3 | from models.ProductType import ProductType 4 | 5 | class OrderInputParams: 6 | def __init__(self, tradingSymbol): 7 | self.exchange = "NSE" # default 8 | self.isFnO = False 9 | self.segment = Segment.EQUITY # default 10 | self.productType = ProductType.MIS # default 11 | self.tradingSymbol = tradingSymbol 12 | self.direction = "" 13 | self.orderType = "" # One of the values of ordermgmt.OrderType 14 | self.qty = 0 15 | self.price = 0 16 | self.triggerPrice = 0 # Applicable in case of SL order 17 | 18 | def __str__(self): 19 | return "symbol=" + str(self.tradingSymbol) + ", exchange=" + self.exchange \ 20 | + ", productType=" + self.productType + ", segment=" + self.segment \ 21 | + ", direction=" + self.direction + ", orderType=" + self.orderType \ 22 | + ", qty=" + str(self.qty) + ", price=" + str(self.price) + ", triggerPrice=" + str(self.triggerPrice) \ 23 | + ", isFnO=" + str(self.isFnO) 24 | -------------------------------------------------------------------------------- /src/ordermgmt/OrderModifyParams.py: -------------------------------------------------------------------------------- 1 | 2 | class OrderModifyParams: 3 | def __init__(self): 4 | self.newPrice = 0 5 | self.newTriggerPrice = 0 # Applicable in case of SL order 6 | self.newQty = 0 7 | self.newOrderType = None # Ex: Can change LIMIT order to SL order or vice versa. Not supported by all brokers 8 | 9 | def __str__(self): 10 | return "newPrice=" + str(self.newPrice) + ", newTriggerPrice=" + str(self.newTriggerPrice) \ 11 | + ", newQty=" + str(self.newQty) + ", newOrderType=" + str(self.newOrderType) 12 | -------------------------------------------------------------------------------- /src/ordermgmt/ZerodhaOrderManager.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from ordermgmt.BaseOrderManager import BaseOrderManager 4 | from ordermgmt.Order import Order 5 | 6 | from models.ProductType import ProductType 7 | from models.OrderType import OrderType 8 | from models.Direction import Direction 9 | from models.OrderStatus import OrderStatus 10 | 11 | from utils.Utils import Utils 12 | 13 | class ZerodhaOrderManager(BaseOrderManager): 14 | def __init__(self): 15 | super().__init__("zerodha") 16 | 17 | def placeOrder(self, orderInputParams): 18 | message="{0}: Going to place order with params {1}".format(self.broker,orderInputParams) 19 | Utils.sendMessageTelegramBot(message) 20 | logging.info(message) 21 | kite = self.brokerHandle 22 | try: 23 | orderId = kite.place_order( 24 | variety=kite.VARIETY_REGULAR, 25 | exchange=kite.EXCHANGE_NFO if orderInputParams.isFnO == True else kite.EXCHANGE_NSE, 26 | tradingsymbol=orderInputParams.tradingSymbol, 27 | transaction_type=self.convertToBrokerDirection(orderInputParams.direction), 28 | quantity=orderInputParams.qty, 29 | price=orderInputParams.price, 30 | trigger_price=orderInputParams.triggerPrice, 31 | product=self.convertToBrokerProductType(orderInputParams.productType), 32 | order_type=self.convertToBrokerOrderType(orderInputParams.orderType)) 33 | 34 | message="{0}: Order placed successfully, orderId {1}".format(self.broker,orderId) 35 | Utils.sendMessageTelegramBot(message) 36 | logging.info(message) 37 | order = Order(orderInputParams) 38 | order.orderId = orderId 39 | order.orderPlaceTimestamp = Utils.getEpoch() 40 | order.lastOrderUpdateTimestamp = Utils.getEpoch() 41 | return order 42 | except Exception as e: 43 | logging.info('%s Order placement failed: %s', self.broker, str(e)) 44 | raise Exception(str(e)) 45 | 46 | def modifyOrder(self, order, orderModifyParams): 47 | logging.info('%s: Going to modify order with params %s', self.broker, orderModifyParams) 48 | kite = self.brokerHandle 49 | try: 50 | orderId = kite.modify_order( 51 | variety=kite.VARIETY_REGULAR, 52 | order_id=order.orderId, 53 | quantity=orderModifyParams.newQty if orderModifyParams.newQty > 0 else None, 54 | price=orderModifyParams.newPrice if orderModifyParams.newPrice > 0 else None, 55 | trigger_price=orderModifyParams.newTriggerPrice if orderModifyParams.newTriggerPrice > 0 else None, 56 | order_type=orderModifyParams.newOrderType if orderModifyParams.newOrderType != None else None) 57 | 58 | logging.info('%s Order modified successfully for orderId = %s', self.broker, orderId) 59 | order.lastOrderUpdateTimestamp = Utils.getEpoch() 60 | return order 61 | except Exception as e: 62 | logging.info('%s Order modify failed: %s', self.broker, str(e)) 63 | raise Exception(str(e)) 64 | 65 | def modifyOrderToMarket(self, order): 66 | logging.info('%s: Going to modify order with params %s', self.broker) 67 | kite = self.brokerHandle 68 | try: 69 | orderId = kite.modify_order( 70 | variety=kite.VARIETY_REGULAR, 71 | order_id=order.orderId, 72 | order_type=kite.ORDER_TYPE_MARKET) 73 | 74 | logging.info('%s Order modified successfully to MARKET for orderId = %s', self.broker, orderId) 75 | order.lastOrderUpdateTimestamp = Utils.getEpoch() 76 | return order 77 | except Exception as e: 78 | logging.info('%s Order modify to market failed: %s', self.broker, str(e)) 79 | raise Exception(str(e)) 80 | 81 | def cancelOrder(self, order): 82 | logging.info('%s Going to cancel order %s', self.broker, order.orderId) 83 | kite = self.brokerHandle 84 | try: 85 | orderId = kite.cancel_order( 86 | variety=kite.VARIETY_REGULAR, 87 | order_id=order.orderId) 88 | 89 | logging.info('%s Order cancelled successfully, orderId = %s', self.broker, orderId) 90 | order.lastOrderUpdateTimestamp = Utils.getEpoch() 91 | return order 92 | except Exception as e: 93 | logging.info('%s Order cancel failed: %s', self.broker, str(e)) 94 | raise Exception(str(e)) 95 | 96 | def fetchAndUpdateAllOrderDetails(self, orders): 97 | #logging.info('%s Going to fetch order book', self.broker) 98 | kite = self.brokerHandle 99 | orderBook = None 100 | try: 101 | orderBook = kite.orders() 102 | except Exception as e: 103 | logging.error('%s Failed to fetch order book', self.broker) 104 | return 105 | 106 | logging.info('%s Order book length = %d', self.broker, len(orderBook)) 107 | numOrdersUpdated = 0 108 | for bOrder in orderBook: 109 | foundOrder = None 110 | for order in orders: 111 | if order.orderId == bOrder['order_id']: 112 | foundOrder = order 113 | break 114 | 115 | if foundOrder != None: 116 | #logging.info('Found order for orderId %s', foundOrder.orderId) 117 | foundOrder.qty = bOrder['quantity'] 118 | foundOrder.filledQty = bOrder['filled_quantity'] 119 | foundOrder.pendingQty = bOrder['pending_quantity'] 120 | foundOrder.orderStatus = bOrder['status'] 121 | if foundOrder.orderStatus == OrderStatus.CANCELLED and foundOrder.filledQty > 0: 122 | # Consider this case as completed in our system as we cancel the order with pending qty when strategy stop timestamp reaches 123 | foundOrder.orderStatus = OrderStatus.COMPLETED 124 | foundOrder.price = bOrder['price'] 125 | foundOrder.triggerPrice = bOrder['trigger_price'] 126 | foundOrder.averagePrice = bOrder['average_price'] 127 | foundOrder.message = bOrder['status_message'] 128 | 129 | #logging.info('%s Updated order %s', self.broker, foundOrder) 130 | numOrdersUpdated += 1 131 | 132 | logging.info('%s: %d orders updated with broker order details', self.broker, numOrdersUpdated) 133 | 134 | def convertToBrokerProductType(self, productType): 135 | kite = self.brokerHandle 136 | if productType == ProductType.MIS: 137 | return kite.PRODUCT_MIS 138 | elif productType == ProductType.NRML: 139 | return kite.PRODUCT_NRML 140 | elif productType == ProductType.CNC: 141 | return kite.PRODUCT_CNC 142 | return None 143 | 144 | def convertToBrokerOrderType(self, orderType): 145 | kite = self.brokerHandle 146 | if orderType == OrderType.LIMIT: 147 | return kite.ORDER_TYPE_LIMIT 148 | elif orderType == OrderType.MARKET: 149 | return kite.ORDER_TYPE_MARKET 150 | elif orderType == OrderType.SL_MARKET: 151 | return kite.ORDER_TYPE_SLM 152 | elif orderType == OrderType.SL_LIMIT: 153 | return kite.ORDER_TYPE_SL 154 | return None 155 | 156 | def convertToBrokerDirection(self, direction): 157 | kite = self.brokerHandle 158 | if direction == Direction.LONG: 159 | return kite.TRANSACTION_TYPE_BUY 160 | elif direction == Direction.SHORT: 161 | return kite.TRANSACTION_TYPE_SELL 162 | return None 163 | -------------------------------------------------------------------------------- /src/restapis/BrokerLoginAPI.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from flask.views import MethodView 3 | from flask import request, redirect 4 | 5 | from core.Controller import Controller 6 | 7 | class BrokerLoginAPI(MethodView): 8 | def get(self): 9 | redirectUrl = Controller.handleBrokerLogin(request.args) 10 | return redirect(redirectUrl, code=302) -------------------------------------------------------------------------------- /src/restapis/HoldingsAPI.py: -------------------------------------------------------------------------------- 1 | from flask.views import MethodView 2 | import json 3 | import logging 4 | from core.Controller import Controller 5 | 6 | class HoldingsAPI(MethodView): 7 | def get(self): 8 | brokerHandle = Controller.getBrokerLogin().getBrokerHandle() 9 | holdings = brokerHandle.holdings() 10 | logging.info('User holdings => %s', holdings) 11 | return json.dumps(holdings) 12 | -------------------------------------------------------------------------------- /src/restapis/HomeAPI.py: -------------------------------------------------------------------------------- 1 | from flask.views import MethodView 2 | from flask import render_template, request 3 | 4 | class HomeAPI(MethodView): 5 | def get(self): 6 | if 'loggedIn' in request.args and request.args['loggedIn'] == 'true': 7 | return render_template('index_loggedin.html') 8 | elif 'algoStarted' in request.args and request.args['algoStarted'] == 'true': 9 | return render_template('index_algostarted.html') 10 | else: 11 | return render_template('index.html') 12 | -------------------------------------------------------------------------------- /src/restapis/PositionsAPI.py: -------------------------------------------------------------------------------- 1 | from flask.views import MethodView 2 | import json 3 | import logging 4 | from core.Controller import Controller 5 | 6 | class PositionsAPI(MethodView): 7 | def get(self): 8 | brokerHandle = Controller.getBrokerLogin().getBrokerHandle() 9 | positions = brokerHandle.positions() 10 | logging.info('User positions => %s', positions) 11 | return json.dumps(positions) 12 | -------------------------------------------------------------------------------- /src/restapis/StartAlgoAPI.py: -------------------------------------------------------------------------------- 1 | from flask.views import MethodView 2 | import json 3 | import logging 4 | import threading 5 | from config.Config import getSystemConfig 6 | from core.Algo import Algo 7 | 8 | class StartAlgoAPI(MethodView): 9 | def post(self): 10 | # start algo in a separate thread 11 | x = threading.Thread(target=Algo.startAlgo) 12 | x.start() 13 | systemConfig = getSystemConfig() 14 | homeUrl = systemConfig['homeUrl'] + '?algoStarted=true' 15 | logging.info('Sending redirect url %s in response', homeUrl) 16 | respData = { 'redirect': homeUrl } 17 | return json.dumps(respData) 18 | -------------------------------------------------------------------------------- /src/strategies/BNFORB30Min.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from datetime import datetime 3 | 4 | from instruments.Instruments import Instruments 5 | from models.Direction import Direction 6 | from models.ProductType import ProductType 7 | from strategies.BaseStrategy import BaseStrategy 8 | from utils.Utils import Utils 9 | from trademgmt.Trade import Trade 10 | from trademgmt.TradeManager import TradeManager 11 | 12 | # Each strategy has to be derived from BaseStrategy 13 | class BNFORB30Min(BaseStrategy): 14 | __instance = None 15 | 16 | @staticmethod 17 | def getInstance(): # singleton class 18 | if BNFORB30Min.__instance == None: 19 | BNFORB30Min() 20 | return BNFORB30Min.__instance 21 | 22 | def __init__(self): 23 | if BNFORB30Min.__instance != None: 24 | raise Exception("This class is a singleton!") 25 | else: 26 | BNFORB30Min.__instance = self 27 | # Call Base class constructor 28 | super().__init__("BNFORB30Min") 29 | # Initialize all the properties specific to this strategy 30 | self.productType = ProductType.MIS 31 | self.symbols = [] 32 | self.slPercentage = 0 33 | self.targetPercentage = 0 34 | self.startTimestamp = Utils.getTimeOfToDay(9, 45, 0) # When to start the strategy. Default is Market start time 35 | self.stopTimestamp = Utils.getTimeOfToDay(14, 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. 36 | self.squareOffTimestamp = Utils.getTimeOfToDay(15, 0, 0) # Square off time 37 | self.capital = 100000 # Capital to trade (This is the margin you allocate from your broker account for this strategy) 38 | self.leverage = 0 39 | self.maxTradesPerDay = 1 # Max number of trades per day under this strategy 40 | self.isFnO = True # Does this strategy trade in FnO or not 41 | self.capitalPerSet = 100000 # Applicable if isFnO is True (1 set means 1CE/1PE or 2CE/2PE etc based on your strategy logic) 42 | 43 | def process(self): 44 | now = datetime.now() 45 | processEndTime = Utils.getTimeOfToDay(9, 50, 0) 46 | if now < self.startTimestamp: 47 | return 48 | if now > processEndTime: 49 | # We are interested in creating the symbol only between 09:45 and 09:50 50 | # since we are not using historical candles so not aware of exact high and low of the first 30 mins 51 | return 52 | 53 | if len(self.trades) >= 2: 54 | return 55 | 56 | symbol = Utils.prepareMonthlyExpiryFuturesSymbol('BANKNIFTY') 57 | quote = self.getQuote(symbol) 58 | if quote == None: 59 | logging.error('%s: Could not get quote for %s', self.getName(), symbol) 60 | return 61 | 62 | logging.info('%s: %s => lastTradedPrice = %f', self.getName(), symbol, quote.lastTradedPrice) 63 | self.generateTrade(symbol, Direction.LONG, quote.high, quote.low) 64 | self.generateTrade(symbol, Direction.SHORT, quote.high, quote.low) 65 | 66 | 67 | def generateTrade(self, tradingSymbol, direction, high, low): 68 | trade = Trade(tradingSymbol) 69 | trade.strategy = self.getName() 70 | trade.isFutures = True 71 | trade.direction = direction 72 | trade.productType = self.productType 73 | trade.placeMarketOrder = True 74 | trade.requestedEntry = high if direction == Direction.LONG else low 75 | trade.timestamp = Utils.getEpoch(self.startTimestamp) # setting this to strategy timestamp 76 | # Calculate lots 77 | numLots = self.calculateLotsPerTrade() 78 | isd = Instruments.getInstrumentDataBySymbol(tradingSymbol) # Get instrument data to know qty per lot 79 | trade.qty = isd['lot_size'] * numLots 80 | 81 | trade.stopLoss = low if direction == Direction.LONG else high 82 | slDiff = high - low 83 | # target is 1.5 times of SL 84 | if direction == 'LONG': 85 | trade.target = Utils.roundToNSEPrice(trade.requestedEntry + 1.5 * slDiff) 86 | else: 87 | trade.target = Utils.roundToNSEPrice(trade.requestedEntry - 1.5 * slDiff) 88 | 89 | trade.intradaySquareOffTimestamp = Utils.getEpoch(self.squareOffTimestamp) 90 | # Hand over the trade to TradeManager 91 | TradeManager.addNewTrade(trade) 92 | 93 | def shouldPlaceTrade(self, trade, tick): 94 | # First call base class implementation and if it returns True then only proceed 95 | if super().shouldPlaceTrade(trade, tick) == False: 96 | return False 97 | 98 | if tick == None: 99 | return False 100 | 101 | if trade.direction == Direction.LONG and tick.lastTradedPrice > trade.requestedEntry: 102 | return True 103 | elif trade.direction == Direction.SHORT and tick.lastTradedPrice < trade.requestedEntry: 104 | return True 105 | return False 106 | -------------------------------------------------------------------------------- /src/strategies/BaseStrategy.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | from datetime import datetime 4 | 5 | from models.ProductType import ProductType 6 | from core.Quotes import Quotes 7 | from trademgmt.TradeManager import TradeManager 8 | 9 | from utils.Utils import Utils 10 | 11 | class BaseStrategy: 12 | def __init__(self, name): 13 | # NOTE: All the below properties should be set by the Derived Class (Specific to each strategy) 14 | self.name = name # strategy name 15 | self.enabled = True # Strategy will be run only when it is enabled 16 | self.productType = ProductType.MIS # MIS/NRML/CNC etc 17 | self.symbols = [] # List of stocks to be traded under this strategy 18 | self.slPercentage = 0 19 | self.targetPercentage = 0 20 | self.startTimestamp = Utils.getMarketStartTime() # When to start the strategy. Default is Market start time 21 | self.stopTimestamp = None # 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. 22 | self.squareOffTimestamp = None # Square off time 23 | self.capital = 10000 # Capital to trade (This is the margin you allocate from your broker account for this strategy) 24 | self.leverage = 1 # 2x, 3x Etc 25 | self.maxTradesPerDay = 1 # Max number of trades per day under this strategy 26 | self.isFnO = False # Does this strategy trade in FnO or not 27 | self.capitalPerSet = 0 # Applicable if isFnO is True (Set means 1CE/1PE or 2CE/2PE etc based on your strategy logic) 28 | # Register strategy with trade manager 29 | TradeManager.registerStrategy(self) 30 | # Load all trades of this strategy into self.trades on restart of app 31 | self.trades = TradeManager.getAllTradesByStrategy(self.name) 32 | 33 | def getName(self): 34 | return self.name 35 | 36 | def isEnabled(self): 37 | return self.enabled 38 | 39 | def setDisabled(self): 40 | self.enabled = False 41 | 42 | def process(self): 43 | # Implementation is specific to each strategy - To defined in derived class 44 | logging.info("BaseStrategy process is called.") 45 | pass 46 | 47 | def calculateCapitalPerTrade(self): 48 | leverage = self.leverage if self.leverage > 0 else 1 49 | capitalPerTrade = int(self.capital * leverage / self.maxTradesPerDay) 50 | return capitalPerTrade 51 | 52 | def calculateLotsPerTrade(self): 53 | if self.isFnO == False: 54 | return 0 55 | # Applicable only for fno 56 | return int(self.capital / self.capitalPerSet) 57 | 58 | def canTradeToday(self): 59 | # Derived class should override the logic if the strategy to be traded only on specific days of the week 60 | return True 61 | 62 | def run(self): 63 | # NOTE: This should not be overriden in Derived class 64 | if self.enabled == False: 65 | logging.warn("%s: Not going to run strategy as its not enabled.", self.getName()) 66 | return 67 | 68 | if Utils.isMarketClosedForTheDay(): 69 | message="{}: Not going to run strategy as market is closed.".format(self.getName()) 70 | Utils.sendMessageTelegramBot(message) 71 | logging.warn(message) 72 | return 73 | 74 | now = datetime.now() 75 | if now < Utils.getMarketStartTime(): 76 | Utils.waitTillMarketOpens(self.getName()) 77 | 78 | if self.canTradeToday() == False: 79 | logging.warn("%s: Not going to run strategy as it cannot be traded today.", self.getName()) 80 | return 81 | 82 | now = datetime.now() 83 | if now < self.startTimestamp: 84 | waitSeconds = Utils.getEpoch(self.startTimestamp) - Utils.getEpoch(now) 85 | message="{0} Waiting for {1} seconds till startegy start timestamp reaches...".format(self.getName(),waitSeconds) 86 | Utils.sendMessageTelegramBot(message) 87 | logging.info(message) 88 | if waitSeconds > 0: 89 | time.sleep(waitSeconds) 90 | 91 | # Run in an loop and keep processing 92 | while True: 93 | if Utils.isMarketClosedForTheDay(): 94 | logging.warn("%s: Exiting the strategy as market closed.", self.getName()) 95 | break 96 | 97 | # Derived class specific implementation will be called when process() is called 98 | self.process() 99 | 100 | # Sleep and wake up on every 30th second 101 | now = datetime.now() 102 | waitSeconds = 30 - (now.second % 30) 103 | time.sleep(waitSeconds) 104 | 105 | def shouldPlaceTrade(self, trade, tick): 106 | # Each strategy should call this function from its own shouldPlaceTrade() method before working on its own logic 107 | if trade == None: 108 | return False 109 | if trade.qty == 0: 110 | TradeManager.disableTrade(trade, 'InvalidQuantity') 111 | return False 112 | 113 | now = datetime.now() 114 | if now > self.stopTimestamp: 115 | TradeManager.disableTrade(trade, 'NoNewTradesCutOffTimeReached') 116 | return False 117 | 118 | numOfTradesPlaced = TradeManager.getNumberOfTradesPlacedByStrategy(self.getName()) 119 | if numOfTradesPlaced >= self.maxTradesPerDay: 120 | TradeManager.disableTrade(trade, 'MaxTradesPerDayReached') 121 | return False 122 | 123 | return True 124 | 125 | def addTradeToList(self, trade): 126 | if trade != None: 127 | self.trades.append(trade) 128 | 129 | def getQuote(self, tradingSymbol): 130 | return Quotes.getQuote(tradingSymbol, self.isFnO) 131 | 132 | def getTrailingSL(self, trade): 133 | return 0 134 | -------------------------------------------------------------------------------- /src/strategies/OptionBuyingStrategy.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | import json 4 | import os 5 | from models.OptionBuying import OptionBuying 6 | from trademgmt.TradeEncoder import TradeEncoder 7 | from core.Controller import Controller 8 | from ticker.ZerodhaTicker import ZerodhaTicker 9 | from ordermgmt.ZerodhaOrderManager import ZerodhaOrderManager 10 | from ordermgmt.Order import Order 11 | from core.Quotes import Quotes 12 | from utils.Utils import Utils 13 | 14 | class OptionBuyingStrategy: 15 | __instance = None 16 | 17 | @staticmethod 18 | def getInstance(): # singleton class 19 | if OptionBuyingStrategy.__instance == None: 20 | OptionBuyingStrategy() 21 | return OptionBuyingStrategy.__instance 22 | 23 | def __init__(self): 24 | if OptionBuyingStrategy.__instance != None: 25 | raise Exception("This class is a singleton!") 26 | else: 27 | OptionBuyingStrategy.__instance = self 28 | 29 | def run(self): 30 | logging.info('Inititated the OptionBuyingStrategy: Run called') 31 | self.strikeRunner() 32 | 33 | @staticmethod 34 | def strikeRunner(): 35 | # Get current market price of Nifty Future 36 | #futureSymbol = Utils.prepareMonthlyExpiryFuturesSymbol('BANKNIFTY') 37 | strikesFilepath='G:\Algo Trading\python\git\python-deploy\stikes\stikes.json' 38 | 39 | #quote = Quotes.getQuote(futureSymbol,True) 40 | 41 | futureSymbol='NIFTY BANK' 42 | quote = Quotes.getStrikePrice(futureSymbol) 43 | if quote == None: 44 | logging.error('OptionBuyingStrategy: Could not get quote for %s', futureSymbol) 45 | return 46 | 47 | ATMStrike = Utils.getNearestStrikePrice(quote, 100) 48 | logging.info('OptionBuyingStrategy: Bank Nifty CMP = %f, ATMStrike = %d', quote, ATMStrike) 49 | 50 | initialATMStrike=ATMStrike+100 51 | OptionBuyingStrategy.trades = [] 52 | OptionBuyingStrategy.loadAndUpdateStrikesFromFile(strikesFilepath) 53 | if(len(OptionBuyingStrategy.trades)==0): 54 | logging.info("As strikes data is empty , it will be added to the file") 55 | # Increment from current ATM strike price to find strike that condition met to CE strike 56 | while True: 57 | optionQuote = Quotes.getOptionBuyingQuote(Utils.prepareWeeklyOptionsSymbol("BANKNIFTY", initialATMStrike, 'CE'),True) 58 | if(OptionBuyingStrategy.isWithinTradingRange(optionQuote.low)): 59 | OptionBuyingStrategy.trades.append(optionQuote) 60 | message="{0} : low is {1} satisfies to trade".format(optionQuote.tradingSymbol,optionQuote.low) 61 | Utils.sendMessageTelegramBot(message) 62 | logging.info(message) 63 | break 64 | initialATMStrike=initialATMStrike+100 65 | 66 | # Increment from current ATM strike price to find strike that condition met to PE strike 67 | initialATMStrike=ATMStrike-100 68 | while True: 69 | optionQuote = Quotes.getOptionBuyingQuote(Utils.prepareWeeklyOptionsSymbol("BANKNIFTY", initialATMStrike, 'PE'),True) 70 | if(OptionBuyingStrategy.isWithinTradingRange(optionQuote.low)): 71 | OptionBuyingStrategy.trades.append(optionQuote) 72 | message="{0} : low is {1} satisfies to trade".format(optionQuote.tradingSymbol,optionQuote.low) 73 | Utils.sendMessageTelegramBot(message) 74 | logging.info(message) 75 | break 76 | initialATMStrike=initialATMStrike-100 77 | 78 | with open(strikesFilepath,'w') as tFile: 79 | json.dump(OptionBuyingStrategy.trades, tFile, indent=2, cls=TradeEncoder) 80 | 81 | message="Option details are saved to file successfully, it will be updated regularly" 82 | Utils.sendMessageTelegramBot(message) 83 | OptionBuyingStrategy.loadAndUpdateStrikesFromFile(strikesFilepath) 84 | 85 | else: 86 | logging.info("Update the low Price ") 87 | 88 | 89 | 90 | def loadAndUpdateStrikesFromFile(strikesFilepath): 91 | if os.path.exists(strikesFilepath) == False: 92 | logging.warn('loadAndUpdateStrikesFromFile() stikes Filepath %s does not exist', strikesFilepath) 93 | return 94 | OptionBuyingStrategy.trades = [] 95 | OptionBuyingStrategy.updatedTrades = [] 96 | tFile = open(strikesFilepath, 'r') 97 | tradesData = json.loads(tFile.read()) 98 | if not tradesData: 99 | return 100 | else: 101 | while True: 102 | isDataUpdate=False 103 | OptionBuyingStrategy.trades = [] 104 | tFile = open(strikesFilepath, 'r') 105 | tradesData = json.loads(tFile.read()) 106 | for tr in tradesData: 107 | 108 | trade = OptionBuyingStrategy.convertJSONToTrade(tr) 109 | if(trade.isTradeLive==False): 110 | #logging.info("As trade is not live , low will be verified") 111 | optionQuote=Quotes.getOptionBuyingQuote(trade.tradingSymbol,True) 112 | if(OptionBuyingStrategy.isWithinTradingRange(optionQuote.low)): 113 | if(trade.low != optionQuote.low): # Check whether the low is still withing range else it has to be updated 114 | isDataUpdate=True 115 | message="OptionBuyingStrategy: Low is updated low for {0}".format(trade.tradingSymbol) 116 | Utils.sendMessageTelegramBot(message) 117 | logging.info(message) 118 | trade.low = optionQuote.low 119 | OptionBuyingStrategy.updatedTrades.append(trade) 120 | else: 121 | isDataUpdate=True 122 | newStrikeTrade=OptionBuyingStrategy.getUpdatedStrike(int(trade.tradingSymbol[-7:-2]),trade.tradingSymbol[-2:]) 123 | message="OptionBuyingStrategy: Strike is updated from {0} to {1}".format(trade.tradingSymbol,newStrikeTrade.tradingSymbol) 124 | Utils.sendMessageTelegramBot(message) 125 | logging.info(message) 126 | trade=newStrikeTrade 127 | OptionBuyingStrategy.trades.append(trade) 128 | if(len(OptionBuyingStrategy.trades)!=0 and isDataUpdate): 129 | #OptionBuyingStrategy.trades=[] 130 | #OptionBuyingStrategy.trades=Test.updatedTrades 131 | OptionBuyingStrategy.writeStrikesToFile(OptionBuyingStrategy.trades) 132 | 133 | time.sleep(60) 134 | 135 | @staticmethod 136 | def convertJSONToTrade(jsonData): 137 | optionBuying = OptionBuying(jsonData['tradingSymbol']) 138 | optionBuying.lastTradedPrice = jsonData['lastTradedPrice'] 139 | optionBuying.high = jsonData['high'] 140 | optionBuying.low = jsonData['low'] 141 | optionBuying.entryPrice= jsonData['entryPrice'] 142 | optionBuying.stopLoss=jsonData['stopLoss'] 143 | optionBuying.target=jsonData['target'] 144 | optionBuying.isTradeLive=jsonData['isTradeLive'] 145 | return optionBuying 146 | 147 | @staticmethod 148 | def isWithinTradingRange(low): 149 | if(low>=50.00 and low<=80.00): 150 | return True 151 | else: 152 | return False 153 | 154 | @staticmethod 155 | def getUpdatedStrike(strikePrice,optionType): 156 | optionQuote=None 157 | if(optionType == "CE"): 158 | initialUpdateStrike=strikePrice-100 159 | else: 160 | initialUpdateStrike=strikePrice+100 161 | 162 | while True: 163 | optionQuote = Quotes.getOptionBuyingQuote(Utils.prepareWeeklyOptionsSymbol("BANKNIFTY", initialUpdateStrike, optionType),True) 164 | if(OptionBuyingStrategy.isWithinTradingRange(optionQuote.low)): 165 | logging.info('Low is updated to %f with low is %f',initialUpdateStrike,optionQuote.low) 166 | break 167 | 168 | if(optionType == "CE" and initialUpdateStrike>strikePrice): 169 | initialUpdateStrike=initialUpdateStrike-100 170 | elif(optionType == "PE" and initialUpdateStrike= self.maxTradesPerDay: 58 | return 59 | 60 | # Get current market price of Nifty Future 61 | futureSymbol = Utils.prepareMonthlyExpiryFuturesSymbol('NIFTY') 62 | quote = self.getQuote(futureSymbol) 63 | if quote == None: 64 | logging.error('%s: Could not get quote for %s', self.getName(), futureSymbol) 65 | return 66 | 67 | ATMStrike = Utils.getNearestStrikePrice(quote.lastTradedPrice, 50) 68 | logging.info('%s: Nifty CMP = %f, ATMStrike = %d', self.getName(), quote.lastTradedPrice, ATMStrike) 69 | 70 | ATMPlus50CESymbol = Utils.prepareWeeklyOptionsSymbol("NIFTY", ATMStrike + 50, 'CE') 71 | ATMMinus50PESymbol = Utils.prepareWeeklyOptionsSymbol("NIFTY", ATMStrike - 50, 'PE') 72 | logging.info('%s: ATMPlus50CE = %s, ATMMinus50PE = %s', self.getName(), ATMPlus50CESymbol, ATMMinus50PESymbol) 73 | # create trades 74 | self.generateTrades(ATMPlus50CESymbol, ATMMinus50PESymbol) 75 | 76 | def generateTrades(self, ATMPlus50CESymbol, ATMMinus50PESymbol): 77 | numLots = self.calculateLotsPerTrade() 78 | quoteATMPlus50CESymbol = self.getQuote(ATMPlus50CESymbol) 79 | quoteATMMinus50PESymbol = self.getQuote(ATMMinus50PESymbol) 80 | if quoteATMPlus50CESymbol == None or quoteATMMinus50PESymbol == None: 81 | logging.error('%s: Could not get quotes for option symbols', self.getName()) 82 | return 83 | 84 | self.generateTrade(ATMPlus50CESymbol, numLots, quoteATMPlus50CESymbol.lastTradedPrice) 85 | self.generateTrade(ATMMinus50PESymbol, numLots, quoteATMMinus50PESymbol.lastTradedPrice) 86 | logging.info('%s: Trades generated.', self.getName()) 87 | 88 | def generateTrade(self, optionSymbol, numLots, lastTradedPrice): 89 | trade = Trade(optionSymbol) 90 | trade.strategy = self.getName() 91 | trade.isOptions = True 92 | trade.direction = Direction.SHORT # Always short here as option selling only 93 | trade.productType = self.productType 94 | trade.placeMarketOrder = True 95 | trade.requestedEntry = lastTradedPrice 96 | trade.timestamp = Utils.getEpoch(self.startTimestamp) # setting this to strategy timestamp 97 | 98 | isd = Instruments.getInstrumentDataBySymbol(optionSymbol) # Get instrument data to know qty per lot 99 | trade.qty = isd['lot_size'] * numLots 100 | 101 | trade.stopLoss = Utils.roundToNSEPrice(trade.requestedEntry + trade.requestedEntry * self.slPercentage / 100) 102 | trade.target = 0 # setting to 0 as no target is applicable for this trade 103 | 104 | trade.intradaySquareOffTimestamp = Utils.getEpoch(self.squareOffTimestamp) 105 | # Hand over the trade to TradeManager 106 | TradeManager.addNewTrade(trade) 107 | 108 | def shouldPlaceTrade(self, trade, tick): 109 | # First call base class implementation and if it returns True then only proceed 110 | if super().shouldPlaceTrade(trade, tick) == False: 111 | return False 112 | # We dont have any condition to be checked here for this strategy just return True 113 | return True 114 | -------------------------------------------------------------------------------- /src/strategies/SampleStrategy.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from models.Direction import Direction 4 | from models.ProductType import ProductType 5 | from strategies.BaseStrategy import BaseStrategy 6 | from utils.Utils import Utils 7 | from trademgmt.Trade import Trade 8 | from trademgmt.TradeManager import TradeManager 9 | 10 | # Each strategy has to be derived from BaseStrategy 11 | class SampleStrategy(BaseStrategy): 12 | __instance = None 13 | 14 | @staticmethod 15 | def getInstance(): # singleton class 16 | if SampleStrategy.__instance == None: 17 | SampleStrategy() 18 | return SampleStrategy.__instance 19 | 20 | def __init__(self): 21 | if SampleStrategy.__instance != None: 22 | raise Exception("This class is a singleton!") 23 | else: 24 | SampleStrategy.__instance = self 25 | # Call Base class constructor 26 | super().__init__("SAMPLE") 27 | # Initialize all the properties specific to this strategy 28 | self.productType = ProductType.MIS 29 | self.symbols = ["SBIN", "INFY", "TATASTEEL", "RELIANCE", "HDFCBANK", "CIPLA"] 30 | self.slPercentage = 1.1 31 | self.targetPercentage = 2.2 32 | self.startTimestamp = Utils.getTimeOfToDay(9, 30, 0) # When to start the strategy. Default is Market start time 33 | self.stopTimestamp = Utils.getTimeOfToDay(14, 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. 34 | self.squareOffTimestamp = Utils.getTimeOfToDay(15, 0, 0) # Square off time 35 | self.capital = 3000 # Capital to trade (This is the margin you allocate from your broker account for this strategy) 36 | self.leverage = 2 # 2x, 3x Etc 37 | self.maxTradesPerDay = 3 # Max number of trades per day under this strategy 38 | self.isFnO = False # Does this strategy trade in FnO or not 39 | self.capitalPerSet = 0 # Applicable if isFnO is True (1 set means 1CE/1PE or 2CE/2PE etc based on your strategy logic) 40 | 41 | def process(self): 42 | if len(self.trades) >= self.maxTradesPerDay: 43 | return 44 | # This is a sample strategy with the following logic: 45 | # 1. If current market price > 0.5% from previous day close then create LONG trade 46 | # 2. If current market price < 0.5% from previous day close then create SHORT trade 47 | for symbol in self.symbols: 48 | quote = self.getQuote(symbol) 49 | if quote == None: 50 | logging.error('%s: Could not get quote for %s', self.getName(), symbol) 51 | continue 52 | longBreakoutPrice = Utils.roundToNSEPrice(quote.close + quote.close * 0.5 / 100) 53 | shortBreakoutPrice = Utils.roundToNSEPrice(quote.close - quote.close * 0.5 / 100) 54 | cmp = quote.lastTradedPrice 55 | logging.info('%s: %s => long = %f, short = %f, CMP = %f', self.getName(), symbol, longBreakoutPrice, shortBreakoutPrice, cmp) 56 | 57 | direction = None 58 | breakoutPrice = 0 59 | if cmp > longBreakoutPrice: 60 | direction = 'LONG' 61 | breakoutPrice = longBreakoutPrice 62 | elif cmp < shortBreakoutPrice: 63 | direction = 'SHORT' 64 | breakoutPrice = shortBreakoutPrice 65 | if direction == None: 66 | continue 67 | 68 | self.generateTrade(symbol, direction, breakoutPrice, cmp) 69 | 70 | 71 | def generateTrade(self, tradingSymbol, direction, breakoutPrice, cmp): 72 | trade = Trade(tradingSymbol) 73 | trade.strategy = self.getName() 74 | trade.direction = direction 75 | trade.productType = self.productType 76 | trade.placeMarketOrder = True 77 | trade.requestedEntry = breakoutPrice 78 | trade.timestamp = Utils.getEpoch(self.startTimestamp) # setting this to strategy timestamp 79 | trade.qty = int(self.calculateCapitalPerTrade() / breakoutPrice) 80 | if trade.qty == 0: 81 | trade.qty = 1 # Keep min 1 qty 82 | if direction == 'LONG': 83 | trade.stopLoss = Utils.roundToNSEPrice(breakoutPrice - breakoutPrice * self.slPercentage / 100) 84 | if cmp < trade.stopLoss: 85 | trade.stopLoss = Utils.roundToNSEPrice(cmp - cmp * 1 / 100) 86 | else: 87 | trade.stopLoss = Utils.roundToNSEPrice(breakoutPrice + breakoutPrice * self.slPercentage / 100) 88 | if cmp > trade.stopLoss: 89 | trade.stopLoss = Utils.roundToNSEPrice(cmp + cmp * 1 / 100) 90 | 91 | if direction == 'LONG': 92 | trade.target = Utils.roundToNSEPrice(breakoutPrice + breakoutPrice * self.targetPercentage / 100) 93 | else: 94 | trade.target = Utils.roundToNSEPrice(breakoutPrice - breakoutPrice * self.targetPercentage / 100) 95 | 96 | trade.intradaySquareOffTimestamp = Utils.getEpoch(self.squareOffTimestamp) 97 | # Hand over the trade to TradeManager 98 | TradeManager.addNewTrade(trade) 99 | 100 | def shouldPlaceTrade(self, trade, tick): 101 | # First call base class implementation and if it returns True then only proceed 102 | if super().shouldPlaceTrade(trade, tick) == False: 103 | return False 104 | 105 | if tick == None: 106 | return False 107 | 108 | if trade.direction == Direction.LONG and tick.lastTradedPrice > trade.requestedEntry: 109 | return True 110 | elif trade.direction == Direction.SHORT and tick.lastTradedPrice < trade.requestedEntry: 111 | return True 112 | return False 113 | -------------------------------------------------------------------------------- /src/strategies/ShortStraddleBNF.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from datetime import datetime 3 | 4 | from instruments.Instruments import Instruments 5 | from models.Direction import Direction 6 | from models.ProductType import ProductType 7 | from strategies.BaseStrategy import BaseStrategy 8 | from utils.Utils import Utils 9 | from trademgmt.Trade import Trade 10 | from trademgmt.TradeManager import TradeManager 11 | from core.Quotes import Quotes 12 | 13 | # Each strategy has to be derived from BaseStrategy 14 | class ShortStraddleBNF(BaseStrategy): 15 | __instance = None 16 | 17 | @staticmethod 18 | def getInstance(): # singleton class 19 | if ShortStraddleBNF.__instance == None: 20 | ShortStraddleBNF() 21 | return ShortStraddleBNF.__instance 22 | 23 | def __init__(self): 24 | if ShortStraddleBNF.__instance != None: 25 | raise Exception("This class is a singleton!") 26 | else: 27 | ShortStraddleBNF.__instance = self 28 | # Call Base class constructor 29 | super().__init__("ShortStraddleBNF") 30 | # Initialize all the properties specific to this strategy 31 | self.productType = ProductType.MIS 32 | self.symbols = [] 33 | self.slPercentage = 25 34 | self.targetPercentage = 0 35 | self.startTimestamp = Utils.getTimeOfToDay(9, 21, 0) # When to start the strategy. Default is Market start time 36 | self.stopTimestamp = Utils.getTimeOfToDay(13, 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. 37 | self.squareOffTimestamp = Utils.getTimeOfToDay(15, 12, 0) # Square off time 38 | self.capital = 100000 # Capital to trade (This is the margin you allocate from your broker account for this strategy) 39 | self.leverage = 0 40 | self.maxTradesPerDay = 2 # (1 CE + 1 PE) Max number of trades per day under this strategy 41 | self.isFnO = True # Does this strategy trade in FnO or not 42 | self.capitalPerSet = 100000 # Applicable if isFnO is True (1 set means 1CE/1PE or 2CE/2PE etc based on your strategy logic) 43 | 44 | def canTradeToday(self): 45 | # Even if you remove this function canTradeToday() completely its same as allowing trade every day 46 | return True 47 | 48 | def process(self): 49 | now = datetime.now() 50 | if now < self.startTimestamp: 51 | return 52 | if len(self.trades) >= self.maxTradesPerDay: 53 | return 54 | 55 | # Get current market price of Nifty Future 56 | #futureSymbol = Utils.prepareMonthlyExpiryFuturesSymbol('BANKNIFTY') 57 | futureSymbol='NIFTY BANK' 58 | quote = Quotes.getStrikePrice(futureSymbol) 59 | if quote == None: 60 | logging.error('%s: Could not get quote for %s', self.getName(), futureSymbol) 61 | return 62 | 63 | ATMStrike = Utils.getNearestStrikePrice(quote, 100) 64 | logging.info('%s: %s = %f, ATMStrike = %d', self.getName(),futureSymbol,quote, ATMStrike) 65 | 66 | ATMCESymbol = Utils.prepareWeeklyOptionsSymbol("BANKNIFTY", ATMStrike, 'CE') 67 | ATMPESymbol = Utils.prepareWeeklyOptionsSymbol("BANKNIFTY", ATMStrike, 'PE') 68 | logging.info('%s: ATMCESymbol = %s, ATMPESymbol = %s', self.getName(), ATMCESymbol, ATMPESymbol) 69 | # create trades 70 | self.generateTrades(ATMCESymbol, ATMPESymbol) 71 | 72 | def generateTrades(self, ATMCESymbol, ATMPESymbol): 73 | numLots = self.calculateLotsPerTrade() 74 | quoteATMCESymbol = self.getQuote(ATMCESymbol) 75 | quoteATMPESymbol = self.getQuote(ATMPESymbol) 76 | if quoteATMCESymbol == None or quoteATMPESymbol == None: 77 | logging.error('%s: Could not get quotes for option symbols', self.getName()) 78 | return 79 | 80 | self.generateTrade(ATMCESymbol, numLots, quoteATMCESymbol.lastTradedPrice,ATMPESymbol) 81 | self.generateTrade(ATMPESymbol, numLots, quoteATMPESymbol.lastTradedPrice,ATMCESymbol) 82 | logging.info('%s: Trades generated.', self.getName()) 83 | 84 | def generateTrade(self, optionSymbol, numLots, lastTradedPrice,counterPosition): 85 | trade = Trade(optionSymbol) 86 | trade.strategy = self.getName() 87 | trade.isOptions = True 88 | trade.direction = Direction.SHORT # Always short here as option selling only 89 | trade.productType = self.productType 90 | trade.placeMarketOrder = True 91 | trade.requestedEntry = lastTradedPrice 92 | trade.timestamp = Utils.getEpoch(self.startTimestamp) # setting this to strategy timestamp 93 | trade.slPercentage = 25 94 | trade.moveToCost=True 95 | trade.counterPosition=counterPosition 96 | 97 | isd = Instruments.getInstrumentDataBySymbol(optionSymbol) # Get instrument data to know qty per lot 98 | trade.qty = isd['lot_size'] * numLots 99 | 100 | trade.stopLoss = Utils.roundToNSEPrice(trade.requestedEntry + trade.requestedEntry * self.slPercentage / 100) 101 | trade.target = 0 # setting to 0 as no target is applicable for this trade 102 | 103 | trade.intradaySquareOffTimestamp = Utils.getEpoch(self.squareOffTimestamp) 104 | # Hand over the trade to TradeManager 105 | TradeManager.addNewTrade(trade) 106 | 107 | def shouldPlaceTrade(self, trade, tick): 108 | # First call base class implementation and if it returns True then only proceed 109 | if super().shouldPlaceTrade(trade, tick) == False: 110 | return False 111 | # We dont have any condition to be checked here for this strategy just return True 112 | return True 113 | 114 | -------------------------------------------------------------------------------- /src/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Algo Trading - Python 9 | 25 | 30 | 31 | 32 | 33 | 34 |

Welcome to algo trading in python

35 |
36 | 39 |
40 | 41 | 42 | -------------------------------------------------------------------------------- /src/templates/index_algostarted.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Algo Trading - Python 9 | 25 | 26 | 27 | 39 | 40 | 41 | 42 | 43 |

Welcome to algo trading in python

44 |
45 | 48 |

Algo is running...

49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /src/templates/index_loggedin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Algo Trading - Python 9 | 25 | 26 | 27 | 43 | 44 | 45 | 46 | 47 |

Welcome to algo trading in python

48 |
49 | 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /src/ticker/BaseTicker.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from core.Controller import Controller 4 | 5 | class BaseTicker: 6 | def __init__(self, broker): 7 | self.broker = broker 8 | self.brokerLogin = Controller.getBrokerLogin() 9 | self.ticker = None 10 | self.tickListeners = [] 11 | 12 | def startTicker(self): 13 | pass 14 | 15 | def stopTicker(self): 16 | pass 17 | 18 | def registerListener(self, listener): 19 | # All registered tick listeners will be notified on new ticks 20 | self.tickListeners.append(listener) 21 | 22 | def registerSymbols(self, symbols): 23 | pass 24 | 25 | def unregisterSymbols(self, symbols): 26 | pass 27 | 28 | def onNewTicks(self, ticks): 29 | # logging.info('New ticks received %s', ticks) 30 | for tick in ticks: 31 | for listener in self.tickListeners: 32 | try: 33 | listener(tick) 34 | except Exception as e: 35 | logging.error('BaseTicker: Exception from listener callback function. Error => %s', str(e)) 36 | 37 | def onConnect(self): 38 | logging.info('Ticker connection successful.') 39 | 40 | def onDisconnect(self, code, reason): 41 | logging.error('Ticker got disconnected. code = %d, reason = %s', code, reason) 42 | 43 | def onError(self, code, reason): 44 | logging.error('Ticker errored out. code = %d, reason = %s', code, reason) 45 | 46 | def onReconnect(self, attemptsCount): 47 | logging.warn('Ticker reconnecting.. attemptsCount = %d', attemptsCount) 48 | 49 | def onMaxReconnectsAttempt(self): 50 | logging.error('Ticker max auto reconnects attempted and giving up..') 51 | 52 | def onOrderUpdate(self, data): 53 | #logging.info('Ticker: order update %s', data) 54 | pass 55 | -------------------------------------------------------------------------------- /src/ticker/ZerodhaTicker.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | 4 | from kiteconnect import KiteTicker 5 | 6 | from ticker.BaseTicker import BaseTicker 7 | from instruments.Instruments import Instruments 8 | from models.TickData import TickData 9 | from utils.Utils import Utils 10 | 11 | class ZerodhaTicker(BaseTicker): 12 | def __init__(self): 13 | super().__init__("zerodha") 14 | 15 | def startTicker(self): 16 | brokerAppDetails = self.brokerLogin.getBrokerAppDetails() 17 | accessToken = self.brokerLogin.getAccessToken() 18 | if accessToken == None: 19 | logging.error('ZerodhaTicker startTicker: Cannot start ticker as accessToken is empty') 20 | return 21 | 22 | ticker = KiteTicker(brokerAppDetails.appKey, accessToken) 23 | ticker.on_connect = self.on_connect 24 | ticker.on_close = self.on_close 25 | ticker.on_error = self.on_error 26 | ticker.on_reconnect = self.on_reconnect 27 | ticker.on_noreconnect = self.on_noreconnect 28 | ticker.on_ticks = self.on_ticks 29 | ticker.on_order_update = self.on_order_update 30 | 31 | logging.info('ZerodhaTicker: Going to connect..') 32 | self.ticker = ticker 33 | self.ticker.connect(threaded=True) 34 | 35 | def stopTicker(self): 36 | logging.info('ZerodhaTicker: stopping..') 37 | self.ticker.close(1000, "Manual close") 38 | 39 | def registerSymbols(self, symbols): 40 | tokens = [] 41 | for symbol in symbols: 42 | isd = Instruments.getInstrumentDataBySymbol(symbol) 43 | token = isd['instrument_token'] 44 | logging.info('ZerodhaTicker registerSymbol: %s token = %s', symbol, token) 45 | tokens.append(token) 46 | 47 | logging.info('ZerodhaTicker Subscribing tokens %s', tokens) 48 | self.ticker.subscribe(tokens) 49 | 50 | def unregisterSymbols(self, symbols): 51 | tokens = [] 52 | for symbol in symbols: 53 | isd = Instruments.getInstrumentDataBySymbol(symbol) 54 | token = isd['instrument_token'] 55 | logging.info('ZerodhaTicker unregisterSymbols: %s token = %s', symbol, token) 56 | tokens.append(token) 57 | 58 | logging.info('ZerodhaTicker Unsubscribing tokens %s', tokens) 59 | self.ticker.unsubscribe(tokens) 60 | 61 | def on_ticks(self, ws, brokerTicks): 62 | # convert broker specific Ticks to our system specific Ticks (models.TickData) and pass to super class function 63 | ticks = [] 64 | for bTick in brokerTicks: 65 | isd = Instruments.getInstrumentDataByToken(bTick['instrument_token']) 66 | tradingSymbol = isd['tradingsymbol'] 67 | tick = TickData(tradingSymbol) 68 | tick.lastTradedPrice = bTick['last_price'] 69 | tick.lastTradedQuantity = bTick['last_quantity'] 70 | tick.avgTradedPrice = bTick['average_price'] 71 | tick.volume = bTick['volume'] 72 | tick.totalBuyQuantity = bTick['buy_quantity'] 73 | tick.totalSellQuantity = bTick['sell_quantity'] 74 | tick.open = bTick['ohlc']['open'] 75 | tick.high = bTick['ohlc']['high'] 76 | tick.low = bTick['ohlc']['low'] 77 | tick.close = bTick['ohlc']['close'] 78 | tick.change = bTick['change'] 79 | ticks.append(tick) 80 | 81 | self.onNewTicks(ticks) 82 | 83 | def on_connect(self, ws, response): 84 | message="Ticker service is connected" 85 | Utils.sendMessageTelegramBot(message) 86 | logging.info(message) 87 | self.onConnect() 88 | 89 | def on_close(self, ws, code, reason): 90 | message="ALERT: ticker service is closed : {0}".format(reason) 91 | Utils.sendMessageTelegramBot(message) 92 | logging.info(message) 93 | self.onDisconnect(code, reason) 94 | 95 | def on_error(self, ws, code, reason): 96 | message="ALERT: Error in ticker service : {0}".format(reason) 97 | Utils.sendMessageTelegramBot(message) 98 | logging.error(message) 99 | self.onError(code, reason) 100 | 101 | def on_reconnect(self, ws, attemptsCount): 102 | self.onReconnect(attemptsCount) 103 | 104 | def on_noreconnect(self, ws): 105 | self.onMaxReconnectsAttempt() 106 | 107 | def on_order_update(self, ws, data): 108 | self.onOrderUpdate(data) 109 | -------------------------------------------------------------------------------- /src/trademgmt/Trade.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from trademgmt.TradeState import TradeState 4 | from models.ProductType import ProductType 5 | 6 | from utils.Utils import Utils 7 | 8 | class Trade: 9 | def __init__(self, tradingSymbol = None): 10 | self.exchange = "NSE" 11 | self.tradeID = Utils.generateTradeID() # Unique ID for each trade 12 | self.tradingSymbol = tradingSymbol 13 | self.strategy = "" 14 | self.direction = "" 15 | self.productType = ProductType.MIS 16 | self.isFutures = False # Futures trade 17 | self.isOptions = False # Options trade 18 | self.optionType = None # CE/PE. Applicable only if isOptions is True 19 | self.placeMarketOrder = False # True means place the entry order with Market Order Type 20 | self.intradaySquareOffTimestamp = None # Can be strategy specific. Some can square off at 15:00:00 some can at 15:15:00 etc. 21 | self.requestedEntry = 0 # Requested entry 22 | self.entry = 0 # Actual entry. This will be different from requestedEntry if the order placed is Market order 23 | self.slPercentage=0 #Stoploss percentage that to be set for each strategy 24 | self.qty = 0 # Requested quantity 25 | self.filledQty = 0 # In case partial fill qty is not equal to filled quantity 26 | self.initialStopLoss = 0 # Initial stop loss 27 | self.stopLoss = 0 # This is the current stop loss. In case of trailing SL the current stopLoss and initialStopLoss will be different after some time 28 | self.target = 0 # Target price if applicable 29 | self.cmp = 0 # Last traded price 30 | 31 | self.moveToCost = False # This need to be set true for enabling move to cost price 32 | self.counterPosition= None # This need to be set with counter position if move to cost price is set with True. when one leg hits with SL counter position that to be set for cost price 33 | 34 | self.tradeState = TradeState.CREATED # state of the trade 35 | self.timestamp = None # Set this timestamp to strategy timestamp if you are not sure what to set 36 | self.createTimestamp = Utils.getEpoch() # Timestamp when the trade is created (Not triggered) 37 | self.startTimestamp = None # Timestamp when the trade gets triggered and order placed 38 | self.endTimestamp = None # Timestamp when the trade ended 39 | self.pnl = 0 # Profit loss of the trade. If trade is Active this shows the unrealized pnl else realized pnl 40 | self.pnlPercentage = 0 # Profit Loss in percentage terms 41 | self.exit = 0 # Exit price of the trade 42 | self.exitReason = None # SL/Target/SquareOff/Any Other 43 | 44 | self.entryOrder = None # Object of Type ordermgmt.Order 45 | self.slOrder = None # Object of Type ordermgmt.Order 46 | self.targetOrder = None # Object of Type ordermgmt.Order 47 | self.emergencyExitOrder = None # Object of Type ordermgmt.Order 48 | 49 | def equals(self, trade): # compares to trade objects and returns True if equals 50 | if trade == None: 51 | return False 52 | if self.tradeID == trade.tradeID: 53 | return True 54 | if self.tradingSymbol != trade.tradingSymbol: 55 | return False 56 | if self.strategy != trade.strategy: 57 | return False 58 | if self.direction != trade.direction: 59 | return False 60 | if self.productType != trade.productType: 61 | return False 62 | if self.requestedEntry != trade.requestedEntry: 63 | return False 64 | if self.qty != trade.qty: 65 | return False 66 | if self.timestamp != trade.timestamp: 67 | return False 68 | return True 69 | 70 | def __str__(self): 71 | return "ID=" + str(self.tradeID) + ", state=" + self.tradeState + ", symbol=" + self.tradingSymbol \ 72 | + ", strategy=" + self.strategy + ", direction=" + self.direction \ 73 | + ", productType=" + self.productType + ", reqEntry=" + str(self.requestedEntry) \ 74 | + ", stopLoss=" + str(self.stopLoss) + ", target=" + str(self.target) \ 75 | + ", entry=" + str(self.entry) + ", exit=" + str(self.exit) \ 76 | + ", profitLoss" + str(self.pnl) 77 | 78 | -------------------------------------------------------------------------------- /src/trademgmt/TradeEncoder.py: -------------------------------------------------------------------------------- 1 | 2 | from json import JSONEncoder 3 | 4 | class TradeEncoder(JSONEncoder): 5 | def default(self, o): 6 | return o.__dict__ -------------------------------------------------------------------------------- /src/trademgmt/TradeExitReason.py: -------------------------------------------------------------------------------- 1 | 2 | class TradeExitReason: 3 | SL_HIT = "SL HIT" 4 | TRAIL_SL_HIT = "TRAIL SL HIT" 5 | TARGET_HIT = "TARGET HIT" 6 | SQUARE_OFF = "SQUARE OFF" 7 | SL_CANCELLED = "SL CANCELLED" 8 | TARGET_CANCELLED = "TARGET CANCELLED" 9 | -------------------------------------------------------------------------------- /src/trademgmt/TradeManager.py: -------------------------------------------------------------------------------- 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 | 237 | if trade.intradaySquareOffTimestamp != None: 238 | nowEpoch = Utils.getEpoch() 239 | if nowEpoch >= trade.intradaySquareOffTimestamp: 240 | TradeManager.squareOffTrade(trade, TradeExitReason.SQUARE_OFF) 241 | 242 | @staticmethod 243 | def trackEntryOrder(trade): 244 | if trade.tradeState != TradeState.ACTIVE: 245 | return 246 | 247 | if trade.entryOrder == None: 248 | return 249 | 250 | if trade.entryOrder.orderStatus == OrderStatus.CANCELLED or trade.entryOrder.orderStatus == OrderStatus.REJECTED: 251 | trade.tradeState = TradeState.CANCELLED 252 | 253 | trade.filledQty = trade.entryOrder.filledQty 254 | if trade.filledQty > 0: 255 | trade.entry = trade.entryOrder.averagePrice 256 | # Update the current market price and calculate pnl 257 | trade.cmp = TradeManager.symbolToCMPMap[trade.tradingSymbol] 258 | Utils.calculateTradePnl(trade) 259 | 260 | @staticmethod 261 | def trackSLOrder(trade): 262 | if trade.tradeState != TradeState.ACTIVE: 263 | return 264 | if trade.stopLoss == 0: # Do not place SL order if no stopLoss provided 265 | return 266 | if trade.slOrder == None: 267 | # Place SL order 268 | TradeManager.placeSLOrder(trade) 269 | else: 270 | if trade.slOrder.orderStatus == OrderStatus.COMPLETE: 271 | # SL Hit 272 | exit = trade.slOrder.averagePrice 273 | exitReason = TradeExitReason.SL_HIT if trade.initialStopLoss == trade.stopLoss else TradeExitReason.TRAIL_SL_HIT 274 | TradeManager.setTradeToCompleted(trade, exit, exitReason) 275 | # Make sure to cancel target order if exists 276 | TradeManager.cancelTargetOrder(trade) 277 | TradeManager.checkAndUpdateMoveToCost(trade) 278 | 279 | elif trade.slOrder.orderStatus == OrderStatus.CANCELLED: 280 | exit = TradeManager.symbolToCMPMap[trade.tradingSymbol] 281 | errorString="The order was cancelled by the exchange" 282 | 283 | if trade.slOrder.message is not None and errorString in trade.slOrder.message: 284 | message="SL order {0} for tradeID {1} cancelled by exchange.".format(trade.slOrder.orderId, trade.tradeID) 285 | Utils.sendMessageTelegramBot(message) 286 | logging.info(message) 287 | TradeManager.placeEmergencyExitOrder(trade) # Placing market order to exit the trade 288 | TradeManager.checkAndUpdateMoveToCost(trade) # If another leg is running move that to cost price 289 | else: 290 | 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) 291 | TradeManager.setTradeToCompleted(trade, exit, TradeExitReason.SL_CANCELLED) 292 | # Cancel target order if exists 293 | TradeManager.cancelTargetOrder(trade) 294 | else: 295 | TradeManager.checkAndUpdateTrailSL(trade) 296 | 297 | @staticmethod 298 | def placeEmergencyExitOrder(trade): 299 | logging.info("TradeManager: Placing emergency exit order as SL order was cancelled by exchange") 300 | oip = OrderInputParams(trade.tradingSymbol) 301 | oip.direction = Direction.SHORT if trade.direction == Direction.LONG else Direction.LONG 302 | oip.productType = trade.productType 303 | oip.orderType = OrderType.MARKET 304 | oip.qty = trade.qty 305 | if trade.isFutures == True or trade.isOptions == True: 306 | oip.isFnO = True 307 | try: 308 | trade.emergencyExitOrder = TradeManager.getOrderManager().placeOrder(oip) 309 | except Exception as e: 310 | logging.error('TradeManager: Failed to place emergency exit order for tradeID %s: Error => %s', trade.tradeID, str(e)) 311 | 312 | @staticmethod 313 | def checkAndUpdateTrailSL(trade): 314 | # Trail the SL if applicable for the trade 315 | strategyInstance = TradeManager.strategyToInstanceMap[trade.strategy] 316 | if strategyInstance == None: 317 | return 318 | 319 | newTrailSL = strategyInstance.getTrailingSL(trade) 320 | updateSL = False 321 | if newTrailSL > 0: 322 | if trade.direction == Direction.LONG and newTrailSL > trade.stopLoss: 323 | updateSL = True 324 | elif trade.direction == Direction.SHORT and newTrailSL < trade.stopLoss: 325 | updateSL = True 326 | if updateSL == True: 327 | omp = OrderModifyParams() 328 | omp.newTriggerPrice = newTrailSL 329 | try: 330 | oldSL = trade.stopLoss 331 | TradeManager.getOrderManager().modifyOrder(trade.slOrder, omp) 332 | logging.info('TradeManager: Trail SL: Successfully modified stopLoss from %f to %f for tradeID %s', oldSL, newTrailSL, trade.tradeID) 333 | trade.stopLoss = newTrailSL # IMPORTANT: Dont forget to update this on successful modification 334 | except Exception as e: 335 | logging.error('TradeManager: Failed to modify SL order for tradeID %s orderId %s: Error => %s', trade.tradeID, trade.slOrder.orderId, str(e)) 336 | @staticmethod 337 | def checkAndUpdateMoveToCost(trade): 338 | # Move to cost price if applicable for the trade 339 | strategyInstance = TradeManager.strategyToInstanceMap[trade.strategy] 340 | if strategyInstance == None: 341 | return 342 | if trade.moveToCost == False: 343 | return 344 | 345 | # Getting counter trade and moving its SL order to cost price 346 | for availableTrades in TradeManager.trades: 347 | if availableTrades.tradingSymbol == trade.counterPosition and availableTrades.tradeState == TradeState.ACTIVE: 348 | counterTrade=availableTrades 349 | 350 | if counterTrade.entry > 0: 351 | if counterTrade.direction == Direction.LONG and counterTrade.entry > counterTrade.stopLoss: 352 | updateSL = True 353 | elif counterTrade.direction == Direction.SHORT and counterTrade.entry < counterTrade.stopLoss: 354 | updateSL = True 355 | if updateSL == True: 356 | omp = OrderModifyParams() 357 | omp.newTriggerPrice = counterTrade.entry 358 | try: 359 | TradeManager.getOrderManager().modifyOrder(counterTrade.slOrder, omp) 360 | message="TradeManager: Move to Cost: Successfully modified stopLoss from {0} to {1} for tradeID {2}".format(counterTrade.stopLoss, omp.newTriggerPrice, counterTrade.tradeID) 361 | Utils.sendMessageTelegramBot(message) 362 | logging.info(message) 363 | counterTrade.stopLoss = counterTrade.entry # IMPORTANT: Dont forget to update this on successful modification 364 | except Exception as e: 365 | logging.error('TradeManager: Failed to modify SL order for tradeID %s orderId %s: Error => %s', trade.tradeID, trade.slOrder.orderId, str(e)) 366 | 367 | @staticmethod 368 | def trackTargetOrder(trade): 369 | if trade.tradeState != TradeState.ACTIVE: 370 | return 371 | if trade.target == 0: # Do not place Target order if no target provided 372 | return 373 | if trade.targetOrder == None: 374 | # Place Target order 375 | TradeManager.placeTargetOrder(trade) 376 | else: 377 | if trade.targetOrder.orderStatus == OrderStatus.COMPLETE: 378 | # Target Hit 379 | exit = trade.targetOrder.averagePrice 380 | TradeManager.setTradeToCompleted(trade, exit, TradeExitReason.TARGET_HIT) 381 | # Make sure to cancel sl order 382 | TradeManager.cancelSLOrder(trade) 383 | 384 | elif trade.targetOrder.orderStatus == OrderStatus.CANCELLED: 385 | # Target order cancelled outside of algo (manually or by broker or by exchange) 386 | 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) 387 | exit = TradeManager.symbolToCMPMap[trade.tradingSymbol] 388 | TradeManager.setTradeToCompleted(trade, exit, TradeExitReason.TARGET_CANCELLED) 389 | # Cancel SL order 390 | TradeManager.cancelSLOrder(trade) 391 | 392 | @staticmethod 393 | def placeSLOrder(trade): 394 | oip = OrderInputParams(trade.tradingSymbol) 395 | oip.direction = Direction.SHORT if trade.direction == Direction.LONG else Direction.LONG 396 | oip.productType = trade.productType 397 | oip.orderType = OrderType.SL_MARKET 398 | oip.triggerPrice = Utils.roundToNSEPrice(trade.entry + trade.entry * trade.slPercentage / 100) 399 | oip.qty = trade.qty 400 | if trade.isFutures == True or trade.isOptions == True: 401 | oip.isFnO = True 402 | try: 403 | trade.slOrder = TradeManager.getOrderManager().placeOrder(oip) 404 | except Exception as e: 405 | logging.error('TradeManager: Failed to place SL order for tradeID %s: Error => %s', trade.tradeID, str(e)) 406 | return False 407 | 408 | message="TradeManager: Successfully placed SL order {0} for tradeID {1}".format(trade.slOrder.orderId, trade.tradeID) 409 | Utils.sendMessageTelegramBot(message) 410 | logging.info(message) 411 | return True 412 | 413 | @staticmethod 414 | def placeTargetOrder(trade, isMarketOrder = False): 415 | oip = OrderInputParams(trade.tradingSymbol) 416 | oip.direction = Direction.SHORT if trade.direction == Direction.LONG else Direction.LONG 417 | oip.productType = trade.productType 418 | oip.orderType = OrderType.MARKET if isMarketOrder == True else OrderType.LIMIT 419 | oip.price = 0 if isMarketOrder == True else trade.target 420 | oip.qty = trade.qty 421 | if trade.isFutures == True or trade.isOptions == True: 422 | oip.isFnO = True 423 | try: 424 | trade.targetOrder = TradeManager.getOrderManager().placeOrder(oip) 425 | except Exception as e: 426 | logging.error('TradeManager: Failed to place Target order for tradeID %s: Error => %s', trade.tradeID, str(e)) 427 | return False 428 | logging.info('TradeManager: Successfully placed Target order %s for tradeID %s', trade.targetOrder.orderId, trade.tradeID) 429 | return True 430 | 431 | @staticmethod 432 | def cancelEntryOrder(trade): 433 | if trade.entryOrder == None: 434 | return 435 | if trade.entryOrder.orderStatus == OrderStatus.CANCELLED: 436 | return 437 | try: 438 | TradeManager.getOrderManager().cancelOrder(trade.entryOrder) 439 | except Exception as e: 440 | logging.error('TradeManager: Failed to cancel Entry order %s for tradeID %s: Error => %s', trade.entryOrder.orderId, trade.tradeID, str(e)) 441 | logging.info('TradeManager: Successfully cancelled Entry order %s for tradeID %s', trade.entryOrder.orderId, trade.tradeID) 442 | 443 | @staticmethod 444 | def cancelSLOrder(trade): 445 | if trade.slOrder == None: 446 | return 447 | if trade.slOrder.orderStatus == OrderStatus.CANCELLED: 448 | return 449 | try: 450 | TradeManager.getOrderManager().cancelOrder(trade.slOrder) 451 | except Exception as e: 452 | logging.error('TradeManager: Failed to cancel SL order %s for tradeID %s: Error => %s', trade.slOrder.orderId, trade.tradeID, str(e)) 453 | logging.info('TradeManager: Successfully cancelled SL order %s for tradeID %s', trade.slOrder.orderId, trade.tradeID) 454 | 455 | @staticmethod 456 | def cancelTargetOrder(trade): 457 | if trade.targetOrder == None: 458 | return 459 | if trade.targetOrder.orderStatus == OrderStatus.CANCELLED: 460 | return 461 | try: 462 | TradeManager.getOrderManager().cancelOrder(trade.targetOrder) 463 | except Exception as e: 464 | logging.error('TradeManager: Failed to cancel Target order %s for tradeID %s: Error => %s', trade.targetOrder.orderId, trade.tradeID, str(e)) 465 | logging.info('TradeManager: Successfully cancelled Target order %s for tradeID %s', trade.targetOrder.orderId, trade.tradeID) 466 | 467 | @staticmethod 468 | def setTradeToCompleted(trade, exit, exitReason = None): 469 | trade.tradeState = TradeState.COMPLETED 470 | trade.exit = exit 471 | trade.exitReason = exitReason if trade.exitReason == None else trade.exitReason 472 | trade.endTimestamp = Utils.getEpoch() 473 | trade = Utils.calculateTradePnl(trade) 474 | 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) 475 | 476 | @staticmethod 477 | def squareOffTrade(trade, reason = TradeExitReason.SQUARE_OFF): 478 | logging.info('TradeManager: squareOffTrade called for tradeID %s with reason %s', trade.tradeID, reason) 479 | if trade == None or trade.tradeState != TradeState.ACTIVE: 480 | return 481 | 482 | trade.exitReason = reason 483 | if trade.entryOrder != None: 484 | if trade.entryOrder.orderStatus == OrderStatus.OPEN: 485 | # Cancel entry order if it is still open (not filled or partially filled case) 486 | TradeManager.cancelEntryOrder(trade) 487 | 488 | if trade.slOrder != None: 489 | TradeManager.cancelSLOrder(trade) 490 | 491 | if trade.targetOrder != None: 492 | # Change target order type to MARKET to exit position immediately 493 | logging.info('TradeManager: changing target order %s to MARKET to exit position for tradeID %s', trade.targetOrder.orderId, trade.tradeID) 494 | TradeManager.getOrderManager().modifyOrderToMarket(trade.targetOrder) 495 | else: 496 | # Place new target order to exit position 497 | logging.info('TradeManager: placing new target order to exit position for tradeID %s', trade.tradeID) 498 | TradeManager.placeTargetOrder(trade, True) 499 | 500 | @staticmethod 501 | def getOrderManager(): 502 | orderManager = None 503 | brokerName = Controller.getBrokerName() 504 | if brokerName == "zerodha": 505 | orderManager = ZerodhaOrderManager() 506 | #elif brokerName == "fyers": # Not implemented 507 | return orderManager 508 | 509 | @staticmethod 510 | def getNumberOfTradesPlacedByStrategy(strategy): 511 | count = 0 512 | for trade in TradeManager.trades: 513 | if trade.strategy != strategy: 514 | continue 515 | if trade.tradeState == TradeState.CREATED or trade.tradeState == TradeState.DISABLED: 516 | continue 517 | # consider active/completed/cancelled trades as trades placed 518 | count += 1 519 | return count 520 | 521 | @staticmethod 522 | def getAllTradesByStrategy(strategy): 523 | tradesByStrategy = [] 524 | for trade in TradeManager.trades: 525 | if trade.strategy == strategy: 526 | tradesByStrategy.append(trade) 527 | return tradesByStrategy 528 | 529 | @staticmethod 530 | def convertJSONToTrade(jsonData): 531 | trade = Trade(jsonData['tradingSymbol']) 532 | trade.tradeID = jsonData['tradeID'] 533 | trade.strategy = jsonData['strategy'] 534 | trade.direction = jsonData['direction'] 535 | trade.productType = jsonData['productType'] 536 | trade.isFutures = jsonData['isFutures'] 537 | trade.isOptions = jsonData['isOptions'] 538 | trade.optionType = jsonData['optionType'] 539 | trade.placeMarketOrder = jsonData['placeMarketOrder'] 540 | trade.intradaySquareOffTimestamp = jsonData['intradaySquareOffTimestamp'] 541 | trade.requestedEntry = jsonData['requestedEntry'] 542 | trade.entry = jsonData['entry'] 543 | trade.qty = jsonData['qty'] 544 | trade.filledQty = jsonData['filledQty'] 545 | trade.initialStopLoss = jsonData['initialStopLoss'] 546 | trade.stopLoss = jsonData['stopLoss'] 547 | trade.target = jsonData['target'] 548 | trade.cmp = jsonData['cmp'] 549 | trade.tradeState = jsonData['tradeState'] 550 | trade.timestamp = jsonData['timestamp'] 551 | trade.createTimestamp = jsonData['createTimestamp'] 552 | trade.startTimestamp = jsonData['startTimestamp'] 553 | trade.endTimestamp = jsonData['endTimestamp'] 554 | trade.pnl = jsonData['pnl'] 555 | trade.pnlPercentage = jsonData['pnlPercentage'] 556 | trade.exit = jsonData['exit'] 557 | trade.exitReason = jsonData['exitReason'] 558 | trade.exchange = jsonData['exchange'] 559 | trade.entryOrder = TradeManager.convertJSONToOrder(jsonData['entryOrder']) 560 | trade.slOrder = TradeManager.convertJSONToOrder(jsonData['slOrder']) 561 | trade.targetOrder = TradeManager.convertJSONToOrder(jsonData['targetOrder']) 562 | trade.moveToCost=jsonData['moveToCost'] 563 | return trade 564 | 565 | @staticmethod 566 | def convertJSONToOrder(jsonData): 567 | if jsonData == None: 568 | return None 569 | order = Order() 570 | order.tradingSymbol = jsonData['tradingSymbol'] 571 | order.exchange = jsonData['exchange'] 572 | order.productType = jsonData['productType'] 573 | order.orderType = jsonData['orderType'] 574 | order.price = jsonData['price'] 575 | order.triggerPrice = jsonData['triggerPrice'] 576 | order.qty = jsonData['qty'] 577 | order.orderId = jsonData['orderId'] 578 | order.orderStatus = jsonData['orderStatus'] 579 | order.averagePrice = jsonData['averagePrice'] 580 | order.filledQty = jsonData['filledQty'] 581 | order.pendingQty = jsonData['pendingQty'] 582 | order.orderPlaceTimestamp = jsonData['orderPlaceTimestamp'] 583 | order.lastOrderUpdateTimestamp = jsonData['lastOrderUpdateTimestamp'] 584 | order.message = jsonData['message'] 585 | return order 586 | 587 | @staticmethod 588 | def getLastTradedPrice(tradingSymbol): 589 | return TradeManager.symbolToCMPMap[tradingSymbol] 590 | 591 | 592 | 593 | -------------------------------------------------------------------------------- /src/trademgmt/TradeState.py: -------------------------------------------------------------------------------- 1 | 2 | class TradeState: 3 | CREATED = 'created' # Trade created but not yet order placed, might have not triggered 4 | ACTIVE = 'active' # order placed and trade is active 5 | COMPLETED = 'completed' # completed when exits due to SL/Target/SquareOff 6 | CANCELLED = 'cancelled' # cancelled/rejected comes under this state only 7 | DISABLED = 'disabled' # disable trade if not triggered within the time limits or for any other reason -------------------------------------------------------------------------------- /src/utils/Utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | import uuid 3 | import time 4 | import logging 5 | import calendar 6 | import requests 7 | from datetime import datetime, timedelta 8 | 9 | from config.Config import getHolidays 10 | from config.Config import getTelegramAppConfig 11 | from models.Direction import Direction 12 | from trademgmt.TradeState import TradeState 13 | 14 | class Utils: 15 | dateFormat = "%Y-%m-%d" 16 | timeFormat = "%H:%M:%S" 17 | dateTimeFormat = "%Y-%m-%d %H:%M:%S" 18 | 19 | @staticmethod 20 | def roundOff(price): # Round off to 2 decimal places 21 | return round(price, 2) 22 | 23 | @staticmethod 24 | def roundToNSEPrice(price): 25 | x = round(price, 2) * 20 26 | y = math.ceil(x) 27 | return y / 20 28 | 29 | @staticmethod 30 | def isMarketOpen(): 31 | if Utils.isTodayHoliday(): 32 | return False 33 | now = datetime.now() 34 | marketStartTime = Utils.getMarketStartTime() 35 | marketEndTime = Utils.getMarketEndTime() 36 | return now >= marketStartTime and now <= marketEndTime 37 | 38 | @staticmethod 39 | def isMarketClosedForTheDay(): 40 | # This method returns true if the current time is > marketEndTime 41 | # Please note this will not return true if current time is < marketStartTime on a trading day 42 | if Utils.isTodayHoliday(): 43 | return True 44 | now = datetime.now() 45 | marketEndTime = Utils.getMarketEndTime() 46 | return now > marketEndTime 47 | 48 | @staticmethod 49 | def waitTillMarketOpens(context): 50 | nowEpoch = Utils.getEpoch(datetime.now()) 51 | marketStartTimeEpoch = Utils.getEpoch(Utils.getMarketStartTime()) 52 | waitSeconds = marketStartTimeEpoch - nowEpoch 53 | if waitSeconds > 0: 54 | logging.info("%s: Waiting for %d seconds till market opens...", context, waitSeconds) 55 | time.sleep(waitSeconds) 56 | 57 | @staticmethod 58 | def getEpoch(datetimeObj = None): 59 | # This method converts given datetimeObj to epoch seconds 60 | if datetimeObj == None: 61 | datetimeObj = datetime.now() 62 | epochSeconds = datetime.timestamp(datetimeObj) 63 | return int(epochSeconds) # converting double to long 64 | 65 | @staticmethod 66 | def getMarketStartTime(dateTimeObj = None): 67 | return Utils.getTimeOfDay(9, 15, 0, dateTimeObj) 68 | 69 | @staticmethod 70 | def getMarketEndTime(dateTimeObj = None): 71 | return Utils.getTimeOfDay(15, 30, 0, dateTimeObj) 72 | 73 | @staticmethod 74 | def getTimeOfDay(hours, minutes, seconds, dateTimeObj = None): 75 | if dateTimeObj == None: 76 | dateTimeObj = datetime.now() 77 | dateTimeObj = dateTimeObj.replace(hour=hours, minute=minutes, second=seconds, microsecond=0) 78 | return dateTimeObj 79 | 80 | @staticmethod 81 | def getTimeOfToDay(hours, minutes, seconds): 82 | return Utils.getTimeOfDay(hours, minutes, seconds, datetime.now()) 83 | 84 | @staticmethod 85 | def getTodayDateStr(): 86 | return Utils.convertToDateStr(datetime.now()) 87 | 88 | @staticmethod 89 | def convertToDateStr(datetimeObj): 90 | return datetimeObj.strftime(Utils.dateFormat) 91 | 92 | @staticmethod 93 | def isHoliday(datetimeObj): 94 | dayOfWeek = calendar.day_name[datetimeObj.weekday()] 95 | if dayOfWeek == 'Saturday' or dayOfWeek == 'Sunday': 96 | return True 97 | 98 | dateStr = Utils.convertToDateStr(datetimeObj) 99 | holidays = getHolidays() 100 | if (dateStr in holidays): 101 | return True 102 | else: 103 | return False 104 | 105 | @staticmethod 106 | def isTodayHoliday(): 107 | return Utils.isHoliday(datetime.now()) 108 | 109 | @staticmethod 110 | def generateTradeID(): 111 | return str(uuid.uuid4()) 112 | 113 | @staticmethod 114 | def calculateTradePnl(trade): 115 | if trade.tradeState == TradeState.ACTIVE: 116 | if trade.cmp > 0: 117 | if trade.direction == Direction.LONG: 118 | trade.pnl = Utils.roundOff(trade.filledQty * (trade.cmp - trade.entry)) 119 | else: 120 | trade.pnl = Utils.roundOff(trade.filledQty * (trade.entry - trade.cmp)) 121 | else: 122 | if trade.exit > 0: 123 | if trade.direction == Direction.LONG: 124 | trade.pnl = Utils.roundOff(trade.filledQty * (trade.exit - trade.entry)) 125 | else: 126 | trade.pnl = Utils.roundOff(trade.filledQty * (trade.entry - trade.exit)) 127 | tradeValue = trade.entry * trade.filledQty 128 | if tradeValue > 0: 129 | trade.pnlPercentage = Utils.roundOff(trade.pnl * 100 / tradeValue) 130 | return trade 131 | 132 | @staticmethod 133 | def prepareMonthlyExpiryFuturesSymbol(inputSymbol): 134 | expiryDateTime = Utils.getMonthlyExpiryDayDate() 135 | expiryDateMarketEndTime = Utils.getMarketEndTime(expiryDateTime) 136 | now = datetime.now() 137 | if now > expiryDateMarketEndTime: 138 | # increasing today date by 20 days to get some day in next month passing to getMonthlyExpiryDayDate() 139 | expiryDateTime = Utils.getMonthlyExpiryDayDate(now + timedelta(days=20)) 140 | year2Digits = str(expiryDateTime.year)[2:] 141 | monthShort = calendar.month_name[expiryDateTime.month].upper()[0:3] 142 | futureSymbol = inputSymbol + year2Digits + monthShort + 'FUT' 143 | logging.info('prepareMonthlyExpiryFuturesSymbol[%s] = %s', inputSymbol, futureSymbol) 144 | return futureSymbol 145 | 146 | @staticmethod 147 | def prepareWeeklyOptionsSymbol(inputSymbol, strike, optionType, numWeeksPlus = 0): 148 | expiryDateTime = Utils.getWeeklyExpiryDayDate() 149 | todayMarketStartTime = Utils.getMarketStartTime() 150 | expiryDayMarketEndTime = Utils.getMarketEndTime(expiryDateTime) 151 | if numWeeksPlus > 0: 152 | expiryDateTime = expiryDateTime + timedelta(days=numWeeksPlus * 7) 153 | expiryDateTime = Utils.getWeeklyExpiryDayDate(expiryDateTime) 154 | if todayMarketStartTime > expiryDayMarketEndTime: 155 | expiryDateTime = expiryDateTime + timedelta(days=6) 156 | expiryDateTime = Utils.getWeeklyExpiryDayDate(expiryDateTime) 157 | # Check if monthly and weekly expiry same 158 | expiryDateTimeMonthly = Utils.getMonthlyExpiryDayDate() 159 | weekAndMonthExpriySame = False 160 | if expiryDateTime == expiryDateTimeMonthly: 161 | weekAndMonthExpriySame = True 162 | logging.info('Weekly and Monthly expiry is same for %s', expiryDateTime) 163 | year2Digits = str(expiryDateTime.year)[2:] 164 | optionSymbol = None 165 | if weekAndMonthExpriySame == True: 166 | monthShort = calendar.month_name[expiryDateTime.month].upper()[0:3] 167 | optionSymbol = inputSymbol + str(year2Digits) + monthShort + str(strike) + optionType.upper() 168 | else: 169 | m = expiryDateTime.month 170 | d = expiryDateTime.day 171 | mStr = str(m) 172 | if m == 10: 173 | mStr = "O" 174 | elif m == 11: 175 | mStr = "N" 176 | elif m == 12: 177 | mStr = "D" 178 | dStr = ("0" + str(d)) if d < 10 else str(d) 179 | optionSymbol = inputSymbol + str(year2Digits) + mStr + dStr + str(strike) + optionType.upper() 180 | logging.info('prepareWeeklyOptionsSymbol[%s, %d, %s, %d] = %s', inputSymbol, strike, optionType, numWeeksPlus, optionSymbol) 181 | return optionSymbol 182 | 183 | @staticmethod 184 | def getMonthlyExpiryDayDate(datetimeObj = None): 185 | if datetimeObj == None: 186 | datetimeObj = datetime.now() 187 | year = datetimeObj.year 188 | month = datetimeObj.month 189 | lastDay = calendar.monthrange(year, month)[1] # 2nd entry is the last day of the month 190 | datetimeExpiryDay = datetime(year, month, lastDay) 191 | while calendar.day_name[datetimeExpiryDay.weekday()] != 'Thursday': 192 | datetimeExpiryDay = datetimeExpiryDay - timedelta(days=1) 193 | while Utils.isHoliday(datetimeExpiryDay) == True: 194 | datetimeExpiryDay = datetimeExpiryDay - timedelta(days=1) 195 | 196 | datetimeExpiryDay = Utils.getTimeOfDay(0, 0, 0, datetimeExpiryDay) 197 | return datetimeExpiryDay 198 | 199 | @staticmethod 200 | def getWeeklyExpiryDayDate(dateTimeObj = None): 201 | if dateTimeObj == None: 202 | dateTimeObj = datetime.now() 203 | daysToAdd = 0 204 | if dateTimeObj.weekday() >= 3: 205 | daysToAdd = -1 * (dateTimeObj.weekday() - 3) 206 | else: 207 | daysToAdd = 3 - dateTimeObj.weekday() 208 | datetimeExpiryDay = dateTimeObj + timedelta(days=daysToAdd) 209 | while Utils.isHoliday(datetimeExpiryDay) == True: 210 | datetimeExpiryDay = datetimeExpiryDay - timedelta(days=1) 211 | 212 | datetimeExpiryDay = Utils.getTimeOfDay(0, 0, 0, datetimeExpiryDay) 213 | return datetimeExpiryDay 214 | 215 | @staticmethod 216 | def isTodayWeeklyExpiryDay(): 217 | expiryDate = Utils.getWeeklyExpiryDayDate() 218 | todayDate = Utils.getTimeOfToDay(0, 0, 0) 219 | if expiryDate == todayDate: 220 | return True 221 | return False 222 | 223 | @staticmethod 224 | def isTodayOneDayBeforeWeeklyExpiryDay(): 225 | expiryDate = Utils.getWeeklyExpiryDayDate() 226 | todayDate = Utils.getTimeOfToDay(0, 0, 0) 227 | if expiryDate - timedelta(days=1) == todayDate: 228 | return True 229 | return False 230 | 231 | @staticmethod 232 | def getNearestStrikePrice(price, nearestMultiple = 50): 233 | inputPrice = int(price) 234 | remainder = int(inputPrice % nearestMultiple) 235 | if remainder < int(nearestMultiple / 2): 236 | return inputPrice - remainder 237 | else: 238 | return inputPrice + (nearestMultiple - remainder) 239 | 240 | @staticmethod 241 | def sendMessageTelegramBot(message): 242 | try: 243 | telegramAppConfig = getTelegramAppConfig() 244 | urlReq="https://api.telegram.org/bot"+telegramAppConfig['bot_token']+"/sendMessage"+"?chat_id="+telegramAppConfig['bot_chat_id']+"&text="+message 245 | results= requests.get(urlReq) 246 | except Exception as e: 247 | logging.info('Exception occured in telegram call : %s', str(e)) 248 | 249 | 250 | 251 | --------------------------------------------------------------------------------