Java tutorial
/* * Copyright (C) 2015 Nu Development Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package com.nubits.nubot.trading.wrappers; import com.nubits.nubot.bot.Global; import com.nubits.nubot.exchanges.Exchange; import com.nubits.nubot.global.Constant; import com.nubits.nubot.global.Settings; import com.nubits.nubot.models.*; import com.nubits.nubot.trading.*; import com.nubits.nubot.trading.keys.ApiKeys; import com.nubits.nubot.utils.Utils; import org.json.JSONException; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.net.ssl.HttpsURLConnection; import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.NoRouteToHostException; import java.net.URL; import java.net.UnknownHostException; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; public class BtceWrapper implements TradeInterface { private static final Logger LOG = LoggerFactory.getLogger(BtceWrapper.class.getName()); //Class fields private ApiKeys keys; protected BtceService service; private Exchange exchange; private String checkConnectionUrl = "http://btc-e.com"; private final String SIGN_HASH_FUNCTION = "HmacSHA512"; private final String ENCODING = "UTF-8"; private final String API_BASE_URL = "https://btc-e.com/tapi/"; private final String API_GET_INFO = "getInfo"; private final String API_TRADE = "Trade"; private final String API_ACTIVE_ORDERS = "ActiveOrders"; private final String API_CANCEL_ORDER = "CancelOrder"; private final String API_GET_FEE = "https://btc-e.com/exchange/"; private final String API_V2_URL = "https://btc-e.com/api/2/"; private final String API_TICKER_USD = "btc_usd/ticker"; // Errors private ErrorManager errors = new ErrorManager(); private final String TOKEN_ERR = "error"; private final String TOKEN_BAD_RETURN = "No Connection With Exchange"; public BtceWrapper(ApiKeys keys, Exchange exchange) { this.keys = keys; this.exchange = exchange; service = new BtceService(keys); setupErrors(); } private void setupErrors() { errors.setExchangeName(exchange); } protected static String createNonce() { long toRet = Math.round(System.currentTimeMillis() / 1000); return Long.toString(toRet); } private ApiResponse getQuery(String url, String method, HashMap<String, String> query_args, boolean needAuth, boolean isGet) { ApiResponse apiResponse = new ApiResponse(); String queryResult = query(API_BASE_URL, method, query_args, needAuth, isGet); if (queryResult == null) { apiResponse.setError(errors.nullReturnError); return apiResponse; } if (queryResult.equals(TOKEN_BAD_RETURN)) { apiResponse.setError(errors.noConnectionError); return apiResponse; } JSONParser parser = new JSONParser(); try { JSONObject httpAnswerJson = (JSONObject) (parser.parse(queryResult)); long success = (long) httpAnswerJson.get("success"); if (success == 0) { //error String errorMessage = (String) httpAnswerJson.get("error"); ApiError apiErr = errors.apiReturnError; apiErr.setDescription(errorMessage); //LOG.error("Btce returned an error: " + errorMessage); apiResponse.setError(apiErr); } else { apiResponse.setResponseObject(httpAnswerJson); } } catch (ClassCastException cce) { //if casting to a JSON object failed, try a JSON Array try { JSONArray httpAnswerJson = (JSONArray) (parser.parse(queryResult)); apiResponse.setResponseObject(httpAnswerJson); } catch (ParseException pe) { LOG.error("httpResponse: " + queryResult + " \n" + pe.toString()); apiResponse.setError(errors.parseError); } } catch (ParseException ex) { LOG.error("httpresponse: " + queryResult + " \n" + ex.toString()); apiResponse.setError(errors.parseError); return apiResponse; } return apiResponse; } @Override public ApiResponse getAvailableBalances(CurrencyPair pair) { ApiResponse apiResponse = new ApiResponse(); String url = API_BASE_URL; String method = API_GET_INFO; boolean isGet = false; HashMap<String, String> query_args = new HashMap<>(); /*Params * */ ApiResponse response = getQuery(url, method, query_args, true, isGet); if (response.isPositive()) { JSONObject httpAnswerJson = (JSONObject) response.getResponseObject(); JSONObject dataJson = (JSONObject) httpAnswerJson.get("return"); JSONObject funds = (JSONObject) dataJson.get("funds"); String pegCode = pair.getPaymentCurrency().getCode().toLowerCase(); String nbtCode = pair.getOrderCurrency().getCode().toLowerCase(); Amount PEGTotal = new Amount(Double.parseDouble(funds.get(pegCode).toString()), CurrencyList.USD); Amount NBTTotal = new Amount(Double.parseDouble(funds.get(nbtCode).toString()), CurrencyList.NBT); PairBalance balance = new PairBalance(NBTTotal, PEGTotal); //Pack it into the ApiResponse apiResponse.setResponseObject(balance); } else { apiResponse = response; } return apiResponse; } @Override public ApiResponse getAvailableBalance(Currency currency) { ApiResponse apiResponse = new ApiResponse(); String url = API_BASE_URL; String method = API_GET_INFO; boolean isGet = false; HashMap<String, String> query_args = new HashMap<>(); ApiResponse response = getQuery(url, method, query_args, true, isGet); if (response.isPositive()) { JSONObject httpAnswerJson = (JSONObject) response.getResponseObject(); JSONObject dataJson = (JSONObject) httpAnswerJson.get("return"); JSONObject funds = (JSONObject) dataJson.get("funds"); Amount amount = new Amount(Double.parseDouble(funds.get(currency.getCode().toLowerCase()).toString()), currency); apiResponse.setResponseObject(amount); } else { apiResponse = response; } return apiResponse; } @Override public ApiResponse getLastPrice(CurrencyPair pair) { Ticker ticker = new Ticker(); ApiResponse apiResponse = new ApiResponse(); String url = API_V2_URL; String method = API_TICKER_USD; boolean isGet = false; double last = -1; double ask = -1; double bid = -1; HashMap<String, String> query_args = new HashMap<>(); ApiResponse response = getQuery(url, method, query_args, false, isGet); if (response.isPositive()) { JSONObject httpAnswerJson = (JSONObject) response.getResponseObject(); JSONObject tickerObject = (JSONObject) httpAnswerJson.get("ticker"); last = Utils.getDouble(tickerObject.get("last")); bid = Utils.getDouble(tickerObject.get("sell")); ask = Utils.getDouble(tickerObject.get("buy")); ticker.setAsk(ask); ticker.setBid(bid); ticker.setLast(last); apiResponse.setResponseObject(ticker); } else { apiResponse = response; } return apiResponse; } @Override public ApiResponse sell(CurrencyPair pair, double amount, double rate) { return enterOrder(Constant.SELL, pair, amount, rate); } @Override public ApiResponse buy(CurrencyPair pair, double amount, double rate) { return enterOrder(Constant.BUY, pair, amount, rate); } @Override public ApiResponse getActiveOrders() { return getActiveOrdersImpl(null); } @Override public ApiResponse getActiveOrders(CurrencyPair pair) { return getActiveOrdersImpl(pair); } @Override public ApiResponse getOrderDetail(String orderID) { ApiResponse apiResp = new ApiResponse(); Order order = null; ApiResponse listApiResp = getActiveOrders(); if (listApiResp.isPositive()) { ArrayList<Order> orderList = (ArrayList<Order>) listApiResp.getResponseObject(); boolean found = false; for (int i = 0; i < orderList.size(); i++) { Order tempOrder = orderList.get(i); if (orderID.equals(tempOrder.getId())) { found = true; apiResp.setResponseObject(tempOrder); return apiResp; } } if (!found) { ApiError error = errors.genericError; error.setDescription("Cannot find the order with id " + orderID); apiResp.setError(error); return apiResp; } } else { return listApiResp; } return apiResp; } @Override public ApiResponse cancelOrder(String orderID, CurrencyPair pair) { ApiResponse apiResponse = new ApiResponse(); String url = API_BASE_URL; String method = API_CANCEL_ORDER; boolean isGet = false; HashMap<String, String> query_args = new HashMap<>(); /*Params * order_id */ query_args.put("order_id", orderID); ApiResponse response = getQuery(url, method, query_args, true, isGet); if (response.isPositive()) { apiResponse.setResponseObject(true); } else { apiResponse = response; } return apiResponse; } @Override public ApiResponse getTxFee() { return new ApiResponse(true, Global.options.getTxFee(), null); /* else { ApiResponse apiResponse = new ApiResponse(); String strDelimiterStart = "the fee for transactions is "; String strDelimterStop = "%.</p>"; String content = "ERROR"; try { content = Utils.getHTML(API_GET_FEE, true); } catch (IOException ex) { LOG.error(ex.toString()); } if (!content.equals("ERROR")) { int startIndex = content.lastIndexOf(strDelimiterStart) + strDelimiterStart.length(); int stopIndex = content.lastIndexOf(strDelimterStop); String feeString = content.substring(startIndex, stopIndex); double fee = Double.parseDouble(feeString); apiResponse.setResponseObject(fee); } else { apiResponse.setError(errors.genericError); } return apiResponse; }*/ } @Override public ApiResponse getTxFee(CurrencyPair pair) { LOG.info("Btc-e uses global TX fee, currency pair not supprted. \n" + "now calling getTxFee()"); return getTxFee(); } /* public ApiResponse getPermissions() { ApiResponse apiResponse = new ApiResponse(); String path = API_GET_INFO; HashMap<String, String> query_args = new HashMap<>(); ApiPermissions permissions = new ApiPermissions(false, false, false, false, false, false); String queryResult = query(API_BASE_URL, API_GET_INFO, query_args, false); if (queryResult.startsWith(TOKEN_ERR)) { apiResponse.setError(new ApiError(ERROR_GENERIC, "Generic error with btce service call")); return apiResponse; } if (queryResult.equals(ERROR_NO_CONNECTION)) { apiResponse.setError(getErrorByCode(ERROR_NO_CONNECTION)); return apiResponse; } /*Sample result *{ *"success":1, *"return":{ * "funds":{ * "usd":325, * "btc":23.998, * "sc":121.998, * "ltc":0, * "ruc":0, * "nmc":0 * }, * "rights":{ * "info":1, * "trade":1 * }, * "transaction_count":80, * "open_orders":1, * "server_time":1342123547 * } *} */ /* JSONParser parser = new JSONParser(); try { JSONObject httpAnswerJson = (JSONObject) (parser.parse(queryResult)); long success = (long) httpAnswerJson.get("success"); if (success == 0) { //error String error = (String) httpAnswerJson.get("error"); apiResponse.setError(new ApiError(ERRROR_API, error)); LOG.error("Btce returned an error: " + error); return apiResponse; } else { //correct JSONObject dataJson = (JSONObject) httpAnswerJson.get("return"); JSONObject rightsJson = (JSONObject) dataJson.get("rights"); long info = (long) rightsJson.get("info"); long trade = (long) rightsJson.get("trade"); long withdraw = (long) rightsJson.get("withdraw"); if (info == 1) { permissions.setGet_info(true); } if (trade == 1) { permissions.setTrade(true); } if (withdraw == 1) { permissions.setWithdraw(true); } permissions.setValid_keys(true); } } catch (ParseException ex) { LOG.error(ex.toStringSep()); apiResponse.setError(new ApiError(ERROR_PARSING, "Error while parsing api permissions for btce")); } apiResponse.setResponseObject(permissions); return apiResponse; } */ private ApiResponse getActiveOrdersImpl(CurrencyPair pair) { ApiResponse apiResponse = new ApiResponse(); ArrayList<Order> orderList = new ArrayList<Order>(); String url = API_BASE_URL; String method = API_ACTIVE_ORDERS; boolean isGet = false; HashMap<String, String> query_args = new HashMap<>(); /*Params * pair, default all pairs */ if (pair != null) { query_args.put("pair", pair.toStringSep()); } ApiResponse response = getQuery(url, method, query_args, true, isGet); if (response.isPositive()) { try { JSONObject httpAnswerJson = (JSONObject) response.getResponseObject(); org.json.JSONObject dataJson = (org.json.JSONObject) httpAnswerJson.get("return"); //Iterate on orders String names[] = org.json.JSONObject.getNames(dataJson); for (int i = 0; i < names.length; i++) { org.json.JSONObject tempJson = dataJson.getJSONObject(names[i]); Order temp = new Order(); temp.setId(names[i]); //Create a CurrencyPair object CurrencyPair cp = CurrencyPair.getCurrencyPairFromString((String) tempJson.get("pair"), "_"); //Parse the status to encapsulate into Order object boolean executed = false; int status = tempJson.getInt("status"); switch (status) { case 0: { executed = false; break; } case 1: { executed = true; break; } default: { apiResponse.setError(new ApiError(231445, "Order status unknown : " + status)); break; } } temp.setPair(cp); temp.setType((String) tempJson.get("type")); temp.setAmount(new Amount(tempJson.getDouble("amount"), cp.getOrderCurrency())); temp.setPrice(new Amount(tempJson.getDouble("rate"), cp.getPaymentCurrency())); temp.setInsertedDate(new Date(tempJson.getLong("timestamp_created"))); temp.setCompleted(executed); if (!executed) //Do not return orders that are already executed { orderList.add(temp); } } apiResponse.setResponseObject(orderList); } catch (JSONException ex) { LOG.error(ex.toString()); apiResponse.setError(errors.parseError); return apiResponse; } } else { apiResponse = response; } return apiResponse; } private ApiResponse enterOrder(String type, CurrencyPair pair, double amount, double rate) { ApiResponse apiResponse = new ApiResponse(); String order_id = ""; String url = API_BASE_URL; String method = API_TRADE; boolean isGet = false; HashMap<String, String> query_args = new HashMap<>(); query_args.put("pair", pair.toStringSep()); query_args.put("type", type); query_args.put("rate", Double.toString(rate)); query_args.put("amount", Double.toString(amount)); ApiResponse response = getQuery(url, method, query_args, true, isGet); if (response.isPositive()) { JSONObject httpAnswerJson = (JSONObject) response.getResponseObject(); JSONObject dataJson = (JSONObject) httpAnswerJson.get("return"); order_id = "" + (long) dataJson.get("order_id"); apiResponse.setResponseObject(order_id); } else { apiResponse = response; } return apiResponse; } @Override public ApiError getErrorByCode(int code) { return null; } @Override public ApiResponse isOrderActive(String id) { ApiResponse existResponse = new ApiResponse(); ApiResponse orderDetailResponse = getOrderDetail(id); if (orderDetailResponse.isPositive()) { Order order = (Order) orderDetailResponse.getResponseObject(); existResponse.setResponseObject(true); } else { ApiError err = orderDetailResponse.getError(); if (err.getCode() == 4564) { //Cannot find order existResponse.setResponseObject(false); } else { existResponse.setError(err); LOG.error(existResponse.getError().toString()); } } return existResponse; } @Override public String query(String base, String method, AbstractMap<String, String> args, boolean needAuth, boolean isGet) { String queryResult = TOKEN_BAD_RETURN; //Will return this string in case it fails if (exchange.getLiveData().isConnected()) { if (exchange.isFree()) { exchange.setBusy(); queryResult = service.executeQuery(base, method, args, needAuth, isGet); exchange.setFree(); } else { //Another thread is probably executing a query. Init the retry procedure long sleeptime = Settings.RETRY_SLEEP_INCREMENT * 1; int counter = 0; long startTimeStamp = System.currentTimeMillis(); LOG.debug(method + " blocked, another call is being processed "); boolean exit = false; do { counter++; sleeptime = counter * Settings.RETRY_SLEEP_INCREMENT; //Increase sleep time sleeptime += (int) (Math.random() * 200) - 100;// Add +- 100 ms random to facilitate competition LOG.debug( "Retrying for the " + counter + " time. Sleep for " + sleeptime + "; Method=" + method); try { Thread.sleep(sleeptime); } catch (InterruptedException e) { LOG.error(e.toString()); } //Try executing the call if (exchange.isFree()) { LOG.debug("Finally the exchange is free, executing query after " + counter + " attempt. Method=" + method); exchange.setBusy(); queryResult = service.executeQuery(base, method, args, needAuth, isGet); exchange.setFree(); break; //Exit loop } else { LOG.debug("Exchange still busy : " + counter + " .Will retry soon; Method=" + method); exit = false; } if (System.currentTimeMillis() - startTimeStamp >= Settings.TIMEOUT_QUERY_RETRY) { exit = true; LOG.error( "Method=" + method + " failed too many times and timed out. attempts = " + counter); } } while (!exit); } } else { LOG.error("The bot will not execute the query, there is no connection with" + exchange.getName()); queryResult = TOKEN_BAD_RETURN; } return queryResult; } @Override public ApiResponse clearOrders(CurrencyPair pair) { //Since there is no API entry point for that, this call will iterate over actie ApiResponse toReturn = new ApiResponse(); boolean ok = true; ApiResponse activeOrdersResponse = getActiveOrders(); if (activeOrdersResponse.isPositive()) { ArrayList<Order> orderList = (ArrayList<Order>) activeOrdersResponse.getResponseObject(); for (int i = 0; i < orderList.size(); i++) { Order tempOrder = orderList.get(i); ApiResponse deleteOrderResponse = cancelOrder(tempOrder.getId(), null); if (deleteOrderResponse.isPositive()) { boolean deleted = (boolean) deleteOrderResponse.getResponseObject(); if (deleted) { LOG.warn("Order " + tempOrder.getId() + " deleted succesfully"); } else { LOG.warn("Could not delete order " + tempOrder.getId() + ""); ok = false; } } else { LOG.error(deleteOrderResponse.getError().toString()); } try { Thread.sleep(500); } catch (InterruptedException ex) { LOG.error(ex.toString()); } } toReturn.setResponseObject(ok); } else { LOG.error(activeOrdersResponse.getError().toString()); toReturn.setError(activeOrdersResponse.getError()); return toReturn; } return toReturn; } @Override public String getUrlConnectionCheck() { return checkConnectionUrl; } @Override public void setKeys(ApiKeys keys) { this.keys = keys; } @Override public void setExchange(Exchange exchange) { this.exchange = exchange; } @Override public void setApiBaseUrl(String apiBaseUrl) { throw new UnsupportedOperationException("Not supported yet."); } @Override public ApiResponse getLastTrades(CurrencyPair pair) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } @Override public ApiResponse getLastTrades(CurrencyPair pair, long startTime) { throw new UnsupportedOperationException("Not supported yet."); } @Override public ApiResponse getOrderBook(CurrencyPair pair) { throw new UnsupportedOperationException("BtceWrapper.getOrderBook() not implemented yet."); } /* Service implementation */ private class BtceService implements ServiceInterface { protected ApiKeys keys; public BtceService(ApiKeys keys) { this.keys = keys; } private BtceService() { //Used for ticker, does not require auth } @Override public String executeQuery(String base, String method, AbstractMap<String, String> args, boolean needAuth, boolean isGet) { String url = base + method; String answer = null; String signature = ""; String post_data = ""; boolean httpError = false; HttpsURLConnection connection = null; try { // add nonce and build arg list if (needAuth) { args.put("nonce", createNonce()); args.put("method", method); post_data = TradeUtils.buildQueryString(args, ENCODING); // args signature with apache cryptografic tools String toHash = post_data; signature = TradeUtils.signRequest(keys.getPrivateKey(), toHash, SIGN_HASH_FUNCTION, ENCODING); } // build URL URL queryUrl; if (needAuth) { queryUrl = new URL(base); } else { queryUrl = new URL(url); } connection = (HttpsURLConnection) queryUrl.openConnection(); connection.setRequestMethod("POST"); // create and setup a HTTP connection connection.setRequestProperty("Content-type", "application/x-www-form-urlencoded"); connection.setRequestProperty("User-Agent", Settings.APP_NAME); if (needAuth) { connection.setRequestProperty("Key", keys.getApiKey()); connection.setRequestProperty("Sign", signature); } connection.setDoOutput(true); connection.setDoInput(true); //Read the response DataOutputStream os = new DataOutputStream(connection.getOutputStream()); os.writeBytes(post_data); os.close(); BufferedReader br = null; boolean toLog = false; if (connection.getResponseCode() >= 400) { httpError = true; br = new BufferedReader(new InputStreamReader((connection.getErrorStream()))); toLog = true; } else { br = new BufferedReader(new InputStreamReader((connection.getInputStream()))); } String output; if (httpError) { LOG.error("Post Data: " + post_data); } LOG.info("Query to :" + base + "(method=" + method + ")" + " , HTTP response : \n"); //do not log unless is error > 400 while ((output = br.readLine()) != null) { LOG.info(output); answer += output; } /* if (httpError) { JSONParser parser = new JSONParser(); try { JSONObject obj2 = (JSONObject) (parser.parse(answer)); answer = (String) obj2.get(TOKEN_ERR); } catch (ParseException ex) { LOG.error(ex.toStringSep()); } } */ } //Capture Exceptions catch (IllegalStateException ex) { LOG.error(ex.toString()); return null; } catch (NoRouteToHostException | UnknownHostException ex) { //Global.BtceExchange.setConnected(false); LOG.error(ex.toString()); answer = TOKEN_BAD_RETURN; } catch (IOException ex) { LOG.error(ex.toString()); return null; } finally { //close the connection, set all objects to null connection.disconnect(); connection = null; } return answer; } } }