Skip to content

Commit

Permalink
feat: Add binance http client
Browse files Browse the repository at this point in the history
  • Loading branch information
namjug-kim committed Jun 27, 2019
1 parent c7a79a2 commit d901a5a
Show file tree
Hide file tree
Showing 17 changed files with 352 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package com.njkim.reactivecrypto.binance

import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.JsonSerializer
Expand All @@ -30,7 +29,6 @@ import com.njkim.reactivecrypto.core.common.model.currency.Currency
import com.njkim.reactivecrypto.core.common.model.currency.CurrencyPair
import com.njkim.reactivecrypto.core.common.util.toEpochMilli
import mu.KotlinLogging
import java.io.IOException
import java.math.BigDecimal
import java.time.Instant
import java.time.ZoneId
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2019 namjug-kim
*
* LINE Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package com.njkim.reactivecrypto.binance.http

import com.njkim.reactivecrypto.core.common.model.account.Balance
import com.njkim.reactivecrypto.core.http.AccountOperation
import com.quantinel.remarketer.strategy.sdk.binance.BinanceRawPrivateHttpClient
import reactor.core.publisher.Flux

class BinanceAccountOperation(
accessKey: String,
secretKey: String,
private val binanceRawPrivateHttpClient: BinanceRawPrivateHttpClient
) : AccountOperation(accessKey, secretKey) {
override fun balance(): Flux<Balance> {
return binanceRawPrivateHttpClient
.userData()
.account()
.flatMapIterable { it.balances }
.map {
Balance(
it.asset,
it.free,
it.locked
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2019 namjug-kim
*
* LINE Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package com.njkim.reactivecrypto.binance.http

import com.njkim.reactivecrypto.core.http.ExchangeHttpClient
import com.njkim.reactivecrypto.core.http.PrivateHttpClient
import com.njkim.reactivecrypto.core.http.PublicHttpClient
import com.quantinel.remarketer.strategy.sdk.binance.BinanceRawHttpClient

class BinanceHttpClient : ExchangeHttpClient() {
override fun privateApi(accessKey: String, secretKey: String): PrivateHttpClient {
val binanceRawPrivateHttpClient = BinanceRawHttpClient()
.private(accessKey, secretKey)
return BinancePrivateHttpClient(accessKey, secretKey, binanceRawPrivateHttpClient)
}

override fun publicApi(): PublicHttpClient {
TODO("not implemented")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* Copyright 2019 namjug-kim
*
* LINE Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package com.njkim.reactivecrypto.binance.http

import com.njkim.reactivecrypto.core.common.model.ExchangeVendor
import com.njkim.reactivecrypto.core.common.model.currency.CurrencyPair
import com.njkim.reactivecrypto.core.common.model.order.*
import com.njkim.reactivecrypto.core.common.model.paging.CursorPageable
import com.njkim.reactivecrypto.core.common.model.paging.FirstPageRequest
import com.njkim.reactivecrypto.core.common.model.paging.Page
import com.njkim.reactivecrypto.core.common.model.paging.Pageable
import com.njkim.reactivecrypto.core.http.OrderOperation
import com.quantinel.remarketer.strategy.sdk.binance.BinanceRawPrivateHttpClient
import reactor.core.publisher.Mono
import java.math.BigDecimal

class BinanceOrderOperation(
accessKey: String,
secretKey: String,
private val binanceRawPrivateHttpClient: BinanceRawPrivateHttpClient
) : OrderOperation(accessKey, secretKey) {
override fun orderStatus(orderId: String): Mono<OrderStatus> {
val splits = orderId.split("-")
val pair = CurrencyPair.parse(splits[0])
val binanceOrderId = splits[1].toLong()

return binanceRawPrivateHttpClient.userData()
.order(pair, binanceOrderId)
.map {
OrderStatus(
createOrderId(it.symbol, it.orderId),
it.status.toOrderStatusType(),
if (it.side == TradeSideType.BUY) OrderSideType.BID else OrderSideType.ASK,
it.symbol,
it.price,
it.origQty,
it.executedQty,
createDateTime = it.time
)
}
}

override fun limitOrder(
pair: CurrencyPair,
tradeSideType: TradeSideType,
price: BigDecimal,
quantity: BigDecimal
): Mono<OrderPlaceResult> {
return binanceRawPrivateHttpClient.trade()
.limitOrder(pair, tradeSideType, quantity, TimeInForceType.GTC, price)
.map { OrderPlaceResult("${it.orderId}") }
}

override fun marketOrder(
pair: CurrencyPair,
tradeSideType: TradeSideType,
quantity: BigDecimal
): Mono<OrderPlaceResult> {
return binanceRawPrivateHttpClient.trade()
.marketOrder(pair, tradeSideType, quantity)
.map { OrderPlaceResult(createOrderId(it.symbol, it.orderId)) }
}

/**
* @param orderId $currencyPair-$originOrderId
*/
override fun cancelOrder(orderId: String): Mono<OrderCancelResult> {
val splits = orderId.split("-")
val pair = CurrencyPair.parse(splits[0])
val binanceOrderId = splits[1].toLong()

return binanceRawPrivateHttpClient.trade()
.cancelOrder(pair, binanceOrderId)
.map { OrderCancelResult() }
}

override fun openOrders(pair: CurrencyPair, pageable: Pageable): Mono<Page<OrderStatus>> {
return binanceRawPrivateHttpClient
.userData()
.openOrders(pair)
.map {
OrderStatus(
createOrderId(it.symbol, it.orderId),
it.status.toOrderStatusType(),
if (it.side == TradeSideType.BUY) OrderSideType.BID else OrderSideType.ASK,
it.symbol,
it.price,
it.origQty,
it.executedQty,
createDateTime = it.time
)
}
.buffer()
.next()
.map {
Page(
it,
FirstPageRequest(0)
)
}
}

override fun tradeHistory(pair: CurrencyPair, pageable: Pageable): Mono<Page<TickData>> {
val cursorPageable = when (pageable) {
is FirstPageRequest -> pageable.toCursorPageable()
is CursorPageable -> pageable.next() as CursorPageable
else -> throw UnsupportedOperationException("not allow ${pageable.javaClass.simpleName}")
}

return binanceRawPrivateHttpClient
.userData()
.myTrades(pair, fromId = cursorPageable.cursor?.toLong())
.map {
TickData(
"${it.id}",
it.time,
it.price,
it.qty,
it.symbol,
ExchangeVendor.BINANCE,
if (it.isBuyer) TradeSideType.BUY else TradeSideType.SELL
)
}
.buffer()
.next()
.map {
Page(
it,
CursorPageable(cursorPageable.cursor, it.lastOrNull()?.uniqueId, 500)
)
}
}

private fun createOrderId(pair: CurrencyPair, orderId: Long): String {
return "$pair-$orderId"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2019 namjug-kim
*
* LINE Corporation licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

package com.njkim.reactivecrypto.binance.http

import com.njkim.reactivecrypto.core.http.AccountOperation
import com.njkim.reactivecrypto.core.http.OrderOperation
import com.njkim.reactivecrypto.core.http.PrivateHttpClient
import com.quantinel.remarketer.strategy.sdk.binance.BinanceRawPrivateHttpClient

class BinancePrivateHttpClient(
accessKey: String,
secretKey: String,
private val binanceRawPrivateHttpClient: BinanceRawPrivateHttpClient
) : PrivateHttpClient(accessKey, secretKey) {
override fun account(): AccountOperation {
return BinanceAccountOperation(accessKey, secretKey, binanceRawPrivateHttpClient)
}

override fun order(): OrderOperation {
return BinanceOrderOperation(accessKey, secretKey, binanceRawPrivateHttpClient)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import org.springframework.http.client.reactive.ReactorClientHttpConnector
import org.springframework.http.codec.json.Jackson2JsonDecoder
import org.springframework.http.codec.json.Jackson2JsonEncoder
import org.springframework.util.MimeTypeUtils
import org.springframework.web.reactive.function.client.ExchangeFilterFunctions
import org.springframework.web.reactive.function.client.ExchangeStrategies
import org.springframework.web.reactive.function.client.WebClient
import reactor.netty.http.client.HttpClient
Expand All @@ -37,8 +36,6 @@ class BinanceRawHttpClient(
}

private fun createDefaultWebClientBuilder(): WebClient.Builder {
ExchangeFilterFunctions.basicAuthentication()

val strategies = ExchangeStrategies.builder()
.codecs { clientCodecConfigurer ->
clientCodecConfigurer.defaultCodecs()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,41 @@ class BinanceRawUserDataOperator internal constructor(private val webClient: Web
.bodyToFlux()
}

/**
* Check an order's status.
*
* Weight: 1
*
* Either orderId or origClientOrderId must be sent.
* For some historical orders cummulativeQuoteQty will be < 0, meaning the data is not available at this time.
*/
fun order(
symbol: CurrencyPair,
orderId: Long? = null,
origClientOrderId: String? = null,
recvWindow: Long = 5000
): Mono<BinanceOrderInfoResponse> {
val request = mapOf(
"symbol" to symbol,
"recvWindow" to recvWindow,
"timestamp" to Instant.now().toEpochMilli()
).toMutableMap()
orderId?.let { request["orderId"] = orderId }
origClientOrderId?.let { request["origClientOrderId"] = origClientOrderId }

val convertedRequest = BinanceJsonObjectMapper.instance.convertValue<Map<String, Any>>(request)
.toMultiValueMap()

return webClient.get()
.uri {
it.path("/api/v3/order")
.queryParams(convertedRequest)
.build()
}
.retrieve()
.bodyToMono()
}

/**
* Get trades for a specific account and symbol.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.njkim.reactivecrypto.binance.model


import com.fasterxml.jackson.annotation.JsonProperty
import com.njkim.reactivecrypto.core.common.model.currency.Currency
import java.math.BigDecimal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package com.njkim.reactivecrypto.binance.model


import com.fasterxml.jackson.annotation.JsonProperty
import com.njkim.reactivecrypto.core.common.model.currency.CurrencyPair
import com.njkim.reactivecrypto.core.common.model.order.OrderStatusType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@

package com.njkim.reactivecrypto.binance.model


import com.fasterxml.jackson.annotation.JsonProperty
import com.njkim.reactivecrypto.core.common.model.currency.CurrencyPair
import com.njkim.reactivecrypto.core.common.model.order.OrderStatusType
import com.njkim.reactivecrypto.core.common.model.order.OrderType
import com.njkim.reactivecrypto.core.common.model.order.TimeInForceType
import com.njkim.reactivecrypto.core.common.model.order.TradeSideType
Expand All @@ -30,7 +28,7 @@ data class BinanceOrderInfoResponse(
@JsonProperty("symbol")
val symbol: CurrencyPair,
@JsonProperty("orderId")
val orderId: Int,
val orderId: Long,
@JsonProperty("clientOrderId")
val clientOrderId: String,
@JsonProperty("price")
Expand All @@ -42,7 +40,7 @@ data class BinanceOrderInfoResponse(
@JsonProperty("cummulativeQuoteQty")
val cummulativeQuoteQty: BigDecimal,
@JsonProperty("status")
val status: OrderStatusType,
val status: BinanceOrderStatusType,
@JsonProperty("timeInForce")
val timeInForce: TimeInForceType,
@JsonProperty("type")
Expand Down

0 comments on commit d901a5a

Please sign in to comment.