Skip to content

Commit

Permalink
added streaming api and increased version to 1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
galimru committed Dec 18, 2020
1 parent f91f7c1 commit 9f03ea1
Show file tree
Hide file tree
Showing 28 changed files with 891 additions and 52 deletions.
65 changes: 37 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Tinkoff Invest client written on Java.

## Installation

Import the library to your project using [jitpack](https://jitpack.io/#galimru/tinkoff-invest-api/1.0.0) repository
Import the library to your project using [jitpack](https://jitpack.io/#galimru/tinkoff-invest-api/1.1.0) repository

#### Gradle

Expand All @@ -27,7 +27,7 @@ repositories {
2. Add the tinkoff-invest-api library dependency

```gradle
implementation 'com.github.galimru:tinkoff-invest-api:1.0.0'
implementation 'com.github.galimru:tinkoff-invest-api:1.1.0'
```

_Note: The JitPack supports both Gradle/Maven build tools, please refer to jitpack [documentation](https://jitpack.io/#galimru/tinkoff-invest-api) if you want use Maven_
Expand All @@ -36,32 +36,41 @@ _Note: The JitPack supports both Gradle/Maven build tools, please refer to jitpa
## Dead simple example

```java
// create api client for sandbox environment
TinkoffInvestClient client = TinkoffInvestClient.create(TestConstants.TOKEN, true);
// register new sandbox broker account
client.sandbox()
.register(BrokerAccountType.TINKOFF);
// set broker account balance to $1000.55
client.sandbox()
.setCurrencyBalance(Currency.USD, BigDecimal.valueOf(1000.55));
// search figi by ticker TSLA (Tesla)
MarketInstrumentList resultList = client.market()
.searchByTicker("TSLA");
String figi = resultList.getInstruments().get(0).getFigi();
// buy 1 lot of Tesla using market order
client.orders()
.place(MarketOrder
.buy(figi)
.quantity(1));
// sell 1 lot of Tesla using limit order
client.orders()
.place(LimitOrder
.sell(figi)
.quantity(1)
.price(BigDecimal.valueOf(800.45)));
// clear all sandbox accounts
client.sandbox()
.clear();
// create api client for sandbox environment
TinkoffInvestClient client = TinkoffInvestClient.create(TestConstants.TOKEN, true);
// register new sandbox broker account
client.sandbox()
.register(BrokerAccountType.TINKOFF);
// set broker account balance to $1000.55
client.sandbox()
.setCurrencyBalance(Currency.USD, BigDecimal.valueOf(1000.55));
// search figi by ticker TSLA (Tesla)
MarketInstrumentList resultList = client.market()
.searchByTicker("TSLA");
String figi = resultList.getInstruments().get(0).getFigi();
// buy 1 lot of Tesla using market order
client.orders()
.place(MarketOrder
.buy(figi)
.quantity(1));
// sell 1 lot of Tesla using limit order
client.orders()
.place(LimitOrder
.sell(figi)
.quantity(1)
.price(BigDecimal.valueOf(800.45)));
// add streaming listener for candle events
client.streaming()
.addCandleListener(event ->
System.out.println("High: " + event.getHigh()));
// subscribe on candle events for SPCE
client.streaming()
.subscribe(CandleSubscription
.on(TestConstants.SPCE_FIGI)
.withInterval(CandleResolution.FIVE_MINUTES));
// clear all sandbox accounts
client.sandbox()
.clear();
```


Expand Down
73 changes: 62 additions & 11 deletions src/main/java/com/github/galimru/tinkoff/TinkoffInvestClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.github.galimru.tinkoff.http.Level;
import com.github.galimru.tinkoff.http.QueryConverterFactory;
import com.github.galimru.tinkoff.services.*;
import com.github.galimru.tinkoff.services.streaming.StreamingService;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import okhttp3.OkHttpClient;
Expand All @@ -17,7 +18,7 @@ public class TinkoffInvestClient {

public final static String PRODUCTION_BASE_URL = "https://api-invest.tinkoff.ru/openapi/";
public final static String SANDBOX_BASE_URL = "https://api-invest.tinkoff.ru/openapi/sandbox/";
public final static String STREAMING_BASE_URL = "wss://api-invest.tinkoff.ru/openapi/md/v1/md-openapi/ws";
public final static String STREAMING_URL = "wss://api-invest.tinkoff.ru/openapi/md/v1/md-openapi/ws";

private static final String DATE_FORMAT = "yyyy-MM-dd'T'hh:mm:ss.SSSSSSXXX";
private static final String SANDBOX_PATH = "/sandbox";
Expand All @@ -28,8 +29,10 @@ public class TinkoffInvestClient {
private final MarketService marketService;
private final OperationsService operationsService;
private final UserService userService;
private final StreamingService streamingService;

private final boolean isSandbox;
private final boolean streamingEnabled;

public static TinkoffInvestClient create(String token) {
return create(token, false);
Expand All @@ -47,6 +50,12 @@ public static Builder builder() {
}

private TinkoffInvestClient(Builder builder) {
isSandbox = builder.baseUrl
.toLowerCase()
.contains(SANDBOX_PATH);

streamingEnabled = builder.streamingEnabled;

OkHttpClient client = builder.httpClient.newBuilder()
.addInterceptor(new AuthenticationInterceptor(builder.token))
.addNetworkInterceptor(new HttpLoggingInterceptor(builder.httpLoggingLevel))
Expand All @@ -69,16 +78,18 @@ private TinkoffInvestClient(Builder builder) {
marketService = new MarketService(retrofit);
operationsService = new OperationsService(retrofit);
userService = new UserService(retrofit);

isSandbox = builder.baseUrl
.toLowerCase()
.contains(SANDBOX_PATH);
streamingService = !streamingEnabled ? null
: new StreamingService(builder.streamingHttpClient, builder.streamingUrl, builder.token);
}

public boolean isSandbox() {
return isSandbox;
}

public boolean isStreamingEnabled() {
return streamingEnabled;
}

public SandboxService sandbox() {
if (!isSandbox) {
throw new IllegalStateException("Cannot access sandbox service in production mode");
Expand Down Expand Up @@ -106,18 +117,33 @@ public UserService user() {
return userService;
}

public StreamingService streaming() {
if (!streamingEnabled) {
throw new IllegalStateException("Streaming operations disabled");
}
return streamingService;
}

public static class Builder {

private OkHttpClient httpClient = new OkHttpClient();
private String baseUrl = PRODUCTION_BASE_URL;
private String baseUrl;
private String streamingUrl;
private String token;
private Level httpLoggingLevel = Level.NONE;
private OkHttpClient httpClient;
private OkHttpClient streamingHttpClient;
private Boolean streamingEnabled;
private Level httpLoggingLevel;

public Builder withBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}

public Builder withStreamingUrl(String streamingUrl) {
this.streamingUrl = streamingUrl;
return this;
}

public Builder withToken(String token) {
this.token = token;
return this;
Expand All @@ -128,16 +154,41 @@ public Builder withHttpClient(OkHttpClient httpClient) {
return this;
}

public Builder withStreamingHttpClient(OkHttpClient streamingHttpClient) {
this.streamingHttpClient = streamingHttpClient;
return this;
}

public Builder withStreamingEnabled(Boolean streamingEnabled) {
this.streamingEnabled = streamingEnabled;
return this;
}

public Builder withHttpLoggingLevel(Level httpLoggingLevel) {
this.httpLoggingLevel = httpLoggingLevel;
return this;
}

public TinkoffInvestClient build() {
Objects.requireNonNull(baseUrl, "baseUrl is null");
if (baseUrl == null) {
baseUrl = PRODUCTION_BASE_URL;
}
if (streamingUrl == null) {
streamingUrl = STREAMING_URL;
}
if (httpClient == null) {
httpClient = new OkHttpClient();
}
if (streamingHttpClient == null) {
streamingHttpClient = new OkHttpClient();
}
if (httpLoggingLevel == null) {
httpLoggingLevel = Level.NONE;
}
if (streamingEnabled == null) {
streamingEnabled = Boolean.TRUE;
}
Objects.requireNonNull(token, "token is null");
Objects.requireNonNull(httpClient, "httpClient is null");
Objects.requireNonNull(httpLoggingLevel, "httpLoggingLevel is null");
return new TinkoffInvestClient(this);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package com.github.galimru.tinkoff.http;

import com.github.galimru.tinkoff.utils.HttpUtil;
import okhttp3.Interceptor;
import okhttp3.Response;

import java.io.IOException;

public class AuthenticationInterceptor implements Interceptor {

private final static String AUTHORIZATION_HEADER = "Authorization";
private final static String BEARER_PREFIX = "Bearer";
private final static String WHITESPACE = " ";

private final String token;

public AuthenticationInterceptor(String token) {
Expand All @@ -19,8 +16,8 @@ public AuthenticationInterceptor(String token) {

@Override
public Response intercept(Chain chain) throws IOException {
String bearerToken = BEARER_PREFIX + WHITESPACE + token;
String bearerToken = HttpUtil.BEARER_PREFIX + token;
return chain.proceed(chain.request().newBuilder()
.addHeader(AUTHORIZATION_HEADER, bearerToken).build());
.addHeader(HttpUtil.AUTHORIZATION_HEADER, bearerToken).build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@

public enum CandleResolution {
@SerializedName("1min")
MIN_1,
ONE_MINUTE,
@SerializedName("2min")
MIN_2,
TWO_MINUTES,
@SerializedName("3min")
MIN_3,
THREE_MINUTES,
@SerializedName("5min")
MIN_5,
FIVE_MINUTES,
@SerializedName("10min")
MIN_10,
TEN_MINUTES,
@SerializedName("15min")
MIN_15,
FIFTEEN_MINUTES,
@SerializedName("30min")
MIN_30,
THIRTY_MINUTES,
@SerializedName("hour")
HOUR,
@SerializedName("day")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.github.galimru.tinkoff.json.streaming;

import com.github.galimru.tinkoff.json.market.CandleResolution;
import com.google.gson.annotations.SerializedName;

import java.math.BigDecimal;
import java.util.Date;

public class CandleEvent {

private String figi;
private CandleResolution interval;
@SerializedName("o")
private BigDecimal open;
@SerializedName("c")
private BigDecimal close;
@SerializedName("h")
private BigDecimal high;
@SerializedName("l")
private BigDecimal low;
@SerializedName("v")
private Integer volume;
private Date time;

public String getFigi() {
return figi;
}

public void setFigi(String figi) {
this.figi = figi;
}

public CandleResolution getInterval() {
return interval;
}

public void setInterval(CandleResolution interval) {
this.interval = interval;
}

public BigDecimal getOpen() {
return open;
}

public void setOpen(BigDecimal open) {
this.open = open;
}

public BigDecimal getClose() {
return close;
}

public void setClose(BigDecimal close) {
this.close = close;
}

public BigDecimal getHigh() {
return high;
}

public void setHigh(BigDecimal high) {
this.high = high;
}

public BigDecimal getLow() {
return low;
}

public void setLow(BigDecimal low) {
this.low = low;
}

public Integer getVolume() {
return volume;
}

public void setVolume(Integer volume) {
this.volume = volume;
}

public Date getTime() {
return time;
}

public void setTime(Date time) {
this.time = time;
}
}
Loading

0 comments on commit 9f03ea1

Please sign in to comment.