diff --git a/.gitignore b/.gitignore index 14570b8..fdea67d 100644 --- a/.gitignore +++ b/.gitignore @@ -56,7 +56,6 @@ local.properties nbproject/private/ build/ nbbuild/ -dist/ nbdist/ nbactions.xml nb-configuration.xml diff --git a/README.md b/README.md index 40a85a3..6f17e7d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,468 @@ -# 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. +# SmartAPI 2.1.0 Java client +The official Java client for communicating with [SmartAPI Connect API](https://smartapi.angelbroking.com). + +SmartAPI 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 portfolios, stream live market data (WebSockets), and more, with the simple HTTP API collection. + +## Documentation +- [SmartAPI - HTTP API documentation] (https://smartapi.angelbroking.com/docs) +- [Java library documentation](https://smartapi.angelbroking.com/docs) + +## Usage +- [Download SmartAPI jar file](https://github.com/angel-one/smartapi-java/blob/main/dist/) and include it in your build path. + +- Include com.angelbroking.smartapi into build path from maven. Use version 2.1.0 + +## API usage +```java + // Initialize SamartAPI using Client code, Password, and TOTP. + 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(, , ); + 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 { + // Modify order request will return the order model which will contain order_id. + + 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 order */ + public void cancelOrder(SmartConnect smartConnect) throws SmartAPIException, IOException { + // Cancel order will return the order model which will have orderId. + String orderId = "201216000755110"; + Order order = smartConnect.cancelOrder(orderId, 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 the 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 Margin in trading account*/ + 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); + } + + + /** Search Scrip Data */ + public void getSearchScrip(SmartConnect smartConnect) throws SmartAPIException{ + JSONObject payload = new JSONObject(); + payload.put("exchange", "MCX"); + payload.put("searchscrip", "Crude"); + String response = smartConnect.getSearchScrip(payload); + } + + /** Logout user. */ + + /** Market Data FULL*/ + public void getMarketData(SmartConnect smartConnect) { + + JSONObject payload = new JSONObject(); + payload.put("mode", "FULL"); + JSONObject exchangeTokens = new JSONObject(); + JSONArray nseTokens = new JSONArray(); + nseTokens.put("3045"); + exchangeTokens.put("NSE", nseTokens); + payload.put("exchangeTokens", exchangeTokens); + JSONObject response = smartConnect.marketData(payload); + + } + + /** Market Data OHLC*/ + public void getMarketData(SmartConnect smartConnect) { + + JSONObject payload = new JSONObject(); + payload.put("mode", "OHLC"); + JSONObject exchangeTokens = new JSONObject(); + JSONArray nseTokens = new JSONArray(); + nseTokens.put("3045"); + exchangeTokens.put("NSE", nseTokens); + payload.put("exchangeTokens", exchangeTokens); + JSONObject response = smartConnect.marketData(payload); + + } + + /** Market Data LTP*/ + public void getMarketData(SmartConnect smartConnect) { + + JSONObject payload = new JSONObject(); + payload.put("mode", "LTP"); + JSONObject exchangeTokens = new JSONObject(); + JSONArray nseTokens = new JSONArray(); + nseTokens.put("3045"); + exchangeTokens.put("NSE", nseTokens); + payload.put("exchangeTokens", exchangeTokens); + JSONObject response = smartConnect.marketData(payload); + + } + +/** Logout user. */ + public void logout(SmartConnect smartConnect) throws SmartAPIException, IOException { + /** Logout user and kill the session. */ + JSONObject jsonObject = smartConnect.logout(); + } + +``` +For more details, take a look at Examples.java in the 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 SmartAPI 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 the 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 SmartAPI 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 the SmartAPI ticker, close websocket connection. + // smartWebsocket.disconnect(); + + } + +``` +For more details, take a look at Examples.java in the sample directory. diff --git a/dist/smartapi-java-2.0.0.jar b/dist/smartapi-java-2.0.0.jar new file mode 100644 index 0000000..51339e8 Binary files /dev/null and b/dist/smartapi-java-2.0.0.jar differ diff --git a/dist/smartapi-java-2.1.0.jar b/dist/smartapi-java-2.1.0.jar new file mode 100644 index 0000000..7b109d8 Binary files /dev/null and b/dist/smartapi-java-2.1.0.jar differ diff --git a/pom.xml b/pom.xml index e19bcb3..5edfa21 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.angelbroking.smartapi smartapi-java - 1.0.0 + 2.1.0 jar smartapi-java @@ -20,71 +20,216 @@ + 1.8 + 3.8.1 + 3.5.1 UTF-8 + 5.8.1 + 5.5.0 + 3.24.2 + 1.16.0 + 2.4.0 + 2.14 + 20230618 + 4.11.0 + 3.6.0 + 4.11.0 + 1.9.10 + 2.10.1 + 2.0.9 + 1.4.11 + 8.16.0 + 1.18.30 + 4.12 + 5.5.0 + 4.11.0 + 1.5.0 + 3.0.1 + 1.7.32 + 1.2.6 + + + + org.projectlombok + lombok + ${lombok.version} + + + com.spotify + docker-client + ${docker-client.version} + + + commons-codec + commons-codec + ${commons-codec.version} + + + net.sf.supercsv + super-csv + ${super-csv.version} + + + com.neovisionaries + nv-websocket-client + ${nv-websocket-client.version} + + + + org.json + json + ${json.version} + + + com.squareup.okhttp3 + okhttp + ${okhttp.version} + + + com.squareup.okio + okio + ${okio.version} + + + com.squareup.okhttp3 + logging-interceptor + ${logging-interceptor.version} + + + org.jetbrains.kotlin + kotlin-stdlib + ${kotlin-stdlib.version} + + + com.google.code.gson + gson + ${gson.version} + + + + + org.mockito + mockito-core + ${mockito.version} + test + + + + + org.assertj + assertj-core + ${assertj.version} + test + + + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + + + ch.qos.logback + logback-classic + ${logback-classic.version} + + + + junit + junit + ${junit.version} + test + + + + org.mockito + mockito-junit-jupiter + ${mockito-junit-jupiter.version} + test + + + com.squareup.okhttp3 + mockwebserver + ${okhttp.mockwebserver.version} + test + + + com.warrenstrange + googleauth + ${google-auth.version} + + + com.github.tomakehurst + wiremock + ${wiremock.version} + + + org.slf4j + slf4j-api + ${slf4j-api.version} + + + ch.qos.logback + logback-classic + ${logback-classic.version} + + + + + - - + + org.apache.maven.plugins maven-compiler-plugin + ${maven-compiler-plugin-version} - 1.8 - 1.8 + ${java-version} + ${java-version} + + + org.apache.maven.plugins + maven-shade-plugin + ${maven-shade-plugin-version} + + + + shade + + + true + + + com.angelbroking.smartapi.Main + + true + + + + + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + - - - commons-codec - commons-codec - 1.11 - - - net.sf.supercsv - super-csv - 2.4.0 - - - - com.neovisionaries - nv-websocket-client - 2.10 - - - - org.json - json - 20171018 - - - com.squareup.okhttp3 - okhttp - 4.4.0 - - - com.squareup.okio - okio - 2.4.3 - - - com.squareup.okhttp3 - logging-interceptor - 4.4.0 - - - org.jetbrains.kotlin - kotlin-stdlib - 1.3.70 - - - com.google.code.gson - gson - 2.6.2 - - - diff --git a/src/main/java/com/angelbroking/smartapi/Routes.java b/src/main/java/com/angelbroking/smartapi/Routes.java index 59c4a08..352fa11 100644 --- a/src/main/java/com/angelbroking/smartapi/Routes.java +++ b/src/main/java/com/angelbroking/smartapi/Routes.java @@ -14,8 +14,9 @@ public class Routes { public Map 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 _loginUrl = _rootUrl+"/rest/auth/angelbroking/user/v1/loginByPassword"; private static String _wsuri = "wss://wsfeeds.angelbroking.com/NestHtml5Mobile/socket/stream"; + private static String _smartStreamWSURI = "wss://smartapisocket.angelone.in/smart-stream"; private static String _swsuri = "wss://smartapisocket.angelbroking.com/websocket"; // Initialize all routes, @@ -43,7 +44,8 @@ public Routes() { put("api.gtt.details", "/rest/secure/angelbroking/gtt/v1/ruleDetails"); put("api.gtt.list", "/rest/secure/angelbroking/gtt/v1/ruleList"); put("api.candle.data", "/rest/secure/angelbroking/historical/v1/getCandleData"); - + put("api.search.script.data", "/rest/secure/angelbroking/order/v1/searchScrip"); + put("api.market.data", "/rest/secure/angelbroking/market/v1/quote"); } }; } @@ -63,4 +65,8 @@ public String getWsuri() { public String getSWsuri() { return _swsuri; } + + public String getSmartStreamWSURI() { + return _smartStreamWSURI; + } } diff --git a/src/main/java/com/angelbroking/smartapi/SmartConnect.java b/src/main/java/com/angelbroking/smartapi/SmartConnect.java index d1b3003..77f1de3 100644 --- a/src/main/java/com/angelbroking/smartapi/SmartConnect.java +++ b/src/main/java/com/angelbroking/smartapi/SmartConnect.java @@ -1,14 +1,5 @@ package com.angelbroking.smartapi; -import java.io.IOException; -import java.net.Proxy; -import java.util.List; - -import org.apache.commons.codec.digest.DigestUtils; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import com.angelbroking.smartapi.http.SessionExpiryHook; import com.angelbroking.smartapi.http.SmartAPIRequestHandler; import com.angelbroking.smartapi.http.exceptions.SmartAPIException; @@ -18,7 +9,26 @@ import com.angelbroking.smartapi.models.OrderParams; import com.angelbroking.smartapi.models.TokenSet; import com.angelbroking.smartapi.models.User; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + + + +import java.io.IOException; +import java.net.Proxy; +import java.util.List; + +import static com.angelbroking.smartapi.utils.Constants.IO_EXCEPTION_ERROR_MSG; +import static com.angelbroking.smartapi.utils.Constants.IO_EXCEPTION_OCCURRED; +import static com.angelbroking.smartapi.utils.Constants.JSON_EXCEPTION_ERROR_MSG; +import static com.angelbroking.smartapi.utils.Constants.JSON_EXCEPTION_OCCURRED; +import static com.angelbroking.smartapi.utils.Constants.SMART_API_EXCEPTION_ERROR_MSG; +import static com.angelbroking.smartapi.utils.Constants.SMART_API_EXCEPTION_OCCURRED; +@Slf4j public class SmartConnect { public static SessionExpiryHook sessionExpiryHook = null; public static boolean ENABLE_LOGGING = false; @@ -50,7 +60,7 @@ public void setApiKey(String apiKey) { /** * Registers callback for session error. - * + * * @param hook can be set to get callback when session is expired. */ public void setSessionExpiryHook(SessionExpiryHook hook) { @@ -59,7 +69,7 @@ public void setSessionExpiryHook(SessionExpiryHook hook) { /** * Returns apiKey of the App. - * + * * @return String apiKey is returned. * @throws NullPointerException if _apiKey is not found. */ @@ -72,7 +82,7 @@ public String getApiKey() throws NullPointerException { /** * Returns accessToken. - * + * * @return String access_token is returned. * @throws NullPointerException if accessToken is null. */ @@ -85,7 +95,7 @@ public String getAccessToken() throws NullPointerException { /** * Returns userId. - * + * * @return String userId is returned. * @throws NullPointerException if userId is null. */ @@ -99,7 +109,7 @@ public String getUserId() throws NullPointerException { /** * Set userId. - * + * * @param id is user_id. */ public void setUserId(String id) { @@ -108,7 +118,7 @@ public void setUserId(String id) { /** * Returns publicToken. - * + * * @throws NullPointerException if publicToken is null. * @return String public token is returned. */ @@ -122,7 +132,7 @@ public String getPublicToken() throws NullPointerException { /** * Set the accessToken received after a successful authentication. - * + * * @param accessToken is the access token received after sending request token * and api secret. */ @@ -132,7 +142,7 @@ public void setAccessToken(String accessToken) { /** * Set publicToken. - * + * * @param publicToken is the public token received after sending request token * and api secret. */ @@ -142,7 +152,7 @@ public void setRefreshToken(String refreshToken) { /** * Retrieves login url - * + * * @return String loginUrl is returned. */ public String getLoginURL() throws NullPointerException { @@ -153,13 +163,13 @@ public String getLoginURL() throws NullPointerException { /** * Do the token exchange with the `request_token` obtained after the login flow, * and retrieve the `access_token` required for all subsequent requests. - * + * * @param requestToken received from login process. * @param apiSecret which is unique for each aap. * @return User is the user model which contains user and session details. - * + * */ - public User generateSession(String clientCode, String password) { + public User generateSession(String clientCode, String password, String totp) { try { smartAPIRequestHandler = new SmartAPIRequestHandler(proxy); @@ -167,6 +177,7 @@ public User generateSession(String clientCode, String password) { JSONObject params = new JSONObject(); params.put("clientcode", clientCode); params.put("password", password); + params.put("totp", totp); JSONObject loginResultObject = smartAPIRequestHandler.postRequest(this.apiKey, routes.getLoginUrl(), params); @@ -190,11 +201,11 @@ public User generateSession(String clientCode, String password) { /** * Get a new access token using refresh token. - * + * * @param refreshToken is the refresh token obtained after generateSession. * @param apiSecret is unique for each app. * @return TokenSet contains user id, refresh token, api secret. - * + * */ public TokenSet renewAccessToken(String accessToken, String refreshToken) { try { @@ -224,7 +235,7 @@ public TokenSet renewAccessToken(String accessToken, String refreshToken) { /** * Hex encodes sha256 output for android support. - * + * * @return Hex encoded String. * @param str is the String that has to be encrypted. */ @@ -238,9 +249,9 @@ public String sha256Hex(String str) { /** * Get the profile details of the use. - * + * * @return Profile is a POJO which contains profile related data. - * + * */ public User getProfile() { try { @@ -255,12 +266,12 @@ public User getProfile() { /** * Places an order. - * + * * @param orderParams is Order params. * @param variety variety="regular". Order variety can be bo, co, amo, * regular. * @return Order contains only orderId. - * + * */ public Order placeOrder(OrderParams orderParams, String variety) { @@ -317,7 +328,7 @@ public Order placeOrder(OrderParams orderParams, String variety) { * regular. * @param orderId order id of the order being modified. * @return Order object contains only orderId. - * + * */ public Order modifyOrder(String orderId, OrderParams orderParams, String variety) { try { @@ -357,12 +368,12 @@ public Order modifyOrder(String orderId, OrderParams orderParams, String variety /** * Cancels an order. - * + * * @param orderId order id of the order to be cancelled. * @param variety [variety="regular"]. Order variety can be bo, co, amo, * regular. * @return Order object contains only orderId. - * + * */ public Order cancelOrder(String orderId, String variety) { try { @@ -383,11 +394,11 @@ public Order cancelOrder(String orderId, String variety) { /** * Returns list of different stages an order has gone through. - * + * * @return List of multiple stages an order has gone through in the system. * @throws SmartAPIException is thrown for all Smart API trade related errors. * @param orderId is the order id which is obtained from orderbook. - * + * */ @SuppressWarnings({}) public JSONObject getOrderHistory(String clientId) { @@ -407,9 +418,9 @@ public JSONObject getOrderHistory(String clientId) { * Retrieves last price. User can either pass exchange with tradingsymbol or * instrument token only. For example {NSE:NIFTY 50, BSE:SENSEX} or {256265, * 265}. - * + * * @return Map of String and LTPQuote. - * + * */ public JSONObject getLTP(String exchange, String tradingSymbol, String symboltoken) { try { @@ -430,7 +441,7 @@ public JSONObject getLTP(String exchange, String tradingSymbol, String symboltok /** * Retrieves list of trades executed. - * + * * @return List of trades. */ public JSONObject getTrades() { @@ -446,7 +457,7 @@ public JSONObject getTrades() { /** * Retrieves RMS. - * + * * @return Object of RMS. * @throws SmartAPIException is thrown for all Smart API trade related errors. * @throws JSONException is thrown when there is exception while parsing @@ -466,9 +477,9 @@ public JSONObject getRMS() { /** * Retrieves Holding. - * + * * @return Object of Holding. - * + * */ public JSONObject getHolding() { try { @@ -483,9 +494,9 @@ public JSONObject getHolding() { /** * Retrieves position. - * + * * @return Object of position. - * + * */ public JSONObject getPosition() { try { @@ -500,7 +511,7 @@ public JSONObject getPosition() { /** * Retrieves conversion. - * + * * @return Object of conversion. * @throws SmartAPIException is thrown for all Smart API trade related errors. * @throws JSONException is thrown when there is exception while parsing @@ -520,10 +531,10 @@ public JSONObject convertPosition(JSONObject params) { /** * Create a Gtt Rule. - * + * * @param gttParams is gtt Params. * @return Gtt contains only orderId. - * + * */ public Gtt gttCreateRule(GttParams gttParams) { @@ -567,10 +578,10 @@ public Gtt gttCreateRule(GttParams gttParams) { /** * Modify a Gtt Rule. - * + * * @param gttParams is gtt Params. * @return Gtt contains only orderId. - * + * */ public Gtt gttModifyRule(Integer id, GttParams gttParams) { @@ -610,7 +621,7 @@ public Gtt gttModifyRule(Integer id, GttParams gttParams) { /** * Cancel a Gtt Rule. - * + * * @param gttParams is gtt Params. * @return Gtt contains only orderId. */ @@ -636,7 +647,7 @@ public Gtt gttCancelRule(Integer id, String symboltoken, String exchange) { /** * Get Gtt Rule Details. - * + * * @param id is gtt rule id. * @return returns the details of gtt rule. */ @@ -661,7 +672,7 @@ public JSONObject gttRuleDetails(Integer id) { /** * Get Gtt Rule Details. - * + * * @param status is list of gtt rule status. * @param page is no of page * @param count is the count of gtt rules @@ -687,7 +698,7 @@ public JSONArray gttRuleList(List status, Integer page, Integer count) { /** * Get Historic Data. - * + * * @param params is historic data params. * @return returns the details of historic data. */ @@ -703,11 +714,59 @@ public String candleData(JSONObject params) { } } + /** + + * Get Search Script Data. + * + * @param payload is Search Script params. + * @return returns the details of Search Script data. + */ + + public String getSearchScrip(JSONObject payload) throws SmartAPIException, IOException { + try { + String url = routes.get("api.search.script.data"); + return smartAPIRequestHandler.postRequestJSONObject(this.apiKey, url, payload, accessToken); + }catch (IOException ex) { + log.error("{} while generating session {}", IO_EXCEPTION_OCCURRED, ex.getMessage()); + throw new IOException(String.format("%s in generating Session %s", IO_EXCEPTION_ERROR_MSG, ex.getMessage())); + } catch (JSONException ex) { + log.error("{} while generating session {}", JSON_EXCEPTION_OCCURRED, ex.getMessage()); + throw new JSONException(String.format("%s in generating Session %s", JSON_EXCEPTION_ERROR_MSG, ex.getMessage())); + } catch (SmartAPIException ex) { + log.error("{} while generating session {}", SMART_API_EXCEPTION_OCCURRED, ex.toString()); + throw new SmartAPIException(String.format("%s in generating Session %s", SMART_API_EXCEPTION_ERROR_MSG, ex)); + } + } + +/** + * Get Market Data. + * + * @param params is market data params. + * @return returns the details of market data. + */ + public JSONObject marketData(JSONObject params) throws SmartAPIException, IOException { + try{ + String url = routes.get("api.market.data"); + JSONObject response = smartAPIRequestHandler.postRequest(this.apiKey, url, params, accessToken); + return response.getJSONObject("data"); + }catch (SmartAPIException ex) { + log.error("{} while placing order {}", SMART_API_EXCEPTION_OCCURRED, ex.toString()); + throw new SmartAPIException(String.format("%s in placing order %s", SMART_API_EXCEPTION_ERROR_MSG, ex)); + } catch (IOException ex) { + log.error("{} while placing order {}", IO_EXCEPTION_OCCURRED, ex.getMessage()); + throw new IOException(String.format("%s in placing order %s", IO_EXCEPTION_ERROR_MSG, ex.getMessage())); + } catch (JSONException ex) { + log.error("{} while placing order {}", JSON_EXCEPTION_OCCURRED, ex.getMessage()); + throw new JSONException(String.format("%s in placing order %s", JSON_EXCEPTION_ERROR_MSG, ex.getMessage())); + + } + } + /** * Logs out user by invalidating the access token. - * + * * @return JSONObject which contains status - * + * */ public JSONObject logout() { @@ -722,5 +781,6 @@ public JSONObject logout() { return null; } } - + } + diff --git a/src/main/java/com/angelbroking/smartapi/http/SmartAPIRequestHandler.java b/src/main/java/com/angelbroking/smartapi/http/SmartAPIRequestHandler.java index f32b3a3..a53229a 100644 --- a/src/main/java/com/angelbroking/smartapi/http/SmartAPIRequestHandler.java +++ b/src/main/java/com/angelbroking/smartapi/http/SmartAPIRequestHandler.java @@ -1,23 +1,7 @@ package com.angelbroking.smartapi.http; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.Proxy; -import java.net.URL; -import java.util.Enumeration; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import com.angelbroking.smartapi.SmartConnect; import com.angelbroking.smartapi.http.exceptions.SmartAPIException; - import okhttp3.FormBody; import okhttp3.HttpUrl; import okhttp3.MediaType; @@ -26,6 +10,20 @@ import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.logging.HttpLoggingInterceptor; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.Proxy; +import java.net.URL; +import java.util.Enumeration; +import java.util.Map; +import java.util.concurrent.TimeUnit; /** * Request handler for all Http requests @@ -374,6 +372,27 @@ public Request createJsonPostRequest(String url, JSONArray jsonArray, String api return request; } + /** + * Makes a POST request. + * + * @return JSONObject which is received by Smart API Trade. + * @param url is the endpoint to which request has to be sent. + * @param apiKey is the api key of the Smart API Connect app. + * @param accessToken is the access token obtained after successful login + * process. + * @param params is the map of params which has to be sent in the body. + * @throws IOException is thrown when there is a connection related error. + * @throws SmartAPIException is thrown for all Smart API Trade related errors. + * @throws JSONException is thrown for parsing errors. + */ + public String postRequestJSONObject(String apiKey, String url, JSONObject params, String accessToken) + throws IOException, SmartAPIException, JSONException { + Request request = createPostRequest(apiKey, url, params, accessToken); + Response response = client.newCall(request).execute(); + String body = response.body().string(); + return new SmartAPIResponseHandler().handler(response, body); + } + /** * Creates a PUT request. * diff --git a/src/main/java/com/angelbroking/smartapi/http/SmartAPIResponseHandler.java b/src/main/java/com/angelbroking/smartapi/http/SmartAPIResponseHandler.java index 3beeaba..1f532a2 100644 --- a/src/main/java/com/angelbroking/smartapi/http/SmartAPIResponseHandler.java +++ b/src/main/java/com/angelbroking/smartapi/http/SmartAPIResponseHandler.java @@ -1,7 +1,14 @@ package com.angelbroking.smartapi.http; import java.io.IOException; - +import java.util.List; + +import com.angelbroking.smartapi.http.exceptions.ApiKeyException; +import com.angelbroking.smartapi.models.SearchScripResponseDTO; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -17,22 +24,26 @@ import okhttp3.Response; +import static com.angelbroking.smartapi.utils.Constants.APIKEY_EXCEPTION_MESSAGE; +import static com.angelbroking.smartapi.utils.Constants.TOKEN_EXCEPTION_MESSAGE; + /** * Response handler for handling all the responses. */ +@Slf4j public class SmartAPIResponseHandler { public JSONObject handle(Response response, String body) throws IOException, SmartAPIException, JSONException { System.out.println("***************************"); if (response.header("Content-Type").contains("json")) { JSONObject jsonObject = new JSONObject(body); - + // if (jsonObject.optString("data") == null || jsonObject.optString("data") == "") { - if (!jsonObject.has("status") || jsonObject.has("success")) { + if (!jsonObject.has("status") || jsonObject.has("success")) { if (jsonObject.has("errorcode")) { throw dealWithException(jsonObject, jsonObject.getString("errorcode")); } else if (jsonObject.has("errorCode")) { - + throw dealWithException(jsonObject, jsonObject.getString("errorCode")); } } @@ -58,8 +69,9 @@ private SmartAPIException dealWithException(JSONObject jsonObject, String code) return new TokenException(jsonObject.getString("message"), code); case "AG8001": - case "AG8002": - return new DataException(jsonObject.getString("message"), code); + return new TokenException(TOKEN_EXCEPTION_MESSAGE, code); + case "AG8002": + return new DataException(jsonObject.getString("message"), code); case "AB1004": case "AB2000": @@ -87,10 +99,63 @@ private SmartAPIException dealWithException(JSONObject jsonObject, String code) case "AB1001": case "AB1011": return new PermissionException(jsonObject.getString("message"), code); - + case "AG8004": + return new ApiKeyException(APIKEY_EXCEPTION_MESSAGE, code); default: return new SmartAPIException(jsonObject.getString("data not found")); } } + public String handler(Response response, String body) throws SmartAPIException, JSONException, IOException { + if (response.code() == 200) { + return handleResponse(response,body); + } else if (response.code() == 400){ + log.error("Bad request. Please provide valid input"); + return "Bad request. Please provide valid input"; + }else { + log.error("Response or response body is null."); + throw new IllegalArgumentException("Response or response body is null."); + } + } + + private String handleResponse(Response response, String body) throws SmartAPIException, IOException { + try { + JSONObject responseBodyJson = new JSONObject(body); + if(responseBodyJson.getBoolean("status")) { + JSONArray dataArray = responseBodyJson.optJSONArray("data"); + if (dataArray != null && dataArray.length() > 0) { + List stockDTOList = parseStockDTOList(dataArray); + + StringBuilder result = new StringBuilder(); + result.append("Search successful. Found ").append(stockDTOList.size()).append(" trading symbols for the given query:\n"); + + int index = 1; + for (SearchScripResponseDTO stockDTO : stockDTOList) { + result.append(index).append(". exchange: ").append(stockDTO.getExchange()).append(", tradingsymbol: ").append(stockDTO.getTradingSymbol()).append(", symboltoken: ").append(stockDTO.getSymbolToken()).append("\n"); + index++; + } + return result.toString(); + } else { + return "Search successful. No matching trading symbols found for the given query."; + } + }else { + return String.valueOf(handle(response,body)); + } + + } catch (JSONException e) { + log.error("Error parsing response body as JSON.", e.getMessage()); + throw new SmartAPIException("Error parsing response body as JSON."); + } + } + + private List parseStockDTOList(JSONArray dataArray) throws JSONException, SmartAPIException { + ObjectMapper objectMapper = new ObjectMapper(); + try { + return objectMapper.readValue(dataArray.toString(), new TypeReference>() { + }); + } catch (IOException e) { + log.error("Error parsing JSON data array.", e); + throw new SmartAPIException("Error parsing JSON data array."); + } + } } diff --git a/src/main/java/com/angelbroking/smartapi/http/exceptions/ApiKeyException.java b/src/main/java/com/angelbroking/smartapi/http/exceptions/ApiKeyException.java new file mode 100644 index 0000000..0a7c38f --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/http/exceptions/ApiKeyException.java @@ -0,0 +1,14 @@ +package com.angelbroking.smartapi.http.exceptions; + +/** + * Exception raised when invalid API Key is provided for Smart API trade. + */ + +public class ApiKeyException extends SmartAPIException { + + // initialize 2fa exception and call constructor of Base Exception + public ApiKeyException(String message, String code){ + super(message, code); + } +} + diff --git a/src/main/java/com/angelbroking/smartapi/models/SearchScripResponseDTO.java b/src/main/java/com/angelbroking/smartapi/models/SearchScripResponseDTO.java new file mode 100644 index 0000000..6f5ec17 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/models/SearchScripResponseDTO.java @@ -0,0 +1,18 @@ +package com.angelbroking.smartapi.models; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class SearchScripResponseDTO { + + @JsonProperty("tradingsymbol") + private String tradingSymbol; + + @JsonProperty("exchange") + private String exchange; + + @JsonProperty("symboltoken") + private String symbolToken; + +} diff --git a/src/main/java/com/angelbroking/smartapi/sample/Examples.java b/src/main/java/com/angelbroking/smartapi/sample/Examples.java index 01cfb43..1c60bd5 100644 --- a/src/main/java/com/angelbroking/smartapi/sample/Examples.java +++ b/src/main/java/com/angelbroking/smartapi/sample/Examples.java @@ -1,12 +1,5 @@ package com.angelbroking.smartapi.sample; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -import org.json.JSONArray; -import org.json.JSONObject; - import com.angelbroking.smartapi.SmartConnect; import com.angelbroking.smartapi.http.exceptions.SmartAPIException; import com.angelbroking.smartapi.models.Gtt; @@ -23,6 +16,12 @@ import com.angelbroking.smartapi.ticker.OnTicks; import com.angelbroking.smartapi.ticker.SmartAPITicker; import com.angelbroking.smartapi.utils.Constants; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; @SuppressWarnings("unused") public class Examples { @@ -35,9 +34,9 @@ public void getProfile(SmartConnect smartConnect) throws IOException, SmartAPIEx /* VARIETY */ /* - * VARIETY_NORMAL: Normal Order (Regular) + * VARIETY_NORMAL: Normal Order (Regular) * VARIETY_AMO: After Market Order - * VARIETY_STOPLOSS: Stop loss order + * VARIETY_STOPLOSS: Stop loss order * VARIETY_ROBO: ROBO (Bracket) Order */ /* TRANSACTION TYPE */ @@ -47,7 +46,7 @@ public void getProfile(SmartConnect smartConnect) throws IOException, SmartAPIEx /* ORDER TYPE */ /* - * ORDER_TYPE_MARKET: Market Order(MKT) + * 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) @@ -55,27 +54,27 @@ public void getProfile(SmartConnect smartConnect) throws IOException, SmartAPIEx /* PRODUCT TYPE */ /* - * PRODUCT_DELIVERY: Cash & Carry for equity (CNC) + * PRODUCT_DELIVERY: Cash & Carry for equity (CNC) * PRODUCT_CARRYFORWARD: Normal - * for futures and options (NRML) + * for futures and options (NRML) * PRODUCT_MARGIN: Margin Delivery - * PRODUCT_INTRADAY: Margin Intraday Squareoff (MIS) + * PRODUCT_INTRADAY: Margin Intraday Squareoff (MIS) * PRODUCT_BO: Bracket Order * (Only for ROBO) */ /* DURATION */ /* - * DURATION_DAY: Valid for a day + * 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_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 */ @@ -273,6 +272,36 @@ public void getCandleData(SmartConnect smartConnect) throws SmartAPIException, I String response = smartConnect.candleData(requestObejct); } + + /** Search Scrip Data */ + public void getSearchScrip(SmartConnect smartConnect) throws SmartAPIException, IOException { + JSONObject payload = new JSONObject(); + payload.put("exchange", "MCX"); + payload.put("searchscrip", "Crude"); + smartConnect.getSearchScrip(payload); + } + + /** + * Market Data + * To Retrieve Market Data with different modes use. + * e.g: + * payload.put("mode", "FULL"); + * payload.put("mode", "LTP"); + * payload.put("mode", "OHLC"); + */ + public void getMarketData(SmartConnect smartConnect) throws SmartAPIException, IOException { + JSONObject payload = new JSONObject(); + payload.put("mode", "FULL"); // You can change the mode as needed + JSONObject exchangeTokens = new JSONObject(); + JSONArray nseTokens = new JSONArray(); + nseTokens.put("3045"); + exchangeTokens.put("NSE", nseTokens); + payload.put("exchangeTokens", exchangeTokens); + JSONObject response = smartConnect.marketData(payload); + } + + + public void tickerUsage(String clientId, String feedToken, String strWatchListScript, String task) throws SmartAPIException { diff --git a/src/main/java/com/angelbroking/smartapi/sample/LoginWithTOTPSample.java b/src/main/java/com/angelbroking/smartapi/sample/LoginWithTOTPSample.java new file mode 100644 index 0000000..910bcc5 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/sample/LoginWithTOTPSample.java @@ -0,0 +1,18 @@ +package com.angelbroking.smartapi.sample; + +import com.angelbroking.smartapi.SmartConnect; +import com.angelbroking.smartapi.models.User; + +public class LoginWithTOTPSample { + + public static void main(String[] args) { + String clientID = System.getProperty("clientID"); + String clientPass = System.getProperty("clientPass"); + String apiKey = System.getProperty("apiKey"); + String totp = System.getProperty("totp"); + SmartConnect smartConnect = new SmartConnect(apiKey); + User user = smartConnect.generateSession(clientID, clientPass, totp); + String feedToken = user.getFeedToken(); + System.out.println(feedToken); + } +} diff --git a/src/main/java/com/angelbroking/smartapi/sample/Test.java b/src/main/java/com/angelbroking/smartapi/sample/Test.java index 935a4ed..6ef34e0 100644 --- a/src/main/java/com/angelbroking/smartapi/sample/Test.java +++ b/src/main/java/com/angelbroking/smartapi/sample/Test.java @@ -19,14 +19,14 @@ public static void main(String[] args) throws SmartAPIException { /* * Set session expiry callback. smartConnect.setSessionExpiryHook(new * SessionExpiryHook() { - * + * * @Override public void sessionExpired() { * System.out.println("session expired"); } }); - * + * * User user = smartConnect.generateSession("", ""); * smartConnect.setAccessToken(user.getAccessToken()); * smartConnect.setUserId(user.getUserId()); - * + * * /* token re-generate */ /* @@ -39,7 +39,7 @@ public static void main(String[] args) throws SmartAPIException { /* System.out.println("getProfile"); */ examples.getProfile(smartConnect); - System.out.println("placeOrder"); + /* System.out.println("placeOrder"); */ examples.placeOrder(smartConnect); /* System.out.println("modifyOrder"); */ @@ -87,12 +87,20 @@ public static void main(String[] args) throws SmartAPIException { /* System.out.println("Historic candle Data"); */ examples.getCandleData(smartConnect); + + /* System.out.println("Search script api"); */ + examples.getSearchScrip(smartConnect); + + /* System.out.println("Market Data"); */ + examples.getMarketData(smartConnect); + + /* System.out.println("logout"); */ examples.logout(smartConnect); /* SmartAPITicker */ String clientId = ""; - User user = smartConnect.generateSession("", ""); + 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"; @@ -102,10 +110,10 @@ public static void main(String[] args) throws SmartAPIException { /* * String jwtToken = user.getAccessToken(); String apiKey = "smartapi_key"; * String actionType = "subscribe"; String feedType = "order_feed"; - * + * * examples.smartWebSocketUsage(clientId, jwtToken, apiKey, actionType, * feedType); - * + * */ } catch (Exception e) { @@ -114,4 +122,4 @@ public static void main(String[] args) throws SmartAPIException { } } -} +} \ No newline at end of file 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..8ce6319 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/ExchangeType.java @@ -0,0 +1,38 @@ +package com.angelbroking.smartapi.smartstream.models; + +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; + +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 static final Map valToExchangeTypeMap = new HashMap<>(); + + private ExchangeType(int val) { + this.val = val; + } + + static { + valToExchangeTypeMap.put(1, NSE_CM); + valToExchangeTypeMap.put(2, NSE_FO); + valToExchangeTypeMap.put(3, BSE_CM); + valToExchangeTypeMap.put(4, BSE_FO); + valToExchangeTypeMap.put(5, MCX_FO); + valToExchangeTypeMap.put(7, NCX_FO); + valToExchangeTypeMap.put(13, CDE_FO); + } + + public int getVal() { + return this.val; + } + + public static ExchangeType findByValue(int val) { + ExchangeType exchange = valToExchangeTypeMap.get(val); + if (exchange == null) { + throw new NoSuchElementException(String.format("No ExchangeType found with value: %s", val)); + } + return exchange; + } +} 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..4399d95 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/LTP.java @@ -0,0 +1,33 @@ +package com.angelbroking.smartapi.smartstream.models; + +import static com.angelbroking.smartapi.utils.Constants.EXCHANGE_FEED_TIME_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.LAST_TRADED_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.SEQUENCE_NUMBER_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.SUBSCRIPTION_MODE; + +import java.nio.ByteBuffer; + +import com.angelbroking.smartapi.utils.ByteUtils; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class LTP { + private byte subscriptionMode; + private ExchangeType exchangeType; + private TokenID token; + private long sequenceNumber; + private long exchangeFeedTimeEpochMillis; + private long lastTradedPrice; + + public LTP(ByteBuffer buffer) { + this.subscriptionMode = buffer.get(SUBSCRIPTION_MODE); + this.token = ByteUtils.getTokenID(buffer); + this.exchangeType = this.token.getExchangeType(); + this.sequenceNumber = buffer.getLong(SEQUENCE_NUMBER_OFFSET); + this.exchangeFeedTimeEpochMillis = buffer.getLong(EXCHANGE_FEED_TIME_OFFSET); + this.lastTradedPrice = buffer.getLong(LAST_TRADED_PRICE_OFFSET); + } +} 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..f04f4d0 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/Quote.java @@ -0,0 +1,61 @@ +package com.angelbroking.smartapi.smartstream.models; + +import static com.angelbroking.smartapi.utils.Constants.AVG_TRADED_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.CLOSE_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.EXCHANGE_FEED_TIME_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.EXCHANGE_TYPE; +import static com.angelbroking.smartapi.utils.Constants.HIGH_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.LAST_TRADED_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.LAST_TRADED_QTY_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.LOW_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.OPEN_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.SEQUENCE_NUMBER_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.SUBSCRIPTION_MODE; +import static com.angelbroking.smartapi.utils.Constants.TOTAL_BUY_QTY_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.TOTAL_SELL_QTY_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.VOLUME_TRADED_TODAY_OFFSET; + +import java.nio.ByteBuffer; + +import com.angelbroking.smartapi.utils.ByteUtils; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class Quote { + private byte subscriptionMode; + private ExchangeType exchangeType; + 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 Quote(ByteBuffer buffer) { + this.subscriptionMode = buffer.get(SUBSCRIPTION_MODE); + this.token = ByteUtils.getTokenID(buffer); + this.exchangeType = this.token.getExchangeType(); + this.sequenceNumber = buffer.getLong(SEQUENCE_NUMBER_OFFSET); + this.exchangeFeedTimeEpochMillis = buffer.getLong(EXCHANGE_FEED_TIME_OFFSET); + this.lastTradedPrice = buffer.getLong(LAST_TRADED_PRICE_OFFSET); + this.lastTradedQty = buffer.getLong(LAST_TRADED_QTY_OFFSET); + this.avgTradedPrice = buffer.getLong(AVG_TRADED_PRICE_OFFSET); + this.volumeTradedToday = buffer.getLong(VOLUME_TRADED_TODAY_OFFSET); + this.totalBuyQty = buffer.getLong(TOTAL_BUY_QTY_OFFSET); + this.totalSellQty = buffer.getLong(TOTAL_SELL_QTY_OFFSET); + this.openPrice = buffer.getLong(OPEN_PRICE_OFFSET); + this.highPrice = buffer.getLong(HIGH_PRICE_OFFSET); + this.lowPrice = buffer.getLong(LOW_PRICE_OFFSET); + this.closePrice = buffer.getLong(CLOSE_PRICE_OFFSET); + } +} 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..0ea783d --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/SmartApiBBSInfo.java @@ -0,0 +1,23 @@ +package com.angelbroking.smartapi.smartstream.models; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class SmartApiBBSInfo { + public static final int BYTES = (2 * Short.BYTES) + (2 * Long.BYTES); + + // siBbBuySellFlag = 1 buy + // siBbBuySellFlag = 0 sell + private short buySellFlag = -1; + private long quantity = -1; + private long price = -1; + private short numberOfOrders = -1; +} \ 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..93ec45f --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/models/SnapQuote.java @@ -0,0 +1,85 @@ +package com.angelbroking.smartapi.smartstream.models; + +import static com.angelbroking.smartapi.utils.Constants.AVG_TRADED_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.CLOSE_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.EXCHANGE_FEED_TIME_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.HIGH_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.LAST_TRADED_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.LAST_TRADED_QTY_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.LAST_TRADED_TIMESTAMP_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.LOWER_CIRCUIT_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.LOW_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.OPEN_INTEREST_CHANGE_PERC_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.OPEN_INTEREST_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.OPEN_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.SEQUENCE_NUMBER_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.SUBSCRIPTION_MODE; +import static com.angelbroking.smartapi.utils.Constants.TOTAL_BUY_QTY_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.TOTAL_SELL_QTY_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.UPPER_CIRCUIT_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.VOLUME_TRADED_TODAY_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.YEARLY_HIGH_PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.YEARLY_LOW_PRICE_OFFSET; + +import java.nio.ByteBuffer; + +import com.angelbroking.smartapi.utils.ByteUtils; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class SnapQuote { + private byte subscriptionMode; + private ExchangeType exchangeType; + 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 SnapQuote(ByteBuffer buffer) { + this.subscriptionMode = buffer.get(SUBSCRIPTION_MODE); + this.token = ByteUtils.getTokenID(buffer); + this.exchangeType = this.token.getExchangeType(); + this.sequenceNumber = buffer.getLong(SEQUENCE_NUMBER_OFFSET); + this.exchangeFeedTimeEpochMillis = buffer.getLong(EXCHANGE_FEED_TIME_OFFSET); + this.lastTradedPrice = buffer.getLong(LAST_TRADED_PRICE_OFFSET); + this.lastTradedQty = buffer.getLong(LAST_TRADED_QTY_OFFSET); + this.avgTradedPrice = buffer.getLong(AVG_TRADED_PRICE_OFFSET); + this.volumeTradedToday = buffer.getLong(VOLUME_TRADED_TODAY_OFFSET); + this.totalBuyQty = buffer.getDouble(TOTAL_BUY_QTY_OFFSET); + this.totalSellQty = buffer.getDouble(TOTAL_SELL_QTY_OFFSET); + this.openPrice = buffer.getLong(OPEN_PRICE_OFFSET); + this.highPrice = buffer.getLong(HIGH_PRICE_OFFSET); + this.lowPrice = buffer.getLong(LOW_PRICE_OFFSET); + this.closePrice = buffer.getLong(CLOSE_PRICE_OFFSET); + this.lastTradedTimestamp = buffer.getLong(LAST_TRADED_TIMESTAMP_OFFSET); + this.openInterest = buffer.getLong(OPEN_INTEREST_OFFSET); + this.openInterestChangePerc = buffer.getDouble(OPEN_INTEREST_CHANGE_PERC_OFFSET); + this.bestFiveBuy = ByteUtils.getBestFiveBuyData(buffer); + this.bestFiveSell = ByteUtils.getBestFiveSellData(buffer); + this.upperCircuit = buffer.getLong(UPPER_CIRCUIT_OFFSET); + this.lowerCircuit = buffer.getLong(LOWER_CIRCUIT_OFFSET); + this.yearlyHighPrice = buffer.getLong(YEARLY_HIGH_PRICE_OFFSET); + this.yearlyLowPrice = buffer.getLong(YEARLY_LOW_PRICE_OFFSET); + } +} 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..a092c46 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/ticker/SmartStreamListener.java @@ -0,0 +1,17 @@ +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 onDisconnected(); + 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..0104bba --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/smartstream/ticker/SmartStreamTicker.java @@ -0,0 +1,340 @@ +package com.angelbroking.smartapi.smartstream.ticker; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.time.LocalDateTime; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; + +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.WebSocketFactory; +import com.neovisionaries.ws.client.WebSocketFrame; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +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 final Routes routes = new Routes(); + private final String wsuri = routes.getSmartStreamWSURI(); + private final SmartStreamListener smartStreamListener; + private WebSocket ws; + private final String clientId; + private final String feedToken; + private EnumMap> tokensByModeMap = new EnumMap<>(SmartStreamSubsMode.class); + private Timer pingTimer; + private LocalDateTime lastPongReceivedTime = LocalDateTime.now(); + + /** + * Initializes the SmartStreamTicker. + * + * @param clientId - the client ID used for authentication + * @param feedToken - the feed token used for authentication + * @param smartStreamListener - the SmartStreamListener for receiving callbacks + * @throws IllegalArgumentException - if the clientId, feedToken, or SmartStreamListener is null or empty + */ + public SmartStreamTicker(String clientId, String feedToken, SmartStreamListener smartStreamListener) { + if (Utils.isEmpty(clientId) || Utils.isEmpty(feedToken) || Utils.validateInputNullCheck(smartStreamListener)) { + 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); + ws.addHeader(FEED_TOKEN_HEADER, feedToken); + ws.addHeader(CLIENT_LIB_HEADER, "JAVA"); + ws.addListener(getWebsocketAdapter()); + } catch (IOException e) { + if (Utils.validateInputNotNullCheck(smartStreamListener)) { + 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(); + startPingTimer(websocket); + } + + @Override + public void onTextMessage(WebSocket websocket, String message) throws Exception { + super.onTextMessage(websocket, message); + } + + @Override + public void onBinaryMessage(WebSocket websocket, byte[] binary) { + SmartStreamSubsMode mode = SmartStreamSubsMode.findByVal(binary[0]); + if (Utils.validateInputNullCheck(mode)) { + StringBuilder sb = new StringBuilder(); + sb.append("Invalid SubsMode="); + sb.append(binary[0]); + sb.append(" in the response binary packet"); + smartStreamListener.onError(getErrorHolder(new SmartAPIException(sb.toString()))); + } + try { + 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; + } + } + } catch (Exception e) { + smartStreamListener.onError(getErrorHolder(e)); + } + } + + @Override + public void onPongFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { + try { + lastPongReceivedTime = LocalDateTime.now(); + 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) { + reconnectAndResubscribe(); + } else { + stopPingTimer(); + smartStreamListener.onDisconnected(); + } + } catch (Exception e) { + SmartStreamError error = new SmartStreamError(); + error.setException(e); + smartStreamListener.onError(error); + } + } + + @Override + public void onCloseFrame(WebSocket websocket, WebSocketFrame frame) throws Exception { + super.onCloseFrame(websocket, frame); + } + }; + } + + private void startPingTimer(final WebSocket websocket) { + + pingTimer = new Timer(); + pingTimer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + try { + LocalDateTime currentTime = LocalDateTime.now(); + if (lastPongReceivedTime.isBefore(currentTime.minusSeconds(20))) { + websocket.disconnect(); + reconnectAndResubscribe(); + } + } catch (Exception e) { + smartStreamListener.onError(getErrorHolder(e)); + } + } + }, 5000, 5000); // run at every 5 second + } + + private void stopPingTimer() { + if (Utils.validateInputNotNullCheck(pingTimer)) { + pingTimer.cancel(); + pingTimer = null; + } + } + + private void reconnectAndResubscribe() throws WebSocketException { + log.info("reconnectAndResubscribe - started"); + init(); + connect(); + // resubscribing the existing tokens as per the mode + tokensByModeMap.forEach((mode,tokens) -> { + subscribe(mode, tokens); + }); + log.info("reconnectAndResubscribe - done"); + } + + /** Disconnects websocket connection. */ + public void disconnect() { + + if (ws != null) { + stopPingTimer(); + 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()) { + tokensByModeMap.put(mode, tokens); + 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()) { + JSONObject wsMWJSONRequest = getApiRequest(SmartStreamAction.UNSUBS, mode, tokens); + ws.sendText(wsMWJSONRequest.toString()); + Set currentlySubscribedTokens = tokensByModeMap.get(mode); + if(currentlySubscribedTokens != null) { + currentlySubscribedTokens.removeAll(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; + } + + 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(); + log.info("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..eab03ac --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/utils/ByteUtils.java @@ -0,0 +1,77 @@ +package com.angelbroking.smartapi.utils; + +import static com.angelbroking.smartapi.utils.Constants.BUY_SELL_FLAG_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.BUY_START_POSITION; +import static com.angelbroking.smartapi.utils.Constants.NUMBER_OF_ORDERS_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.NUM_PACKETS; +import static com.angelbroking.smartapi.utils.Constants.PACKET_SIZE; +import static com.angelbroking.smartapi.utils.Constants.PRICE_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.QUANTITY_OFFSET; +import static com.angelbroking.smartapi.utils.Constants.SELL_START_POSITION; + +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.SmartApiBBSInfo; +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) { + return new LTP(packet); + } + + public static Quote mapToQuote(ByteBuffer packet) { + return new Quote(packet); + } + + public static SnapQuote mapToSnapQuote(ByteBuffer packet) { + return new SnapQuote(packet); + } + + public static TokenID getTokenID(ByteBuffer byteBuffer) { + byte[] token = new byte[CHAR_ARRAY_SIZE]; + for (int i = 0; i < CHAR_ARRAY_SIZE; i++) { + token[i] = byteBuffer.get(2 + i); + } + return new TokenID(ExchangeType.findByValue(byteBuffer.get(1)), new String(token, StandardCharsets.UTF_8)); + } + + public static SmartApiBBSInfo[] getBestFiveBuyData(ByteBuffer buffer) { + SmartApiBBSInfo[] bestFiveBuyData = new SmartApiBBSInfo[NUM_PACKETS]; + + for (int i = 0; i < NUM_PACKETS; i++) { + int offset = BUY_START_POSITION + (i * PACKET_SIZE); + short buySellFlag = buffer.getShort(offset + BUY_SELL_FLAG_OFFSET); + long quantity = buffer.getLong(offset + QUANTITY_OFFSET); + long price = buffer.getLong(offset + PRICE_OFFSET); + short numberOfOrders = buffer.getShort(offset + NUMBER_OF_ORDERS_OFFSET); + bestFiveBuyData[i] = new SmartApiBBSInfo(buySellFlag, quantity, price, numberOfOrders); + } + + return bestFiveBuyData; + } + + public static SmartApiBBSInfo[] getBestFiveSellData(ByteBuffer buffer) { + SmartApiBBSInfo[] bestFiveSellData = new SmartApiBBSInfo[NUM_PACKETS]; + for (int i = 0; i < NUM_PACKETS; i++) { + int offset = SELL_START_POSITION + (i * PACKET_SIZE); + short buySellFlag = buffer.getShort(offset + BUY_SELL_FLAG_OFFSET); + long quantity = buffer.getLong(offset + QUANTITY_OFFSET); + long price = buffer.getLong(offset + PRICE_OFFSET); + short numberOfOrders = buffer.getShort(offset + NUMBER_OF_ORDERS_OFFSET); + bestFiveSellData[i] = new SmartApiBBSInfo(buySellFlag, quantity, price, numberOfOrders); + } + return bestFiveSellData; + } +} diff --git a/src/main/java/com/angelbroking/smartapi/utils/Constants.java b/src/main/java/com/angelbroking/smartapi/utils/Constants.java index b9ce75f..8f9d212 100644 --- a/src/main/java/com/angelbroking/smartapi/utils/Constants.java +++ b/src/main/java/com/angelbroking/smartapi/utils/Constants.java @@ -39,5 +39,54 @@ public class Constants { public static String EXCHANGE_CDS = "CDS"; public static String EXCHANGE_NCDEX = "NCDEX"; public static String EXCHANGE_MCX = "MCX"; + + /** + * LTP QUOTE SNAPQUOTE Constants + */ + + public static final int SEQUENCE_NUMBER_OFFSET = 27; + public static final int EXCHANGE_FEED_TIME_OFFSET = 35; + public static final int LAST_TRADED_PRICE_OFFSET = 43; + public static final int SUBSCRIPTION_MODE = 0; + public static final int EXCHANGE_TYPE = 1; + public static final int LAST_TRADED_QTY_OFFSET = 51; + public static final int AVG_TRADED_PRICE_OFFSET = 59; + public static final int VOLUME_TRADED_TODAY_OFFSET = 67; + public static final int TOTAL_BUY_QTY_OFFSET = 75; + public static final int TOTAL_SELL_QTY_OFFSET = 83; + public static final int OPEN_PRICE_OFFSET = 91; + public static final int HIGH_PRICE_OFFSET = 99; + public static final int LOW_PRICE_OFFSET = 107; + public static final int CLOSE_PRICE_OFFSET = 115; + public static final int LAST_TRADED_TIMESTAMP_OFFSET = 123; + public static final int OPEN_INTEREST_OFFSET = 131; + public static final int OPEN_INTEREST_CHANGE_PERC_OFFSET = 139; + public static final int UPPER_CIRCUIT_OFFSET = 347; + public static final int LOWER_CIRCUIT_OFFSET = 355; + public static final int YEARLY_HIGH_PRICE_OFFSET = 363; + public static final int YEARLY_LOW_PRICE_OFFSET = 371; + + + + public static final int BUY_START_POSITION = 147; + public static final int SELL_START_POSITION = 247; + public static final int NUM_PACKETS = 5; + public static final int PACKET_SIZE = 20; + public static final int BUY_SELL_FLAG_OFFSET = 0; + public static final int QUANTITY_OFFSET = 2; + public static final int PRICE_OFFSET = 10; + public static final int NUMBER_OF_ORDERS_OFFSET = 18; + public static final int PRICE_CONVERSION_FACTOR = 100; + + + public static final String SMART_API_EXCEPTION_ERROR_MSG = "The operation failed to execute because of a SmartAPIException error"; + public static final String IO_EXCEPTION_ERROR_MSG = "The operation failed to execute because of an IO error."; + public static final String JSON_EXCEPTION_ERROR_MSG = "The operation failed to execute because of a JSON error"; + public static final String SMART_API_EXCEPTION_OCCURRED = "SmartAPIException occurred "; + public static final String IO_EXCEPTION_OCCURRED = "IOException occurred "; + public static final String JSON_EXCEPTION_OCCURRED = "JSONException occurred "; + + public static final String TOKEN_EXCEPTION_MESSAGE = "Unauthorized access. Please provide a valid or non-expired jwtToken."; + public static final String APIKEY_EXCEPTION_MESSAGE = "Invalid or missing api key. Please provide a valid api key."; } diff --git a/src/main/java/com/angelbroking/smartapi/utils/Utils.java b/src/main/java/com/angelbroking/smartapi/utils/Utils.java new file mode 100644 index 0000000..21e9fc5 --- /dev/null +++ b/src/main/java/com/angelbroking/smartapi/utils/Utils.java @@ -0,0 +1,60 @@ +package com.angelbroking.smartapi.utils; + +public class Utils { + private Utils() { + + } + + public static boolean isEmpty(final CharSequence cs) { + return cs == null || cs.length() == 0; + } + + public static boolean isNotEmpty(final CharSequence cs) { + return !isEmpty(cs); + } + + public static boolean areCharArraysEqual(char[] a, char[] b) { + if (a == null && b == null) { + return true; + } + + if (a != null && b != null) { + if (a.length == b.length) { + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } + } + return false; + } + + public static boolean areByteArraysEqual(byte[] a, byte[] b) { + if (a == null && b == null) { + return true; + } + + if (a != null && b != null) { + if (a.length == b.length) { + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } + } + return false; + } + + public static boolean validateInputNullCheck(T input) { + return input == null; + } + + public static boolean validateInputNotNullCheck(T input) { + return input != null; + } + +} diff --git a/src/test/java/com/angelbroking/smartapi/SmartConnectTest.java b/src/test/java/com/angelbroking/smartapi/SmartConnectTest.java new file mode 100644 index 0000000..14c4b9e --- /dev/null +++ b/src/test/java/com/angelbroking/smartapi/SmartConnectTest.java @@ -0,0 +1,323 @@ +package com.angelbroking.smartapi; + +import com.angelbroking.smartapi.http.SmartAPIRequestHandler; +import com.angelbroking.smartapi.http.exceptions.DataException; +import com.angelbroking.smartapi.http.exceptions.SmartAPIException; +import lombok.extern.slf4j.Slf4j; +import org.json.JSONArray; +import org.json.JSONException; + +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.io.IOException; + + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + + +import static com.angelbroking.smartapi.utils.Constants.IO_EXCEPTION_ERROR_MSG; +import static com.angelbroking.smartapi.utils.Constants.IO_EXCEPTION_OCCURRED; +import static com.angelbroking.smartapi.utils.Constants.JSON_EXCEPTION_ERROR_MSG; +import static com.angelbroking.smartapi.utils.Constants.JSON_EXCEPTION_OCCURRED; +import static com.angelbroking.smartapi.utils.Constants.SMART_API_EXCEPTION_ERROR_MSG; +import static com.angelbroking.smartapi.utils.Constants.SMART_API_EXCEPTION_OCCURRED; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + + +@RunWith(MockitoJUnitRunner.Silent.class) +@Slf4j +public class SmartConnectTest { + @Mock + private SmartAPIRequestHandler smartAPIRequestHandler; + + @Mock + private SmartConnect smartConnect; + + @Mock + private Routes routes; + private String apiKey; + private String accessToken; + + + @Before + public void setup() { + apiKey = System.getProperty("apiKey"); + accessToken = System.getenv("accessToken"); + } + + + @Test + public void testGetSearchScript_Success() throws SmartAPIException, IOException { + // Mock the necessary objects + JSONObject payload = new JSONObject(); + when(smartConnect.getSearchScrip(payload)).thenReturn("response-data"); + + // Call the method under test + String result = smartConnect.getSearchScrip(payload); + // Assert the result + assertEquals("response-data", result); + + } + + @Test(expected = SmartAPIException.class) + public void testGetSearchScript_Exception() throws SmartAPIException, IOException { + JSONObject payload = new JSONObject(); + SmartAPIException expectedException = new SmartAPIException("Simulated SmartAPIException"); + when(smartConnect.getSearchScrip(payload)).thenThrow(expectedException); + try { + smartConnect.getSearchScrip(payload); + } catch (SmartAPIException e) { + throw new SmartAPIException(String.format("The operation failed to execute because of a SmartAPIException error in Search scrip api data %s", e)); + } + verify(smartConnect).getSearchScrip(payload); + } + + + + + private static JSONObject createMarketDataResponse() { + JSONObject jsonObject = new JSONObject(); + + // Create "data" object + JSONObject dataObject = new JSONObject(); + + // Create the "unfetched" array + JSONArray unfetchedArray = new JSONArray(); + dataObject.put("unfetched", unfetchedArray); + + // Create the "fetched" array and its elements + JSONArray fetchedArray = new JSONArray(); + JSONObject fetchedElement = new JSONObject(); + fetchedElement.put("netChange", 3.15); + fetchedElement.put("tradeVolume", 5718111); + fetchedElement.put("lowerCircuit", 533.15); + fetchedElement.put("percentChange", 0.53); + fetchedElement.put("exchFeedTime", "19-Jul-2023 11:20:51"); + fetchedElement.put("avgPrice", 595.25); + fetchedElement.put("ltp", 595.5); + fetchedElement.put("exchTradeTime", "19-Jul-2023 11:20:51"); + fetchedElement.put("totSellQuan", 1924292); + fetchedElement.put("upperCircuit", 651.55); + fetchedElement.put("lastTradeQty", 46); + fetchedElement.put("high", 599.6); + fetchedElement.put("totBuyQuan", 890300); + + // Create the "depth" object and its "buy" and "sell" arrays + JSONObject depthObject = new JSONObject(); + JSONArray buyArray = new JSONArray(); + JSONArray sellArray = new JSONArray(); + + // Add elements to "buy" array + JSONObject buyElement1 = new JSONObject(); + buyElement1.put("quantity", 1776); + buyElement1.put("price", 595.3); + buyElement1.put("orders", 3); + buyArray.put(buyElement1); + + JSONObject buyElement2 = new JSONObject(); + buyElement2.put("quantity", 767); + buyElement2.put("price", 595.25); + buyElement2.put("orders", 3); + buyArray.put(buyElement2); + + // Add elements to "sell" array + JSONObject sellElement1 = new JSONObject(); + sellElement1.put("quantity", 249); + sellElement1.put("price", 595.5); + sellElement1.put("orders", 5); + sellArray.put(sellElement1); + + JSONObject sellElement2 = new JSONObject(); + sellElement2.put("quantity", 1379); + sellElement2.put("price", 595.55); + sellElement2.put("orders", 4); + sellArray.put(sellElement2); + + // Add "buy" and "sell" arrays to "depth" object + depthObject.put("buy", buyArray); + depthObject.put("sell", sellArray); + + fetchedElement.put("depth", depthObject); + + // Add remaining properties to "fetched" element + fetchedElement.put("low", 592); + fetchedElement.put("exchange", "NSE"); + fetchedElement.put("opnInterest", 0); + fetchedElement.put("tradingSymbol", "SBIN-EQ"); + fetchedElement.put("symbolToken", "3045"); + fetchedElement.put("close", 592.35); + fetchedElement.put("52WeekLow", 482.1); + fetchedElement.put("open", 594.65); + fetchedElement.put("52WeekHigh", 629.55); + + // Add the "fetched" element to the "fetched" array + fetchedArray.put(fetchedElement); + + dataObject.put("fetched", fetchedArray); + + // Add "data" object, "message", "errorcode", and "status" properties to the main JSON object + jsonObject.put("data", dataObject); + jsonObject.put("message", "SUCCESS"); + jsonObject.put("errorcode", ""); + jsonObject.put("status", true); + + return jsonObject; + } + + // Testing market data success for Full payload + @Test + public void testMarketData_Success() throws SmartAPIException, IOException { + String url = routes.get("api.market.data"); + JSONObject params = getMarketDataRequest("FULL"); + when(smartAPIRequestHandler.postRequest(eq(this.apiKey), eq(url), eq(params), eq(this.accessToken))).thenReturn(createMarketDataResponse()); + try { + JSONObject response = smartAPIRequestHandler.postRequest(this.apiKey, url, params, this.accessToken); + JSONObject data = response.getJSONObject("data"); + assertNotNull(data); + } catch (SmartAPIException ex) { + log.error("{} while placing order {}", SMART_API_EXCEPTION_OCCURRED, ex.toString()); + throw new SmartAPIException(String.format("%s in placing order %s", SMART_API_EXCEPTION_ERROR_MSG, ex)); + } catch (IOException ex) { + log.error("{} while placing order {}", IO_EXCEPTION_OCCURRED, ex.getMessage()); + throw new IOException(String.format("%s in placing order %s", IO_EXCEPTION_ERROR_MSG, ex.getMessage())); + } catch (JSONException ex) { + log.error("{} while placing order {}", JSON_EXCEPTION_OCCURRED, ex.getMessage()); + throw new JSONException(String.format("%s in placing order %s", JSON_EXCEPTION_ERROR_MSG, ex.getMessage())); + } + } + + // Testing market data failure for Full payload + @Test(expected = SmartAPIException.class) + public void testMarketData_Failure() throws SmartAPIException, IOException { + // Stub the postRequest method + String url = routes.get("api.market.data"); + JSONObject params = getMarketDataRequest("FULL"); + when(smartAPIRequestHandler.postRequest(eq(this.apiKey), eq(url), eq(params), eq(this.accessToken))) + .thenThrow(new SmartAPIException("API request failed")); + try { + JSONObject response = smartAPIRequestHandler.postRequest(apiKey, url, params, accessToken); + response.getJSONObject("data"); + } catch (SmartAPIException ex) { + log.error("{} while placing order {}", SMART_API_EXCEPTION_OCCURRED, ex.toString()); + throw new SmartAPIException(String.format("%s in placing order %s", SMART_API_EXCEPTION_ERROR_MSG, ex)); + } catch (IOException ex) { + log.error("{} while placing order {}", IO_EXCEPTION_OCCURRED, ex.getMessage()); + throw new IOException(String.format("%s in placing order %s", IO_EXCEPTION_ERROR_MSG, ex.getMessage())); + } catch (JSONException ex) { + log.error("{} while placing order {}", JSON_EXCEPTION_OCCURRED, ex.getMessage()); + throw new JSONException(String.format("%s in placing order %s", JSON_EXCEPTION_ERROR_MSG, ex.getMessage())); + } + } + + // Testing market data success for LTP payload + @Test + public void testMarketDataLTP_Success() throws SmartAPIException, IOException { + String url = routes.get("api.market.data"); + JSONObject params = getMarketDataRequest("LTP"); + when(smartAPIRequestHandler.postRequest(eq(this.apiKey), eq(url), eq(params), eq(this.accessToken))).thenReturn(createMarketDataResponse()); + try { + JSONObject response = smartAPIRequestHandler.postRequest(this.apiKey, url, params, this.accessToken); + JSONObject data = response.getJSONObject("data"); + assertNotNull(data); + } catch (SmartAPIException ex) { + log.error("{} while placing order {}", SMART_API_EXCEPTION_OCCURRED, ex.toString()); + throw new SmartAPIException(String.format("%s in placing order %s", SMART_API_EXCEPTION_ERROR_MSG, ex)); + } catch (IOException ex) { + log.error("{} while placing order {}", IO_EXCEPTION_OCCURRED, ex.getMessage()); + throw new IOException(String.format("%s in placing order %s", IO_EXCEPTION_ERROR_MSG, ex.getMessage())); + } catch (JSONException ex) { + log.error("{} while placing order {}", JSON_EXCEPTION_OCCURRED, ex.getMessage()); + throw new JSONException(String.format("%s in placing order %s", JSON_EXCEPTION_ERROR_MSG, ex.getMessage())); + } + } + + // Testing market data failure for LTP payload + @Test(expected = SmartAPIException.class) + public void testMarketDataLTP_Failure() throws SmartAPIException, IOException { + // Stub the postRequest method + String url = routes.get("api.market.data"); + JSONObject params = getMarketDataRequest("LTP"); + when(smartAPIRequestHandler.postRequest(eq(this.apiKey), eq(url), eq(params), eq(this.accessToken))) + .thenThrow(new SmartAPIException("API request failed")); + try { + JSONObject response = smartAPIRequestHandler.postRequest(apiKey, url, params, accessToken); + response.getJSONObject("data"); + } catch (SmartAPIException ex) { + log.error("{} while placing order {}", SMART_API_EXCEPTION_OCCURRED, ex.toString()); + throw new SmartAPIException(String.format("%s in placing order %s", SMART_API_EXCEPTION_ERROR_MSG, ex)); + } catch (IOException ex) { + log.error("{} while placing order {}", IO_EXCEPTION_OCCURRED, ex.getMessage()); + throw new IOException(String.format("%s in placing order %s", IO_EXCEPTION_ERROR_MSG, ex.getMessage())); + } catch (JSONException ex) { + log.error("{} while placing order {}", JSON_EXCEPTION_OCCURRED, ex.getMessage()); + throw new JSONException(String.format("%s in placing order %s", JSON_EXCEPTION_ERROR_MSG, ex.getMessage())); + } + } + + // Testing market data success for OHLC payload + @Test + public void testMarketDataOHLC_Success() throws SmartAPIException, IOException { + String url = routes.get("api.market.data"); + JSONObject params = getMarketDataRequest("OHLC"); + when(smartAPIRequestHandler.postRequest(eq(this.apiKey), eq(url), eq(params), eq(this.accessToken))).thenReturn(createMarketDataResponse()); + try { + JSONObject response = smartAPIRequestHandler.postRequest(this.apiKey, url, params, this.accessToken); + JSONObject data = response.getJSONObject("data"); + assertNotNull(data); + } catch (SmartAPIException ex) { + log.error("{} while placing order {}", SMART_API_EXCEPTION_OCCURRED, ex.toString()); + throw new SmartAPIException(String.format("%s in placing order %s", SMART_API_EXCEPTION_ERROR_MSG, ex)); + } catch (IOException ex) { + log.error("{} while placing order {}", IO_EXCEPTION_OCCURRED, ex.getMessage()); + throw new IOException(String.format("%s in placing order %s", IO_EXCEPTION_ERROR_MSG, ex.getMessage())); + } catch (JSONException ex) { + log.error("{} while placing order {}", JSON_EXCEPTION_OCCURRED, ex.getMessage()); + throw new JSONException(String.format("%s in placing order %s", JSON_EXCEPTION_ERROR_MSG, ex.getMessage())); + } + } + + // Testing market data failure for OHLC payload + @Test(expected = SmartAPIException.class) + public void testMarketDataOHLC_Failure() throws SmartAPIException, IOException { + // Stub the postRequest method + String url = routes.get("api.market.data"); + JSONObject params = getMarketDataRequest("OHLC"); + when(smartAPIRequestHandler.postRequest(eq(this.apiKey), eq(url), eq(params), eq(this.accessToken))) + .thenThrow(new SmartAPIException("API request failed")); + try { + JSONObject response = smartAPIRequestHandler.postRequest(apiKey, url, params, accessToken); + response.getJSONObject("data"); + } catch (SmartAPIException ex) { + log.error("{} while placing order {}", SMART_API_EXCEPTION_OCCURRED, ex.toString()); + throw new SmartAPIException(String.format("%s in placing order %s", SMART_API_EXCEPTION_ERROR_MSG, ex)); + } catch (IOException ex) { + log.error("{} while placing order {}", IO_EXCEPTION_OCCURRED, ex.getMessage()); + throw new IOException(String.format("%s in placing order %s", IO_EXCEPTION_ERROR_MSG, ex.getMessage())); + } catch (JSONException ex) { + log.error("{} while placing order {}", JSON_EXCEPTION_OCCURRED, ex.getMessage()); + throw new JSONException(String.format("%s in placing order %s", JSON_EXCEPTION_ERROR_MSG, ex.getMessage())); + } + } + + private JSONObject getMarketDataRequest(String mode) { + JSONObject payload = new JSONObject(); + payload.put("mode", mode); + JSONObject exchangeTokens = new JSONObject(); + JSONArray nseTokens = new JSONArray(); + nseTokens.put("3045"); + exchangeTokens.put("NSE", nseTokens); + payload.put("exchangeTokens", exchangeTokens); + return payload; + } + +} + diff --git a/src/test/java/com/angelbroking/smartapi/smartstream/SmartStreamListenerImpl.java b/src/test/java/com/angelbroking/smartapi/smartstream/SmartStreamListenerImpl.java new file mode 100644 index 0000000..4993ccd --- /dev/null +++ b/src/test/java/com/angelbroking/smartapi/smartstream/SmartStreamListenerImpl.java @@ -0,0 +1,105 @@ +package com.angelbroking.smartapi.smartstream; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Arrays; + +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.SmartStreamSubsMode; +import com.angelbroking.smartapi.smartstream.models.SnapQuote; +import com.angelbroking.smartapi.smartstream.ticker.SmartStreamListener; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SmartStreamListenerImpl implements SmartStreamListener { + + public static final ZoneId TZ_UTC = ZoneId.of("UTC"); + public static final ZoneId TZ_IST = ZoneId.of("Asia/Kolkata"); + + @Override + public void onLTPArrival(LTP ltp) { + ZonedDateTime exchangeTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(ltp.getExchangeFeedTimeEpochMillis()), TZ_IST); + String ltpData = String.format( + "subscriptionMode: %s exchangeType: %s token: %s sequenceNumber: %d ltp: %.2f exchangeTime: %s exchangeToClientLatency: %s", + SmartStreamSubsMode.findByVal(ltp.getSubscriptionMode()), + ltp.getExchangeType(), ltp.getToken().toString(), ltp.getSequenceNumber(), + (ltp.getLastTradedPrice() / 100.0), exchangeTime, + Instant.now().toEpochMilli() - ltp.getExchangeFeedTimeEpochMillis()); + log.info(ltpData); + } + + @Override + public void onQuoteArrival(Quote quote) { + ZonedDateTime exchangeTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(quote.getExchangeFeedTimeEpochMillis()), TZ_IST); + String data = String.format("token: %s" + + " sequenceNumber: %d" + + " ltp: %.2f" + + " open: %.2f" + + " high: %.2f" + + " low: %.2f" + + " close: %.2f" + + " exchangeTime: %s" + + " exchangeToClientLatency: %s", + quote.getToken().toString(), + quote.getSequenceNumber(), + (quote.getLastTradedPrice() / 100.0), + (quote.getOpenPrice() / 100.0), + (quote.getHighPrice() / 100.0), + (quote.getLowPrice() / 100.0), + (quote.getClosePrice() / 100.0), + exchangeTime, + Instant.now().toEpochMilli() - quote.getExchangeFeedTimeEpochMillis()); + log.info(data); + } + + @Override + public void onSnapQuoteArrival(SnapQuote snapQuote) { + String snapQuoteData = String.format( + "subscriptionMode: %s exchangeType: %s token: %s sequenceNumber: %d ltp: %.2f lastTradedQty: %d avgTradedPrice: %.2f volumeTradedToday: %d totalBuyQty: %.2f totalSellQty: %.2f open: %.2f high: %.2f low: %.2f close: %.2f " + + "lastTradedTimestamp: %s openInterest: %.2f openInterestChangePerc: %.2f bestFiveBuyData: %s bestFiveSellData: %s upperCircuit: %.2f lowerCircuit: %.2f yearlyHighPrice: %.2f yearlyLowPrice: %.2f exchangeTime: %s exchangeToClientLatency: %s", + SmartStreamSubsMode.findByVal(snapQuote.getSubscriptionMode()), + snapQuote.getExchangeType(), snapQuote.getToken().toString(), + snapQuote.getSequenceNumber(), (snapQuote.getLastTradedPrice() / 100.0), snapQuote.getLastTradedQty(), + (snapQuote.getAvgTradedPrice() / 100.0), snapQuote.getVolumeTradedToday(), snapQuote.getTotalBuyQty(), + snapQuote.getTotalSellQty(), (snapQuote.getOpenPrice() / 100.0), (snapQuote.getHighPrice() / 100.0), + (snapQuote.getLowPrice() / 100.0), (snapQuote.getClosePrice() / 100.0), + snapQuote.getLastTradedTimestamp(), (snapQuote.getOpenInterest() / 100.0), + (snapQuote.getOpenInterestChangePerc()), Arrays.toString(snapQuote.getBestFiveBuy()), + Arrays.toString(snapQuote.getBestFiveSell()), (snapQuote.getUpperCircuit() / 100.0), + (snapQuote.getLowerCircuit() / 100.0), (snapQuote.getYearlyHighPrice() / 100.0), + (snapQuote.getYearlyLowPrice() / 100.0), getExchangeTime(snapQuote.getExchangeFeedTimeEpochMillis()), + Instant.now().toEpochMilli() - snapQuote.getExchangeFeedTimeEpochMillis()); + log.info(snapQuoteData); + } + + private ZonedDateTime getExchangeTime(long exchangeFeedTimeEpochMillis) { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(exchangeFeedTimeEpochMillis), TZ_IST); + } + + @Override + public void onConnected() { + // TODO Auto-generated method stub + + } + + @Override + public void onError(SmartStreamError error) { + error.getException().printStackTrace(); + } + + @Override + public void onPong() { + log.info("pong received"); + } + + @Override + public void onDisconnected() { + // TODO Auto-generated method stub + + } + +} diff --git a/src/test/java/com/angelbroking/smartapi/smartstream/SmartStreamTickerTest.java b/src/test/java/com/angelbroking/smartapi/smartstream/SmartStreamTickerTest.java new file mode 100644 index 0000000..ee48d20 --- /dev/null +++ b/src/test/java/com/angelbroking/smartapi/smartstream/SmartStreamTickerTest.java @@ -0,0 +1,95 @@ +package com.angelbroking.smartapi.smartstream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +import java.util.HashSet; +import java.util.Scanner; +import java.util.Set; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.angelbroking.smartapi.SmartConnect; +import com.angelbroking.smartapi.models.User; +import com.angelbroking.smartapi.smartstream.models.ExchangeType; +import com.angelbroking.smartapi.smartstream.models.SmartStreamSubsMode; +import com.angelbroking.smartapi.smartstream.models.TokenID; +import com.angelbroking.smartapi.smartstream.ticker.SmartStreamTicker; +import com.neovisionaries.ws.client.WebSocketException; + +public class SmartStreamTickerTest { + + private static String clientID; + private static String clientPass; + private static String apiKey; + private static String feedToken; + private static String totp; + + @BeforeAll + public static void initClass() throws InterruptedException { + clientID = System.getProperty("clientID"); + clientPass = System.getProperty("clientPass"); + apiKey = System.getProperty("apiKey"); + + Scanner sc = new Scanner(System.in); + System.out.print("enter totp: "); + totp = sc.nextLine(); + + SmartConnect smartConnect = new SmartConnect(apiKey); + User user = smartConnect.generateSession(clientID, clientPass, totp); + feedToken = user.getFeedToken(); +// feedToken = "123"; + } + + @Test + void testSmartStreamTicketLTP() throws WebSocketException, InterruptedException { + try { + SmartStreamTicker ticker = new SmartStreamTicker(clientID, feedToken, new SmartStreamListenerImpl()); + ticker.connect(); + ticker.subscribe(SmartStreamSubsMode.QUOTE, getTokens()); +// ticker.subscribe(SmartStreamSubsMode.SNAP_QUOTE, getTokens()); + // uncomment the below line to allow test thread to keep running so that ticks + // can be received in the listener +// Thread.sleep(5000); + ticker.disconnect(); + System.out.println("isConnected = "+ticker.isConnectionOpen()); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + + } + + private Set 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, "26000")); // NIFTY + tokenSet.add(new TokenID(ExchangeType.NSE_CM, "26009")); // NIFTY BANK + tokenSet.add(new TokenID(ExchangeType.BSE_CM, "19000")); // Sensex + + tokenSet.add(new TokenID(ExchangeType.NSE_CM, "99926000")); // NIFTY + tokenSet.add(new TokenID(ExchangeType.NSE_CM, "99926009")); // NIFTY BANK + tokenSet.add(new TokenID(ExchangeType.BSE_CM, "99919000")); // Sensex + + tokenSet.add(new TokenID(ExchangeType.NSE_CM, "1594")); // NSE Infosys + tokenSet.add(new TokenID(ExchangeType.NSE_FO, "35003")); // Nifty June 2023 FUT + tokenSet.add(new TokenID(ExchangeType.CDE_FO, "1185")); // 1185 USDINR + tokenSet.add(new TokenID(ExchangeType.BSE_CM, "532540")); // BSE TCS + tokenSet.add(new TokenID(ExchangeType.NCX_FO, "GUARGUM5")); // GUAREX (NCDEX) + tokenSet.add(new TokenID(ExchangeType.MCX_FO, "252453")); //CRUDEOIL + 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); + + } +} \ No newline at end of file