diff --git a/README.md b/README.md index 40a85a3..1d3e6ce 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,417 @@ -# Migrated to New Github Repo https://github.com/angel-one/smartapi-java -For latest updates and bug fixes please refer to the new git repo. +# Smart API 1.0 Java client +The official Java client for communicating with [Smart API Connect API](https://smartapi.angelbroking.com). + +Smart API is a set of REST-like APIs that expose many capabilities required to build a complete investment and trading platform. Execute orders in real time, manage user portfolio, stream live market data (WebSockets), and more, with the simple HTTP API collection. + +## Documentation +- [Smart API - HTTP API documentation] (https://smartapi.angelbroking.com/docs) +- [Java library documentation](https://smartapi.angelbroking.com/docs/connect) + +## Usage +- [Download SmartAPI jar file](https://github.com/angelbroking-github/smartapi-java/blob/main/dist) and include it in your build path. + +- Include com.angelbroking.smartapi into build path from maven. Use version 1.0.0 + +## API usage +```java + // Initialize Samart API using clientcode and password. + SmartConnect smartConnect = new SmartConnect(); + + // Provide your api key here + smartConnect.setApiKey(""); + + // Set session expiry callback. + smartConnect.setSessionExpiryHook(new SessionExpiryHook() { + @Override + public void sessionExpired() { + System.out.println("session expired"); + } + }); + + User user = smartConnect.generateSession(, ); + System.out.println(user.toString()); + smartConnect.setAccessToken(user.getAccessToken()); + smartConnect.setUserId(user.getUserId()); + + // token re-generate + + TokenSet tokenSet = smartConnect.renewAccessToken(user.getAccessToken(), + user.getRefreshToken()); + smartConnect.setAccessToken(tokenSet.getAccessToken()); + + /** CONSTANT Details */ + + /* VARIETY */ + /* + * VARIETY_NORMAL: Normal Order (Regular) + * VARIETY_AMO: After Market Order + * VARIETY_STOPLOSS: Stop loss order + * VARIETY_ROBO: ROBO (Bracket) Order + */ + /* TRANSACTION TYPE */ + /* + * TRANSACTION_TYPE_BUY: Buy TRANSACTION_TYPE_SELL: Sell + */ + + /* ORDER TYPE */ + /* + * ORDER_TYPE_MARKET: Market Order(MKT) + * ORDER_TYPE_LIMIT: Limit Order(L) + * ORDER_TYPE_STOPLOSS_LIMIT: Stop Loss Limit Order(SL) + * ORDER_TYPE_STOPLOSS_MARKET: Stop Loss Market Order(SL-M) + */ + + /* PRODUCT TYPE */ + /* + * PRODUCT_DELIVERY: Cash & Carry for equity (CNC) + * PRODUCT_CARRYFORWARD: Normal + * for futures and options (NRML) + * PRODUCT_MARGIN: Margin Delivery + * PRODUCT_INTRADAY: Margin Intraday Squareoff (MIS) + * PRODUCT_BO: Bracket Order + * (Only for ROBO) + */ + + /* DURATION */ + /* + * DURATION_DAY: Valid for a day + * DURATION_IOC: Immediate or Cancel + */ + + /* EXCHANGE */ + /* + * EXCHANGE_BSE: BSE Equity + * EXCHANGE_NSE: NSE Equity + * EXCHANGE_NFO: NSE Future and Options + * EXCHANGE_CDS: NSE Currency + * EXCHANGE_NCDEX: NCDEX Commodity + * EXCHANGE_MCX: MCX Commodity + */ + + /** Place order. */ + public void placeOrder(SmartConnect smartConnect) throws SmartAPIException, IOException { + + OrderParams orderParams = new OrderParams(); + orderParams.variety = "NORMAL"; + orderParams.quantity = 1; + orderParams.symboltoken = "3045"; + orderParams.exchange = Constants.EXCHANGE_NSE; + orderParams.ordertype = Constants.ORDER_TYPE_LIMIT; + orderParams.tradingsymbol = "SBIN-EQ"; + orderParams.producttype = Constants.PRODUCT_INTRADAY; + orderParams.duration = Constants.VALIDITY_DAY; + orderParams.transactiontype = Constants.TRANSACTION_TYPE_BUY; + orderParams.price = 122.2; + orderParams.squareoff = "0"; + orderParams.stoploss = "0"; + + Order order = smartConnect.placeOrder(orderParams, Constants.VARIETY_REGULAR); + } + + /** Modify order. */ + public void modifyOrder(SmartConnect smartConnect) throws SmartAPIException, IOException { + // Order modify request will return order model which will contain only + + OrderParams orderParams = new OrderParams(); + orderParams.quantity = 1; + orderParams.ordertype = Constants.ORDER_TYPE_LIMIT; + orderParams.tradingsymbol = "ASHOKLEY"; + orderParams.symboltoken = "3045"; + orderParams.producttype = Constants.PRODUCT_DELIVERY; + orderParams.exchange = Constants.EXCHANGE_NSE; + orderParams.duration = Constants.VALIDITY_DAY; + orderParams.price = 122.2; + + String orderId = "201216000755110"; + Order order = smartConnect.modifyOrder(orderId, orderParams, Constants.VARIETY_REGULAR); + } + + /** Cancel an order */ + public void cancelOrder(SmartConnect smartConnect) throws SmartAPIException, IOException { + // Order modify request will return order model which will contain only + // order_id. + // Cancel order will return order model which will only have orderId. + Order order = smartConnect.cancelOrder("201009000000015", Constants.VARIETY_REGULAR); + } + + /** Get order details */ + public void getOrder(SmartConnect smartConnect) throws SmartAPIException, IOException { + List orders = smartConnect.getOrderHistory(smartConnect.getUserId()); + for (int i = 0; i < orders.size(); i++) { + System.out.println(orders.get(i).orderId + " " + orders.get(i).status); + } + } + + /** + * Get last price for multiple instruments at once. USers can either pass + * exchange with tradingsymbol or instrument token only. For example {NSE:NIFTY + * 50, BSE:SENSEX} or {256265, 265} + */ + public void getLTP(SmartConnect smartConnect) throws SmartAPIException, IOException { + String exchange = "NSE"; + String tradingSymbol = "SBIN-EQ"; + String symboltoken = "3045"; + JSONObject ltpData = smartConnect.getLTP(exchange, tradingSymbol, symboltoken); + } + + /** Get tradebook */ + public void getTrades(SmartConnect smartConnect) throws SmartAPIException, IOException { + // Returns tradebook. + List trades = smartConnect.getTrades(); + for (int i = 0; i < trades.size(); i++) { + System.out.println(trades.get(i).tradingSymbol + " " + trades.size()); + } + } + + /** Get RMS */ + public void getRMS(SmartConnect smartConnect) throws SmartAPIException, IOException { + // Returns RMS. + JSONObject response = smartConnect.getRMS(); + } + + /** Get Holdings */ + public void getHolding(SmartConnect smartConnect) throws SmartAPIException, IOException { + // Returns Holding. + JSONObject response = smartConnect.getHolding(); + } + + /** Get Position */ + public void getPosition(SmartConnect smartConnect) throws SmartAPIException, IOException { + // Returns Position. + JSONObject response = smartConnect.getPosition(); + } + + /** convert Position */ + public void convertPosition(SmartConnect smartConnect) throws SmartAPIException, IOException { + + JSONObject requestObejct = new JSONObject(); + requestObejct.put("exchange", "NSE"); + requestObejct.put("oldproducttype", "DELIVERY"); + requestObejct.put("newproducttype", "MARGIN"); + requestObejct.put("tradingsymbol", "SBIN-EQ"); + requestObejct.put("transactiontype", "BUY"); + requestObejct.put("quantity", 1); + requestObejct.put("type", "DAY"); + + JSONObject response = smartConnect.convertPosition(requestObejct); + } + + /** Create Gtt Rule*/ + public void createRule(SmartConnect smartConnect)throws SmartAPIException,IOException{ + GttParams gttParams= new GttParams(); + + gttParams.tradingsymbol="SBIN-EQ"; + gttParams.symboltoken="3045"; + gttParams.exchange="NSE"; + gttParams.producttype="MARGIN"; + gttParams.transactiontype="BUY"; + gttParams.price= 100000.01; + gttParams.qty=10; + gttParams.disclosedqty=10; + gttParams.triggerprice=20000.1; + gttParams.timeperiod=300; + + Gtt gtt = smartConnect.gttCreateRule(gttParams); + } + + + /** Modify Gtt Rule */ + public void modifyRule(SmartConnect smartConnect)throws SmartAPIException,IOException{ + GttParams gttParams= new GttParams(); + + gttParams.tradingsymbol="SBIN-EQ"; + gttParams.symboltoken="3045"; + gttParams.exchange="NSE"; + gttParams.producttype="MARGIN"; + gttParams.transactiontype="BUY"; + gttParams.price= 100000.1; + gttParams.qty=10; + gttParams.disclosedqty=10; + gttParams.triggerprice=20000.1; + gttParams.timeperiod=300; + + Integer id= 1000051; + + Gtt gtt = smartConnect.gttModifyRule(id,gttParams); + } + + /** Cancel Gtt Rule */ + public void cancelRule(SmartConnect smartConnect)throws SmartAPIException, IOException{ + Integer id=1000051; + String symboltoken="3045"; + String exchange="NSE"; + + Gtt gtt = smartConnect.gttCancelRule(id,symboltoken,exchange); + } + + /** Gtt Rule Details */ + public void ruleDetails(SmartConnect smartConnect)throws SmartAPIException, IOException{ + Integer id=1000051; + + JSONObject gtt = smartConnect.gttRuleDetails(id); + } + + /** Gtt Rule Lists */ + public void ruleList(SmartConnect smartConnect)throws SmartAPIException, IOException{ + + List status=new ArrayList(){{ + add("NEW"); + add("CANCELLED"); + add("ACTIVE"); + add("SENTTOEXCHANGE"); + add("FORALL"); + }}; + Integer page=1; + Integer count=10; + + JSONArray gtt = smartConnect.gttRuleList(status,page,count); + } + + /** Historic Data */ + public void getCandleData(SmartConnect smartConnect) throws SmartAPIException, IOException { + + JSONObject requestObejct = new JSONObject(); + requestObejct.put("exchange", "NSE"); + requestObejct.put("symboltoken", "3045"); + requestObejct.put("interval", "ONE_MINUTE"); + requestObejct.put("fromdate", "2021-03-08 09:00"); + requestObejct.put("todate", "2021-03-09 09:20"); + + String response = smartConnect.candleData(requestObejct); + } + + /** Logout user. */ + public void logout(SmartConnect smartConnect) throws SmartAPIException, IOException { + /** Logout user and kill session. */ + JSONObject jsonObject = smartConnect.logout(); + } + +``` +For more details, take a look at Examples.java in sample directory. + +## WebSocket live streaming data + +```java + + /* SmartAPITicker */ + String clientId = ""; + User user = smartConnect.generateSession("", ""); + String feedToken = user.getFeedToken(); + String strWatchListScript = "nse_cm|2885&nse_cm|1594&nse_cm|11536&mcx_fo|221658"; + String task = "mw"; + + examples.tickerUsage(clientId, feedToken, strWatchListScript, task); + + /* + * String jwtToken = user.getAccessToken(); + * String apiKey = ""; + * String actionType = "subscribe"; String feedType = "order_feed"; + * + * examples.smartWebSocketUsage(clientId, jwtToken, apiKey, actionType, + * feedType); + * + */ + + public void tickerUsage(String clientId, String feedToken, String strWatchListScript, String task) + throws SmartAPIException { + + SmartAPITicker tickerProvider = new SmartAPITicker(clientId, feedToken, strWatchListScript, task); + + tickerProvider.setOnConnectedListener(new OnConnect() { + @Override + public void onConnected() { + System.out.println("subscribe() called!"); + tickerProvider.subscribe(); + } + }); + + tickerProvider.setOnTickerArrivalListener(new OnTicks() { + @Override + public void onTicks(JSONArray ticks) { + System.out.println("ticker data: " + ticks.toString()); + } + }); + + /** + * connects to Smart API ticker server for getting live quotes + */ + tickerProvider.connect(); + + /** + * You can check, if websocket connection is open or not using the following + * method. + */ + boolean isConnected = tickerProvider.isConnectionOpen(); + System.out.println(isConnected); + + // After using SmartAPI ticker, close websocket connection. + // tickerProvider.disconnect(); + + } + + public void smartWebSocketUsage(String clientId, String jwtToken, String apiKey, String actionType, String feedType) + throws SmartAPIException { + + SmartWebsocket smartWebsocket = new SmartWebsocket(clientId, jwtToken, apiKey, actionType, feedType); + + smartWebsocket.setOnConnectedListener(new SmartWSOnConnect() { + + @Override + public void onConnected() { + + smartWebsocket.runscript(); + } + }); + + smartWebsocket.setOnDisconnectedListener(new SmartWSOnDisconnect() { + @Override + public void onDisconnected() { + System.out.println("onDisconnected"); + } + }); + + /** Set error listener to listen to errors. */ + smartWebsocket.setOnErrorListener(new SmartWSOnError() { + @Override + public void onError(Exception exception) { + System.out.println("onError: " + exception.getMessage()); + } + + @Override + public void onError(SmartAPIException smartAPIException) { + System.out.println("onError: " + smartAPIException.getMessage()); + } + + @Override + public void onError(String error) { + System.out.println("onError: " + error); + } + }); + + smartWebsocket.setOnTickerArrivalListener(new SmartWSOnTicks() { + @Override + public void onTicks(JSONArray ticks) { + System.out.println("ticker data: " + ticks.toString()); + } + }); + + /** + * connects to Smart API ticker server for getting live quotes + */ + smartWebsocket.connect(); + + /** + * You can check, if websocket connection is open or not using the following + * method. + */ + boolean isConnected = smartWebsocket.isConnectionOpen(); + System.out.println(isConnected); + + // After using SmartAPI ticker, close websocket connection. + // smartWebsocket.disconnect(); + + } + +``` +For more details, take a look at Examples.java in sample directory. diff --git a/pom.xml b/pom.xml index e19bcb3..90bf75d 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,9 @@ UTF-8 + 5.8.1 + 3.10.0 + 3.19.0 @@ -28,6 +31,7 @@ org.apache.maven.plugins maven-compiler-plugin + 3.8.1 1.8 1.8 @@ -85,6 +89,31 @@ gson 2.6.2 + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + + + org.mockito + mockito-core + ${mockito.version} + test + + + + + org.assertj + assertj-core + ${assertj.version} + test + + diff --git a/src/main/java/com/angelbroking/smartapi/Routes.java b/src/main/java/com/angelbroking/smartapi/Routes.java index 59c4a08..81a3ab7 100644 --- a/src/main/java/com/angelbroking/smartapi/Routes.java +++ b/src/main/java/com/angelbroking/smartapi/Routes.java @@ -16,6 +16,7 @@ public class Routes { private static String _rootUrl = "https://apiconnect.angelbroking.com"; private static String _loginUrl = "https://apiconnect.angelbroking.com/rest/auth/angelbroking/user/v1/loginByPassword"; private static String _wsuri = "wss://wsfeeds.angelbroking.com/NestHtml5Mobile/socket/stream"; + private static String _smartStreamWSURI = "ws://smartapisocket.angelone.in/smart-stream"; private static String _swsuri = "wss://smartapisocket.angelbroking.com/websocket"; // Initialize all routes, @@ -63,4 +64,8 @@ public String getWsuri() { public String getSWsuri() { return _swsuri; } + + public String getSmartStreamWSURI() { + return _smartStreamWSURI; + } } diff --git a/src/main/java/com/angelbroking/smartapi/smartstream/models/ExchangeType.java b/src/main/java/com/angelbroking/smartapi/smartstream/models/ExchangeType.java new file mode 100644 index 0000000..528ccc1 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/ExchangeType.java @@ -0,0 +1,24 @@ +package com.angelbroking.smartapi.smartstream.models; + +public enum ExchangeType { + NSE_CM(1), NSE_FO(2), BSE_CM(3), BSE_FO(4), MCX_FO(5), NCX_FO(7), CDE_FO(13); + + private int val; + + private ExchangeType(int val) { + this.val = val; + } + + public int getVal() { + return this.val; + } + + public static ExchangeType findByValue(int val) { + for(ExchangeType entry : ExchangeType.values()) { + if(entry.getVal() == val) { + return entry; + } + } + return null; + } +} diff --git a/src/main/java/com/angelbroking/smartapi/smartstream/models/LTP.java b/src/main/java/com/angelbroking/smartapi/smartstream/models/LTP.java new file mode 100644 index 0000000..720737e --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/LTP.java @@ -0,0 +1,43 @@ +package com.angelbroking.smartapi.smartstream.models; + +public class LTP { + public static final int PACKET_SIZE_IN_BYTES = 51; + + private TokenID token; + private long sequenceNumber; + private long exchangeFeedTimeEpochMillis; + private long lastTradedPrice; + + public TokenID getToken() { + return token; + } + + public void setToken(TokenID token) { + this.token = token; + } + + public long getSequenceNumber() { + return sequenceNumber; + } + + public void setSequenceNumber(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + public long getExchangeFeedTimeEpochMillis() { + return exchangeFeedTimeEpochMillis; + } + + public void setExchangeFeedTimeEpochMillis(long exchangeFeedTimeEpochMillis) { + this.exchangeFeedTimeEpochMillis = exchangeFeedTimeEpochMillis; + } + + public long getLastTradedPrice() { + return lastTradedPrice; + } + + public void setLastTradedPrice(long lastTradedPrice) { + this.lastTradedPrice = lastTradedPrice; + } + +} diff --git a/src/main/java/com/angelbroking/smartapi/smartstream/models/Quote.java b/src/main/java/com/angelbroking/smartapi/smartstream/models/Quote.java new file mode 100644 index 0000000..077be4e --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/Quote.java @@ -0,0 +1,124 @@ +package com.angelbroking.smartapi.smartstream.models; + +public class Quote { + public static final int PACKET_SIZE_IN_BYTES = 123; + + private TokenID token; + private long sequenceNumber; + private long exchangeFeedTimeEpochMillis; + private long lastTradedPrice; + private long lastTradedQty; + private long avgTradedPrice; + private long volumeTradedToday; + private double totalBuyQty; + private double totalSellQty; + private long openPrice; + private long highPrice; + private long lowPrice; + private long closePrice; + + public TokenID getToken() { + return token; + } + + public void setToken(TokenID token) { + this.token = token; + } + + public long getSequenceNumber() { + return sequenceNumber; + } + + public void setSequenceNumber(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + public long getExchangeFeedTimeEpochMillis() { + return exchangeFeedTimeEpochMillis; + } + + public void setExchangeFeedTimeEpochMillis(long exchangeFeedTimeEpochMillis) { + this.exchangeFeedTimeEpochMillis = exchangeFeedTimeEpochMillis; + } + + public long getLastTradedPrice() { + return lastTradedPrice; + } + + public void setLastTradedPrice(long lastTradedPrice) { + this.lastTradedPrice = lastTradedPrice; + } + + public long getLastTradedQty() { + return lastTradedQty; + } + + public void setLastTradedQty(long lastTradedQty) { + this.lastTradedQty = lastTradedQty; + } + + public long getAvgTradedPrice() { + return avgTradedPrice; + } + + public void setAvgTradedPrice(long avgTradedPrice) { + this.avgTradedPrice = avgTradedPrice; + } + + public long getVolumeTradedToday() { + return volumeTradedToday; + } + + public void setVolumeTradedToday(long volumeTradedToday) { + this.volumeTradedToday = volumeTradedToday; + } + + public double getTotalBuyQty() { + return totalBuyQty; + } + + public void setTotalBuyQty(double totalBuyQty) { + this.totalBuyQty = totalBuyQty; + } + + public double getTotalSellQty() { + return totalSellQty; + } + + public void setTotalSellQty(double totalSellQty) { + this.totalSellQty = totalSellQty; + } + + public long getOpenPrice() { + return openPrice; + } + + public void setOpenPrice(long openPrice) { + this.openPrice = openPrice; + } + + public long getHighPrice() { + return highPrice; + } + + public void setHighPrice(long highPrice) { + this.highPrice = highPrice; + } + + public long getLowPrice() { + return lowPrice; + } + + public void setLowPrice(long lowPrice) { + this.lowPrice = lowPrice; + } + + public long getClosePrice() { + return closePrice; + } + + public void setClosePrice(long closePrice) { + this.closePrice = closePrice; + } + +} diff --git a/src/main/java/com/angelbroking/smartapi/smartstream/models/SmartApiBBSInfo.java b/src/main/java/com/angelbroking/smartapi/smartstream/models/SmartApiBBSInfo.java new file mode 100644 index 0000000..9ba5f45 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/SmartApiBBSInfo.java @@ -0,0 +1,45 @@ +package com.angelbroking.smartapi.smartstream.models; + +public class SmartApiBBSInfo { + public static final int BYTES = (2 * Short.BYTES) + (2 * Long.BYTES); + + // siBbBuySellFlag = 1 buy + // siBbBuySellFlag = 0 sell + private short siBbBuySellFlag = -1; + private long lQuantity = -1; + private long lPrice = -1; + private short siNumberOfOrders = -1; + + public short getSiBbBuySellFlag() { + return siBbBuySellFlag; + } + + public void setSiBbBuySellFlag(short siBbBuySellFlag) { + this.siBbBuySellFlag = siBbBuySellFlag; + } + + public long getlQuantity() { + return lQuantity; + } + + public void setlQuantity(long lQuantity) { + this.lQuantity = lQuantity; + } + + public long getlPrice() { + return lPrice; + } + + public void setlPrice(long lPrice) { + this.lPrice = lPrice; + } + + public short getSiNumberOfOrders() { + return siNumberOfOrders; + } + + public void setSiNumberOfOrders(short siNumberOfOrders) { + this.siNumberOfOrders = siNumberOfOrders; + } + +} \ No newline at end of file diff --git a/src/main/java/com/angelbroking/smartapi/smartstream/models/SmartStreamAction.java b/src/main/java/com/angelbroking/smartapi/smartstream/models/SmartStreamAction.java new file mode 100644 index 0000000..4091b49 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/SmartStreamAction.java @@ -0,0 +1,24 @@ +package com.angelbroking.smartapi.smartstream.models; + +public enum SmartStreamAction { + SUBS(1), UNSUBS(0); + + private int val; + + private SmartStreamAction(int val) { + this.val = val; + } + + public static SmartStreamAction findByVal(int val) { + for(SmartStreamAction entry : SmartStreamAction.values()) { + if(entry.getVal() == val) { + return entry; + } + } + return null; + } + + public int getVal() { + return this.val; + } +} \ No newline at end of file diff --git a/src/main/java/com/angelbroking/smartapi/smartstream/models/SmartStreamError.java b/src/main/java/com/angelbroking/smartapi/smartstream/models/SmartStreamError.java new file mode 100644 index 0000000..f5ca0e2 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/SmartStreamError.java @@ -0,0 +1,14 @@ +package com.angelbroking.smartapi.smartstream.models; + +public class SmartStreamError { + private Throwable exception; + + public Throwable getException() { + return exception; + } + + public void setException(Throwable exception) { + this.exception = exception; + } + +} diff --git a/src/main/java/com/angelbroking/smartapi/smartstream/models/SmartStreamSubsMode.java b/src/main/java/com/angelbroking/smartapi/smartstream/models/SmartStreamSubsMode.java new file mode 100644 index 0000000..9e2bfcc --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/SmartStreamSubsMode.java @@ -0,0 +1,31 @@ +package com.angelbroking.smartapi.smartstream.models; + +public enum SmartStreamSubsMode { + LTP(1), QUOTE(2), SNAP_QUOTE(3); + + private static final int SIZE = SmartStreamSubsMode.values().length; + + private int val; + + private SmartStreamSubsMode(int val) { + this.val = val; + } + + public static SmartStreamSubsMode findByVal(int val) { + for(SmartStreamSubsMode entry : SmartStreamSubsMode.values()) { + if(entry.getVal() == val) { + return entry; + } + } + return null; + } + + public static int size() { + return SIZE; + } + + public int getVal() { + return this.val; + } + +} \ No newline at end of file diff --git a/src/main/java/com/angelbroking/smartapi/smartstream/models/SnapQuote.java b/src/main/java/com/angelbroking/smartapi/smartstream/models/SnapQuote.java new file mode 100644 index 0000000..957a3df --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/SnapQuote.java @@ -0,0 +1,205 @@ +package com.angelbroking.smartapi.smartstream.models; + +public class SnapQuote { + public static final int PACKET_SIZE_IN_BYTES = 379; + + private TokenID token; + private long sequenceNumber; + private long exchangeFeedTimeEpochMillis; + private long lastTradedPrice; + private long lastTradedQty; + private long avgTradedPrice; + private long volumeTradedToday; + private double totalBuyQty; + private double totalSellQty; + private long openPrice; + private long highPrice; + private long lowPrice; + private long closePrice; + private long lastTradedTimestamp = 0; + private long openInterest = 0; + private double openInterestChangePerc = 0; + private SmartApiBBSInfo[] bestFiveBuy; + private SmartApiBBSInfo[] bestFiveSell; + private long upperCircuit = 0; + private long lowerCircuit = 0; + private long yearlyHighPrice = 0; + private long yearlyLowPrice = 0; + + public TokenID getToken() { + return token; + } + + public void setToken(TokenID token) { + this.token = token; + } + + public long getSequenceNumber() { + return sequenceNumber; + } + + public void setSequenceNumber(long sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + public long getExchangeFeedTimeEpochMillis() { + return exchangeFeedTimeEpochMillis; + } + + public void setExchangeFeedTimeEpochMillis(long exchangeFeedTimeEpochMillis) { + this.exchangeFeedTimeEpochMillis = exchangeFeedTimeEpochMillis; + } + + public long getLastTradedPrice() { + return lastTradedPrice; + } + + public void setLastTradedPrice(long lastTradedPrice) { + this.lastTradedPrice = lastTradedPrice; + } + + public long getLastTradedQty() { + return lastTradedQty; + } + + public void setLastTradedQty(long lastTradedQty) { + this.lastTradedQty = lastTradedQty; + } + + public long getAvgTradedPrice() { + return avgTradedPrice; + } + + public void setAvgTradedPrice(long avgTradedPrice) { + this.avgTradedPrice = avgTradedPrice; + } + + public long getVolumeTradedToday() { + return volumeTradedToday; + } + + public void setVolumeTradedToday(long volumeTradedToday) { + this.volumeTradedToday = volumeTradedToday; + } + + public double getTotalBuyQty() { + return totalBuyQty; + } + + public void setTotalBuyQty(double totalBuyQty) { + this.totalBuyQty = totalBuyQty; + } + + public double getTotalSellQty() { + return totalSellQty; + } + + public void setTotalSellQty(double totalSellQty) { + this.totalSellQty = totalSellQty; + } + + public long getOpenPrice() { + return openPrice; + } + + public void setOpenPrice(long openPrice) { + this.openPrice = openPrice; + } + + public long getHighPrice() { + return highPrice; + } + + public void setHighPrice(long highPrice) { + this.highPrice = highPrice; + } + + public long getLowPrice() { + return lowPrice; + } + + public void setLowPrice(long lowPrice) { + this.lowPrice = lowPrice; + } + + public long getClosePrice() { + return closePrice; + } + + public void setClosePrice(long closePrice) { + this.closePrice = closePrice; + } + + public long getLastTradedTimestamp() { + return lastTradedTimestamp; + } + + public void setLastTradedTimestamp(long lastTradedTimestamp) { + this.lastTradedTimestamp = lastTradedTimestamp; + } + + public long getOpenInterest() { + return openInterest; + } + + public void setOpenInterest(long openInterest) { + this.openInterest = openInterest; + } + + public double getOpenInterestChangePerc() { + return openInterestChangePerc; + } + + public void setOpenInterestChangePerc(double openInterestChangePerc) { + this.openInterestChangePerc = openInterestChangePerc; + } + + public SmartApiBBSInfo[] getBestFiveBuy() { + return bestFiveBuy; + } + + public void setBestFiveBuy(SmartApiBBSInfo[] bestFiveBuy) { + this.bestFiveBuy = bestFiveBuy; + } + + public SmartApiBBSInfo[] getBestFiveSell() { + return bestFiveSell; + } + + public void setBestFiveSell(SmartApiBBSInfo[] bestFiveSell) { + this.bestFiveSell = bestFiveSell; + } + + public long getUpperCircuit() { + return upperCircuit; + } + + public void setUpperCircuit(long upperCircuit) { + this.upperCircuit = upperCircuit; + } + + public long getLowerCircuit() { + return lowerCircuit; + } + + public void setLowerCircuit(long lowerCircuit) { + this.lowerCircuit = lowerCircuit; + } + + public long getYearlyHighPrice() { + return yearlyHighPrice; + } + + public void setYearlyHighPrice(long yearlyHighPrice) { + this.yearlyHighPrice = yearlyHighPrice; + } + + public long getYearlyLowPrice() { + return yearlyLowPrice; + } + + public void setYearlyLowPrice(long yearlyLowPrice) { + this.yearlyLowPrice = yearlyLowPrice; + } + +} diff --git a/src/main/java/com/angelbroking/smartapi/smartstream/models/TokenID.java b/src/main/java/com/angelbroking/smartapi/smartstream/models/TokenID.java new file mode 100644 index 0000000..57463e0 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/TokenID.java @@ -0,0 +1,43 @@ +package com.angelbroking.smartapi.smartstream.models; + +public class TokenID { + + private ExchangeType exchangeType; + private String token; + + public TokenID(ExchangeType exchangeType, String token) throws IllegalArgumentException { + if(exchangeType == null || token == null || token.isEmpty()) { + throw new IllegalArgumentException("Invalid exchangeType or token."); + } + this.exchangeType = exchangeType; + this.token = token; + } + + public ExchangeType getExchangeType() { + return exchangeType; + } + + public String getToken() { + return token; + } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof TokenID)) { + return false; + } + + TokenID newObj = (TokenID) obj; + return this.exchangeType.equals(newObj.getExchangeType()) && this.token.equals(newObj.getToken()); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return (exchangeType.name()+"-"+token); + } +} diff --git a/src/main/java/com/angelbroking/smartapi/smartstream/ticker/SmartStreamListener.java b/src/main/java/com/angelbroking/smartapi/smartstream/ticker/SmartStreamListener.java new file mode 100644 index 0000000..06b4755 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/ticker/SmartStreamListener.java @@ -0,0 +1,16 @@ +package com.angelbroking.smartapi.smartstream.ticker; + +import com.angelbroking.smartapi.smartstream.models.LTP; +import com.angelbroking.smartapi.smartstream.models.Quote; +import com.angelbroking.smartapi.smartstream.models.SmartStreamError; +import com.angelbroking.smartapi.smartstream.models.SnapQuote; + +public interface SmartStreamListener { + void onLTPArrival(LTP ltp); + void onQuoteArrival(Quote quote); + void onSnapQuoteArrival(SnapQuote snapQuote); + + void onConnected(); + void onError(SmartStreamError error); + void onPong(); +} \ No newline at end of file diff --git a/src/main/java/com/angelbroking/smartapi/smartstream/ticker/SmartStreamTicker.java b/src/main/java/com/angelbroking/smartapi/smartstream/ticker/SmartStreamTicker.java new file mode 100644 index 0000000..ab7ad66 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/ticker/SmartStreamTicker.java @@ -0,0 +1,315 @@ +package com.angelbroking.smartapi.smartstream.ticker; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.json.JSONArray; +import org.json.JSONObject; + +import com.angelbroking.smartapi.Routes; +import com.angelbroking.smartapi.http.exceptions.SmartAPIException; +import com.angelbroking.smartapi.smartstream.models.ExchangeType; +import com.angelbroking.smartapi.smartstream.models.LTP; +import com.angelbroking.smartapi.smartstream.models.Quote; +import com.angelbroking.smartapi.smartstream.models.SmartStreamAction; +import com.angelbroking.smartapi.smartstream.models.SmartStreamError; +import com.angelbroking.smartapi.smartstream.models.SmartStreamSubsMode; +import com.angelbroking.smartapi.smartstream.models.SnapQuote; +import com.angelbroking.smartapi.smartstream.models.TokenID; +import com.angelbroking.smartapi.utils.ByteUtils; +import com.angelbroking.smartapi.utils.Utils; +import com.neovisionaries.ws.client.WebSocket; +import com.neovisionaries.ws.client.WebSocketAdapter; +import com.neovisionaries.ws.client.WebSocketException; +import com.neovisionaries.ws.client.WebSocketExtension; +import com.neovisionaries.ws.client.WebSocketFactory; +import com.neovisionaries.ws.client.WebSocketFrame; + +public class SmartStreamTicker { + + private static final int PING_INTERVAL = 10000; // 10 seconds + private static final String CLIENT_ID_HEADER = "x-client-code"; + private static final String FEED_TOKEN_HEADER = "x-feed-token"; + private static final String CLIENT_LIB_HEADER = "x-client-lib"; + + private Routes routes = new Routes(); + private final String wsuri = +// "ws://10.253.17.25:8080/smart-stream"; // GPX UAT Physical +// "ws://192.168.0.113:8080/smart-stream"; +// "ws://mds.angelone.in/smart-stream"; +// "ws://mds-uat.angelone.in/smart-stream"; +// "ws://mds-dev.angelone.in/smart-stream"; +// "ws://172.31.25.143:8080/smart-stream"; +// "ws://172.31.25.38:8080/smart-stream"; + routes.getSmartStreamWSURI(); + private SmartStreamListener smartStreamListener; + private WebSocket ws; + private String clientId; + private String feedToken; + private EnumMap> tokensByModeMap = new EnumMap<>(SmartStreamSubsMode.class); + + /** + * Initialize SmartStreamTicker. + */ + public SmartStreamTicker(String clientId, String feedToken, SmartStreamListener smartStreamListener) { + if (Utils.isEmpty(clientId) || Utils.isEmpty(feedToken) || smartStreamListener == null) { + throw new IllegalArgumentException( + "clientId, feedToken and smartStreamListener should not be empty or null"); + } + + this.clientId = clientId; + this.feedToken = feedToken; + this.smartStreamListener = smartStreamListener; + init(); + } + + private void init() { + try { + ws = new WebSocketFactory().setVerifyHostname(false).createSocket(wsuri).setPingInterval(PING_INTERVAL); + ws.addHeader(CLIENT_ID_HEADER, clientId); // mandatory header + ws.addHeader(FEED_TOKEN_HEADER, feedToken); // mandatory header + ws.addHeader(CLIENT_LIB_HEADER, "JAVA"); // optional header on the server + ws.addListener(getWebsocketAdapter()); + } catch (IOException e) { + if (smartStreamListener != null) { + smartStreamListener.onError(getErrorHolder(e)); + } + } + } + + private SmartStreamError getErrorHolder(Throwable e) { + SmartStreamError error = new SmartStreamError(); + error.setException(e); + return error; + } + + /** Returns a WebSocketAdapter to listen to ticker related events. */ + public WebSocketAdapter getWebsocketAdapter() { + return new WebSocketAdapter() { + + @Override + public void onConnected(WebSocket websocket, Map> headers) throws WebSocketException { + smartStreamListener.onConnected(); + } + + @Override + public void onTextMessage(WebSocket websocket, String message) throws Exception { + super.onTextMessage(websocket, message); + } + + @Override + public void onBinaryMessage(WebSocket websocket, byte[] binary) { + try { + SmartStreamSubsMode mode = SmartStreamSubsMode.findByVal(binary[0]); + if (mode == null) { + smartStreamListener.onError(getErrorHolder(new SmartAPIException( + "Invalid SubsMode=" + binary[0] + " in the response binary packet"))); + return; + } + + switch (mode) { + case LTP: { + ByteBuffer packet = ByteBuffer.wrap(binary).order(ByteOrder.LITTLE_ENDIAN); + LTP ltp = ByteUtils.mapToLTP(packet); + smartStreamListener.onLTPArrival(ltp); + break; + } + case QUOTE: { + ByteBuffer packet = ByteBuffer.wrap(binary).order(ByteOrder.LITTLE_ENDIAN); + Quote quote = ByteUtils.mapToQuote(packet); + smartStreamListener.onQuoteArrival(quote); + break; + } + case SNAP_QUOTE: { + ByteBuffer packet = ByteBuffer.wrap(binary).order(ByteOrder.LITTLE_ENDIAN); + SnapQuote snapQuote = ByteUtils.mapToSnapQuote(packet); + smartStreamListener.onSnapQuoteArrival(snapQuote); + break; + } + default: + smartStreamListener.onError(getErrorHolder( + new SmartAPIException("SubsMode=" + mode + " in the response is not handled."))); + break; + } + super.onBinaryMessage(websocket, binary); + } catch (Exception e) { + smartStreamListener.onError(getErrorHolder(e)); + } + } + + @Override + public void onPongFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { + try { + smartStreamListener.onPong(); + } catch (Exception e) { + SmartStreamError error = new SmartStreamError(); + error.setException(e); + smartStreamListener.onError(error); + } + } + + /** + * On disconnection, return statement ensures that the thread ends. + * + * @param websocket + * @param serverCloseFrame + * @param clientCloseFrame + * @param closedByServer + * @throws Exception + */ + @Override + public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame, + WebSocketFrame clientCloseFrame, boolean closedByServer) { + + try { + if (closedByServer) { + if (serverCloseFrame.getCloseCode() == 1001) { + + } + reconnectAndResubscribe(); + } + + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public void onCloseFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { + super.onCloseFrame(websocket, frame); + } + }; + } + + private void reconnectAndResubscribe() throws WebSocketException { + init(); + connect(); + resubscribe(); + } + + /** Disconnects websocket connection. */ + public void disconnect() { + + if (ws != null && ws.isOpen()) { + ws.disconnect(); + } + } + + /** + * Returns true if websocket connection is open. + * + * @return boolean + */ + public boolean isConnectionOpen() { + return (ws != null) && ws.isOpen(); + } + + /** + * Returns true if websocket connection is closed. + * + * @return boolean + */ + public boolean isConnectionClosed() { + return !isConnectionOpen(); + } + + /** + * Subscribes tokens. + */ + public void subscribe(SmartStreamSubsMode mode, Set tokens) { + if (ws != null) { + if (ws.isOpen()) { + JSONObject wsMWJSONRequest = getApiRequest(SmartStreamAction.SUBS, mode, tokens); + ws.sendText(wsMWJSONRequest.toString()); + } else { + smartStreamListener.onError(getErrorHolder(new SmartAPIException("ticker is not connected", "504"))); + } + } else { + smartStreamListener.onError(getErrorHolder(new SmartAPIException("ticker is null not connected", "504"))); + } + } + + /** + * Unsubscribes tokens. + */ + public void unsubscribe(SmartStreamSubsMode mode, Set tokens) { + if (ws != null) { + if (ws.isOpen()) { + getApiRequest(SmartStreamAction.UNSUBS, mode, tokens); + } else { + smartStreamListener.onError(getErrorHolder(new SmartAPIException("ticker is not connected", "504"))); + } + } else { + smartStreamListener.onError(getErrorHolder(new SmartAPIException("ticker is null not connected", "504"))); + } + } + + private JSONArray generateExchangeTokensList(Set tokens) { + Map tokensByExchange = new EnumMap<>(ExchangeType.class); + tokens.stream().forEach(t -> { + JSONArray tokenList = tokensByExchange.get(t.getExchangeType()); + if (tokenList == null) { + tokenList = new JSONArray(); + tokensByExchange.put(t.getExchangeType(), tokenList); + } + + tokenList.put(t.getToken()); + }); + + JSONArray exchangeTokenList = new JSONArray(); + tokensByExchange.forEach((ex, t) -> { + JSONObject exchangeTokenObj = new JSONObject(); + exchangeTokenObj.put("exchangeType", ex.getVal()); + exchangeTokenObj.put("tokens", t); + + exchangeTokenList.put(exchangeTokenObj); + }); + + return exchangeTokenList; + } + + /** + * resubscribe existing tokens. + */ + public void resubscribe() { + if (ws != null) { + if (ws.isOpen()) { + + JSONObject wsMWJSONRequest = new JSONObject(); + wsMWJSONRequest.put("token", this.feedToken); + wsMWJSONRequest.put("user", this.clientId); + wsMWJSONRequest.put("acctid", this.clientId); + + ws.sendText(wsMWJSONRequest.toString()); + + } else { + smartStreamListener.onError(getErrorHolder(new SmartAPIException("ticker is not connected", "504"))); + } + } else { + smartStreamListener.onError(getErrorHolder(new SmartAPIException("ticker is null not connected", "504"))); + } + } + + private JSONObject getApiRequest(SmartStreamAction action, SmartStreamSubsMode mode, Set tokens) { + JSONObject params = new JSONObject(); + params.put("mode", mode.getVal()); + params.put("tokenList", this.generateExchangeTokensList(tokens)); + + JSONObject wsMWJSONRequest = new JSONObject(); + wsMWJSONRequest.put("action", action.getVal()); + wsMWJSONRequest.put("params", params); + + return wsMWJSONRequest; + } + + public void connect() throws WebSocketException { + ws.connect(); + System.out.println("connected to uri: "+ wsuri); + } + +} diff --git a/src/main/java/com/angelbroking/smartapi/utils/ByteUtils.java b/src/main/java/com/angelbroking/smartapi/utils/ByteUtils.java new file mode 100644 index 0000000..bdfe262 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/utils/ByteUtils.java @@ -0,0 +1,64 @@ +package com.angelbroking.smartapi.utils; + +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; + +import com.angelbroking.smartapi.smartstream.models.ExchangeType; +import com.angelbroking.smartapi.smartstream.models.LTP; +import com.angelbroking.smartapi.smartstream.models.Quote; +import com.angelbroking.smartapi.smartstream.models.SnapQuote; +import com.angelbroking.smartapi.smartstream.models.TokenID; + +public class ByteUtils { + + private static final int CHAR_ARRAY_SIZE = 25; + + private ByteUtils() { + + } + + public static LTP mapToLTP(ByteBuffer packet) { + LTP pojo = new LTP(); + pojo.setToken(getTokenID(packet)); + pojo.setSequenceNumber(packet.getLong(27)); + pojo.setExchangeFeedTimeEpochMillis(packet.getLong(35)); + pojo.setLastTradedPrice(packet.getLong(43)); + return pojo; + } + + public static Quote mapToQuote(ByteBuffer packet) { + Quote pojo = new Quote(); + pojo.setToken(getTokenID(packet)); + pojo.setSequenceNumber(packet.getLong(27)); + pojo.setExchangeFeedTimeEpochMillis(packet.getLong(35)); + pojo.setLastTradedPrice(packet.getLong(43)); + pojo.setLastTradedQty(packet.getLong(51)); + pojo.setAvgTradedPrice(packet.getLong(59)); + pojo.setVolumeTradedToday(packet.getLong(67)); + pojo.setTotalBuyQty(packet.getLong(75)); + pojo.setTotalSellQty(packet.getLong(83)); + pojo.setOpenPrice(packet.getLong(91)); + pojo.setHighPrice(packet.getLong(99)); + pojo.setLowPrice(packet.getLong(107)); + pojo.setClosePrice(packet.getLong(115)); + return pojo; + } + + public static SnapQuote mapToSnapQuote(ByteBuffer packet) { + SnapQuote pojo = new SnapQuote(); + pojo.setToken(getTokenID(packet)); + pojo.setSequenceNumber(packet.getLong(27)); + pojo.setExchangeFeedTimeEpochMillis(packet.getLong(35)); + pojo.setLastTradedPrice(packet.getLong(43)); + return pojo; + } + + public static TokenID getTokenID(ByteBuffer byteBuffer) { + byte[] token = new byte[CHAR_ARRAY_SIZE]; +// byteBuffer.get(token, 2, CHAR_ARRAY_SIZE); + for(int i=0; i getTokens(){ + // find out the required token from https://margincalculator.angelbroking.com/OpenAPI_File/files/OpenAPIScripMaster.json + Set tokenSet = new HashSet<>(); +// tokenSet.add(new TokenID(ExchangeType.NSE_CM, "26009")); // NIFTY BANK + tokenSet.add(new TokenID(ExchangeType.NSE_CM, "1594")); // NSE Infosys +// tokenSet.add(new TokenID(ExchangeType.NSE_CM, "19000")); + tokenSet.add(new TokenID(ExchangeType.NCX_FO, "GUARGUM5")); // GUAREX (NCDEX) +// tokenSet.add(new TokenID(ExchangeType.NSE_FO, "57919")); + tokenSet.add(new TokenID(ExchangeType.MCX_FO, "239484")); + return tokenSet; + } + + @Test + void testTokenID() { + TokenID t1 = new TokenID(ExchangeType.NSE_CM, "1594"); + TokenID t2 = new TokenID(ExchangeType.NSE_CM, "4717"); + TokenID t3 = new TokenID(ExchangeType.NSE_CM, "1594"); + TokenID t4 = new TokenID(ExchangeType.NCX_FO, "GUAREX31MAR2022"); + + assertNotEquals(t1, t2); + assertEquals(t1, t3); + + } +}