diff --git a/.gitignore b/.gitignore index eb5a316..a0ed885 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,31 @@ target +### Java template +*.class + +*.iml + +.idea + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/ + +/src/main/resources/application-local.properties + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +### Maven template +target/ diff --git a/README.md b/README.md index 9252965..7153ae5 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,58 @@ -java-api-streaming +FX-OANDA-Streaming ================== -A simple demo app in Java for getting streaming rates using OANDA -API. This example uses [apache httpcomponents](http://hc.apache.org/httpcomponents-client-ga/) for https -connections and [json-simple](https://code.google.com/p/json-simple/) for json decoding. +A simple demo app in Java 8 using Spring Boot for getting streaming rates using OANDA +API. This example uses [Spring RestTemplate](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/client/RestTemplate.html) for https +connections. ### Setup Clone this repo to the location of your choice. Modify the following variables in -[JavaApiStreaming.java](src/main/java/JavaApiStreaming.java): +[application.properites](src/main/resources/application.properties): domain access_token account_id - instruments + instruments + +Make sure you separate the instruments by comma (e.g: EUR_USD,USD_JPY,EUR_JPY). + Maven is used for building. Install from http://maven.apache.org/download.cgi. -On Ubuntu, you can run `sudo apt-get install maven`. +On Ubuntu, you can run `sudo apt-get install maven`. On Mac using brew `brew install maven` To create and execute the jar file, run - mvn package - java -jar target/streaming-example-java-1.jar + mvn clean package + java -jar target/oanda.streaming-0.0.1-SNAPSHOT.jar ### Sample Output - EUR_USD - 2014-03-21T17:56:09.932922Z - 1.37912 - 1.37923 - ------- - USD_CAD - 2014-03-21T17:56:20.776248Z - 1.12011 - 1.12029 - ------- - USD_JPY - 2014-03-21T17:56:13.668154Z - 102.262 - 102.275 + 2016-04-10 14:28:57.068 INFO 84369 --- [ restartedMain] o.s.b.d.a.OptionalLiveReloadServer : LiveReload server is running on port 35729 + 2016-04-10 14:28:57.121 INFO 84369 --- [ restartedMain] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup + 2016-04-10 14:28:57.132 INFO 84369 --- [ restartedMain] o.s.c.support.DefaultLifecycleProcessor : Starting beans in phase 0 + 2016-04-10 14:28:57.305 INFO 84369 --- [ restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http) + 2016-04-10 14:28:58.809 INFO 84369 --- [ restartedMain] i.c.fx.oanda.streaming.FXOANDAStreaming : { + "instrument" : "EUR_USD", + "bid" : 1.13929, + "ask" : 1.14029, + "time" : "2016-04-08T21:08:58.499" + } + 2016-04-10 14:28:58.813 INFO 84369 --- [ restartedMain] i.c.fx.oanda.streaming.FXOANDAStreaming : { + "instrument" : "USD_JPY", + "bid" : 108.028, + "ask" : 108.128, + "time" : "2016-04-08T21:09:00.391" + } + 2016-04-10 14:28:58.819 INFO 84369 --- [ restartedMain] i.c.fx.oanda.streaming.FXOANDAStreaming : { + "instrument" : "EUR_JPY", + "bid" : 123.133, + "ask" : 123.233, + "time" : "2016-04-08T21:08:59.84" + } + ### More Information diff --git a/pom.xml b/pom.xml index 8298b6a..cb9f2bb 100644 --- a/pom.xml +++ b/pom.xml @@ -1,69 +1,57 @@ - 4.0.0 - Oanda - streaming-example-java - 1 + io.cax.fx + oanda.streaming + 0.0.1-SNAPSHOT + jar + + oanda.streaming + Demo FX OANDA Streaming with Spring Boot + + + org.springframework.boot + spring-boot-starter-parent + 1.3.3.RELEASE + + + + + UTF-8 + 1.8 + - org.apache.httpcomponents - httpclient - 4.3 - compile + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-devtools + + + org.springframework.boot + spring-boot-starter-web + - com.googlecode.json-simple - json-simple - 1.1 - compile + org.springframework.boot + spring-boot-starter-test + test - org.apache.maven.plugins - maven-compiler-plugin - 2.3.2 + org.springframework.boot + spring-boot-maven-plugin - - - org.apache.maven.plugins - maven-jar-plugin - - - - true - dependency-jars/ - JavaApiStreaming - - - - - - - org.apache.maven.plugins - maven-dependency-plugin - 2.5.1 - - - copy-dependencies - package - - copy-dependencies - - - ${project.build.directory}/dependency-jars/ - - - - - + diff --git a/src/main/java/JavaApiStreaming.java b/src/main/java/JavaApiStreaming.java deleted file mode 100644 index 3bf5e57..0000000 --- a/src/main/java/JavaApiStreaming.java +++ /dev/null @@ -1,80 +0,0 @@ -import java.io.IOException; -import java.io.InputStream; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.util.Date; - -import org.apache.http.*; -import org.apache.http.client.methods.*; -import org.apache.http.impl.client.BasicResponseHandler; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.client.HttpClient; -import org.apache.http.message.BasicHeader; -import org.apache.http.util.EntityUtils; - -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; - -public class JavaApiStreaming { - public static void main (String[]args) throws IOException { - - HttpClient httpClient = HttpClientBuilder.create().build(); - - try { - - // Set these variables to whatever personal ones are preferred - String domain = "https://stream-fxpractice.oanda.com"; - String access_token = "ACCESS-TOKEN"; - String account_id = "1234567"; - String instruments = "EUR_USD,USD_JPY,EUR_JPY"; - - - HttpUriRequest httpGet = new HttpGet(domain + "/v1/prices?accountId=" + account_id + "&instruments=" + instruments); - httpGet.setHeader(new BasicHeader("Authorization", "Bearer " + access_token)); - - System.out.println("Executing request: " + httpGet.getRequestLine()); - - HttpResponse resp = httpClient.execute(httpGet); - HttpEntity entity = resp.getEntity(); - - if (resp.getStatusLine().getStatusCode() == 200 && entity != null) { - InputStream stream = entity.getContent(); - String line; - BufferedReader br = new BufferedReader(new InputStreamReader(stream)); - - while ((line = br.readLine()) != null) { - - Object obj = JSONValue.parse(line); - JSONObject tick = (JSONObject) obj; - - // unwrap if necessary - if (tick.containsKey("tick")) { - tick = (JSONObject) tick.get("tick"); - } - - // ignore heartbeats - if (tick.containsKey("instrument")) { - System.out.println("-------"); - - String instrument = tick.get("instrument").toString(); - String time = tick.get("time").toString(); - double bid = Double.parseDouble(tick.get("bid").toString()); - double ask = Double.parseDouble(tick.get("ask").toString()); - - System.out.println(instrument); - System.out.println(time); - System.out.println(bid); - System.out.println(ask); - } - } - } else { - // print error message - String responseString = EntityUtils.toString(entity, "UTF-8"); - System.out.println(responseString); - } - - } finally { - httpClient.getConnectionManager().shutdown(); - } - } -} diff --git a/src/main/java/io/cax/fx/oanda/streaming/FXOandaStreaming.java b/src/main/java/io/cax/fx/oanda/streaming/FXOandaStreaming.java new file mode 100644 index 0000000..c90c066 --- /dev/null +++ b/src/main/java/io/cax/fx/oanda/streaming/FXOandaStreaming.java @@ -0,0 +1,85 @@ +package io.cax.fx.oanda.streaming; + +import com.fasterxml.jackson.core.util.DefaultPrettyPrinter; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cax.fx.oanda.streaming.domain.Tick; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponents; +import org.springframework.web.util.UriComponentsBuilder; + +import java.io.IOException; +import java.util.Scanner; + + +@SpringBootApplication +public class FXOANDAStreaming implements CommandLineRunner{ + + Logger logger = LoggerFactory.getLogger(FXOANDAStreaming.class); + + @Value("${oanda.access_token}") + private String accessToken; + + @Value("${oanda.account_id}") + private String accountId; + + + @Value("${oanda.domain}") + private String domain; + + @Value("${oanda.instruments}") + private String instruments; + + private RestOperations restTemplate; + + public static void main (String[]args) throws IOException { + SpringApplication.run(FXOANDAStreaming.class, args); + } + + @Override + public void run(String... strings) throws Exception { + + restTemplate = new RestTemplate(); + + + UriComponents uriComponents = UriComponentsBuilder.fromUriString(domain).path("/v1/prices").queryParam("accountId",accountId).queryParam("instruments",instruments).build(); + + restTemplate.execute(uriComponents.toUriString(), + HttpMethod.GET, + clientHttpRequest -> clientHttpRequest.getHeaders().add("Authorization","Bearer " + accessToken), + clientHttpResponse -> { + try(Scanner scanner = new Scanner(clientHttpResponse.getBody(),"utf-8")){ + + while(scanner.hasNext()) formatPrintContent(scanner.nextLine()); + } + return new ResponseEntity<>(HttpStatus.OK); + }); + } + + private void formatPrintContent(String content){ + + ObjectMapper mapper = new ObjectMapper(); + + try { + + mapper.configure(DeserializationFeature.UNWRAP_ROOT_VALUE, true); + Tick tick = mapper.readValue(content,Tick.class); + logger.info(mapper.writer(new DefaultPrettyPrinter()).writeValueAsString(tick)); + + } catch (IOException e) { + logger.debug("heartbeat not processed: " + e.getMessage()); + } + + + } +} \ No newline at end of file diff --git a/src/main/java/io/cax/fx/oanda/streaming/domain/Tick.java b/src/main/java/io/cax/fx/oanda/streaming/domain/Tick.java new file mode 100644 index 0000000..dbd17d8 --- /dev/null +++ b/src/main/java/io/cax/fx/oanda/streaming/domain/Tick.java @@ -0,0 +1,68 @@ +package io.cax.fx.oanda.streaming.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonRootName; + +import java.util.Date; + +/** + * Created by cq on 10/4/16. + */ +@JsonRootName(value = "tick") +public class Tick { + + //{"tick":{"instrument":"EUR_USD","time":"2016-04-08T20:59:58.540499Z","bid":1.13929,"ask":1.14029}} + private String instrument; + + @JsonProperty("time") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss.S", locale = "ENGLISH") + private Date dateTime; + + private double bid; + + private double ask; + + public String getInstrument() { + return instrument; + } + + public void setInstrument(String instrument) { + this.instrument = instrument; + } + + public Date getDateTime() { + return dateTime; + } + + public void setDateTime(Date dateTime) { + this.dateTime = dateTime; + } + + public double getBid() { + return bid; + } + + public void setBid(double bid) { + this.bid = bid; + } + + public double getAsk() { + return ask; + } + + public void setAsk(double ask) { + this.ask = ask; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Tick{"); + sb.append("instrument='").append(instrument).append('\''); + sb.append(", dateTime=").append(dateTime); + sb.append(", bid=").append(bid); + sb.append(", ask=").append(ask); + sb.append('}'); + return sb.toString(); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..2254c61 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,4 @@ +oanda.domain= +oanda.access_token= +oanda.account_id= +oanda.instruments= \ No newline at end of file