Skip to content

Commit fc407db

Browse files
authored
Merge pull request #11 from namjug-kim/feature/add_exchange_bitmex
Add exchange bitmex
2 parents 95c0113 + 84fecb6 commit fc407db

File tree

16 files changed

+738
-11
lines changed

16 files changed

+738
-11
lines changed

README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ A Kotlin library for cryptocurrency trading.
88
### Websocket
99
Support public market feature (tickData, orderBook)
1010

11-
| Exchange | ver | doc |
12-
|----------------|--------|---|
13-
| Binance | * | [ws](https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md) |
14-
| Upbit | v1.0.3 | [ws](https://docs.upbit.com/docs/upbit-quotation-websocket) |
15-
| HuobiKorea | * | [ws](https://github.com/alphaex-api/BAPI_Docs_ko/wiki) |
16-
| Okex | v3 | [ws](https://www.okex.com/docs/en/#spot_ws-all) |
17-
| Bithumb⚠️ | - | - |
18-
| Hubi | * | [ws](https://www.hubi.com/docs/index-en.pdf) |
11+
| logo | name | ExchangeVendor | ver | doc |
12+
| --------------------------------------------------------------------------------------------------------------------- | ----------- | ---------------- |--------|---|
13+
| ![binance](https://user-images.githubusercontent.com/16334718/57194951-e5e88600-6f87-11e9-918e-74de5c58e883.jpg) | Binance | BINANCE | * | [ws](https://github.com/binance-exchange/binance-official-api-docs/blob/master/web-socket-streams.md) |
14+
| ![upbit](https://user-images.githubusercontent.com/16334718/57194949-e54fef80-6f87-11e9-85b3-67b8f82db564.jpg) | Upbit | UPBIT | v1.0.3 | [ws](https://docs.upbit.com/docs/upbit-quotation-websocket) |
15+
| ![huobi korea](https://user-images.githubusercontent.com/16334718/57194946-e4b75900-6f87-11e9-940a-08ceb98193e4.jpg) | HuobiKorea | HUOBI_KOREA | * | [ws](https://github.com/alphaex-api/BAPI_Docs_ko/wiki) |
16+
| ![okex](https://user-images.githubusercontent.com/16334718/57195022-90f93f80-6f88-11e9-8aaa-f6a515d300ae.jpg) | Okex | OKEX | v3 | [ws](https://www.okex.com/docs/en/#spot_ws-all) |
17+
| ![bithumb](https://user-images.githubusercontent.com/16334718/57194948-e54fef80-6f87-11e9-90d8-41f108789c77.jpg) | Bithumb | BITHUMB | ⚠️ | ⚠️ |
18+
| ![hubi](https://user-images.githubusercontent.com/16334718/57194945-e4b75900-6f87-11e9-8fea-889fc93a7ba4.jpg) | Hubi | HUBI | * | [ws](https://www.hubi.com/docs/index-en.pdf) |
19+
| ![bitmex](https://user-images.githubusercontent.com/16334718/57194950-e54fef80-6f87-11e9-8b54-3f2192012306.jpg) | Bitmex | BITMEX | * | [ws](https://www.bitmex.com/app/wsAPI) |
1920

2021
⚠️ : Uses endpoints that are used by the official web. This is not an official api and should be used with care.
2122

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apply plugin: 'kotlin'
2+
apply plugin: 'org.jetbrains.kotlin.jvm'
3+
4+
version '1.0-SNAPSHOT'
5+
6+
dependencies {
7+
compile project(':reactive-crypto-core')
8+
9+
compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
10+
}
11+
12+
compileKotlin {
13+
kotlinOptions.jvmTarget = "1.8"
14+
}
15+
compileTestKotlin {
16+
kotlinOptions.jvmTarget = "1.8"
17+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2019 namjug-kim
3+
*
4+
* LINE Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package com.njkim.reactivecrypto.bitmex
18+
19+
import com.fasterxml.jackson.core.JsonParser
20+
import com.fasterxml.jackson.core.JsonProcessingException
21+
import com.fasterxml.jackson.databind.DeserializationContext
22+
import com.fasterxml.jackson.databind.DeserializationFeature
23+
import com.fasterxml.jackson.databind.JsonDeserializer
24+
import com.fasterxml.jackson.databind.ObjectMapper
25+
import com.fasterxml.jackson.databind.module.SimpleModule
26+
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
27+
import com.njkim.reactivecrypto.core.common.model.currency.CurrencyPair
28+
import com.njkim.reactivecrypto.core.common.model.order.TradeSideType
29+
import com.njkim.reactivecrypto.core.common.util.CurrencyPairUtil
30+
import java.io.IOException
31+
import java.math.BigDecimal
32+
import java.time.ZonedDateTime
33+
34+
class BitmexJsonObjectMapper {
35+
companion object {
36+
val instance = BitmexJsonObjectMapper().objectMapper()
37+
}
38+
39+
private fun objectMapper(): ObjectMapper {
40+
val simpleModule = SimpleModule()
41+
42+
simpleModule.addDeserializer(ZonedDateTime::class.java, object : JsonDeserializer<ZonedDateTime>() {
43+
@Throws(IOException::class, JsonProcessingException::class)
44+
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): ZonedDateTime {
45+
return ZonedDateTime.parse(p.valueAsString)
46+
}
47+
})
48+
49+
simpleModule.addDeserializer(BigDecimal::class.java, object : JsonDeserializer<BigDecimal>() {
50+
@Throws(IOException::class, JsonProcessingException::class)
51+
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): BigDecimal {
52+
return BigDecimal.valueOf(p.valueAsDouble)
53+
}
54+
})
55+
56+
simpleModule.addDeserializer(CurrencyPair::class.java, object : JsonDeserializer<CurrencyPair>() {
57+
@Throws(IOException::class, JsonProcessingException::class)
58+
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): CurrencyPair {
59+
val parse = CurrencyPairUtil.parse(p.valueAsString)
60+
return checkNotNull(parse)
61+
}
62+
})
63+
64+
simpleModule.addDeserializer(TradeSideType::class.java, object : JsonDeserializer<TradeSideType>() {
65+
@Throws(IOException::class, JsonProcessingException::class)
66+
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): TradeSideType {
67+
val valueAsString = p.valueAsString
68+
return TradeSideType.valueOf(valueAsString.toUpperCase())
69+
}
70+
})
71+
72+
val objectMapper = ObjectMapper().registerKotlinModule()
73+
objectMapper.registerModule(simpleModule)
74+
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
75+
return objectMapper
76+
}
77+
78+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright 2019 namjug-kim
3+
*
4+
* LINE Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package com.njkim.reactivecrypto.bitmex
18+
19+
import com.fasterxml.jackson.module.kotlin.readValue
20+
import com.njkim.reactivecrypto.bitmex.model.BitmexMessageFrame
21+
import com.njkim.reactivecrypto.bitmex.model.BitmexOrderBook
22+
import com.njkim.reactivecrypto.bitmex.model.BitmexTickData
23+
import com.njkim.reactivecrypto.core.ExchangeWebsocketClient
24+
import com.njkim.reactivecrypto.core.common.model.ExchangeVendor
25+
import com.njkim.reactivecrypto.core.common.model.currency.CurrencyPair
26+
import com.njkim.reactivecrypto.core.common.model.order.OrderBook
27+
import com.njkim.reactivecrypto.core.common.model.order.TickData
28+
import com.njkim.reactivecrypto.core.common.util.toEpochMilli
29+
import mu.KotlinLogging
30+
import reactor.core.publisher.Flux
31+
import reactor.netty.http.client.HttpClient
32+
33+
34+
class BitmexWebsocketClient : ExchangeWebsocketClient {
35+
36+
private val log = KotlinLogging.logger {}
37+
38+
private val baseUri = "wss://www.bitmex.com/realtime"
39+
40+
override fun createDepthSnapshot(subscribeTargets: List<CurrencyPair>): Flux<OrderBook> {
41+
val args = subscribeTargets.map { "\"orderBook10:${it.targetCurrency}${it.baseCurrency}\"" }
42+
.joinToString(",", "[", "]")
43+
44+
val subscribeMessage = "{\"op\": \"subscribe\", \"args\": $args}"
45+
46+
return HttpClient.create()
47+
.wiretap(log.isDebugEnabled)
48+
.websocket()
49+
.uri(baseUri)
50+
.handle { inbound, outbound ->
51+
outbound.sendString(Flux.just(subscribeMessage))
52+
.then()
53+
.thenMany(inbound.receive().asString())
54+
}
55+
.filter { it.contains("\"table\":\"orderBook10\"") }
56+
.map { BitmexJsonObjectMapper.instance.readValue<BitmexMessageFrame<List<BitmexOrderBook>>>(it) }
57+
.flatMapIterable { messageFrame ->
58+
messageFrame.data.map { bitmexOrderBook ->
59+
OrderBook(
60+
"${bitmexOrderBook.symbol}${bitmexOrderBook.timestamp.toEpochMilli()}",
61+
bitmexOrderBook.symbol,
62+
bitmexOrderBook.timestamp,
63+
ExchangeVendor.BITMEX,
64+
bitmexOrderBook.getBids(),
65+
bitmexOrderBook.getAsks()
66+
)
67+
}
68+
}
69+
.doOnError { log.error(it.message, it) }
70+
}
71+
72+
override fun createTradeWebsocket(subscribeTargets: List<CurrencyPair>): Flux<TickData> {
73+
val args = subscribeTargets.map { "\"trade:${it.targetCurrency}${it.baseCurrency}\"" }
74+
.joinToString(",", "[", "]")
75+
76+
val subscribeMessage = "{\"op\": \"subscribe\", \"args\": $args}"
77+
78+
return HttpClient.create()
79+
.wiretap(log.isDebugEnabled)
80+
.websocket()
81+
.uri(baseUri)
82+
.handle { inbound, outbound ->
83+
outbound.sendString(Flux.just(subscribeMessage))
84+
.then()
85+
.thenMany(inbound.receive().asString())
86+
}
87+
.filter { it.contains("\"table\":\"trade\"") }
88+
.map { BitmexJsonObjectMapper.instance.readValue<BitmexMessageFrame<List<BitmexTickData>>>(it) }
89+
.flatMapIterable {
90+
it.data
91+
.map { hubiTickData ->
92+
TickData(
93+
hubiTickData.trdMatchID,
94+
hubiTickData.timestamp,
95+
hubiTickData.price,
96+
hubiTickData.size,
97+
hubiTickData.symbol,
98+
ExchangeVendor.BITMEX
99+
)
100+
}
101+
}
102+
.doOnError { log.error(it.message, it) }
103+
}
104+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2019 namjug-kim
3+
*
4+
* LINE Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package com.njkim.reactivecrypto.bitmex.model
18+
19+
import com.fasterxml.jackson.annotation.JsonProperty
20+
21+
data class BitmexMessageFrame<T>(
22+
@get:JsonProperty("table")
23+
val table: String,
24+
25+
@get:JsonProperty("action")
26+
val action: String,
27+
28+
@get:JsonProperty("data")
29+
val data: T
30+
)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2019 namjug-kim
3+
*
4+
* LINE Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package com.njkim.reactivecrypto.bitmex.model
18+
19+
import com.njkim.reactivecrypto.core.common.model.currency.CurrencyPair
20+
import com.njkim.reactivecrypto.core.common.model.order.OrderBookUnit
21+
import com.njkim.reactivecrypto.core.common.model.order.OrderSideType
22+
import java.math.BigDecimal
23+
import java.time.ZonedDateTime
24+
import kotlin.streams.toList
25+
26+
data class BitmexOrderBook(
27+
val symbol: CurrencyPair,
28+
val timestamp: ZonedDateTime,
29+
private val bids: List<List<String>>,
30+
private val asks: List<List<String>>
31+
) {
32+
fun getBids(): List<OrderBookUnit> {
33+
return bids.stream()
34+
.map { objects ->
35+
OrderBookUnit(
36+
BigDecimal(objects[0]),
37+
BigDecimal(objects[1]),
38+
OrderSideType.BID,
39+
null
40+
)
41+
}
42+
.toList()
43+
}
44+
45+
fun getAsks(): List<OrderBookUnit> {
46+
return asks.stream()
47+
.map { objects ->
48+
OrderBookUnit(
49+
BigDecimal(objects[0]),
50+
BigDecimal(objects[1]),
51+
OrderSideType.ASK,
52+
null
53+
)
54+
}
55+
.toList()
56+
}
57+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2019 namjug-kim
3+
*
4+
* LINE Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
17+
package com.njkim.reactivecrypto.bitmex.model
18+
19+
import com.fasterxml.jackson.annotation.JsonProperty
20+
import com.njkim.reactivecrypto.core.common.model.currency.CurrencyPair
21+
import com.njkim.reactivecrypto.core.common.model.order.TradeSideType
22+
import java.math.BigDecimal
23+
import java.time.ZonedDateTime
24+
25+
data class BitmexTickData(
26+
@get:JsonProperty("timestamp")
27+
val timestamp: ZonedDateTime,
28+
29+
@get:JsonProperty("symbol")
30+
val symbol: CurrencyPair,
31+
32+
@get:JsonProperty("side")
33+
val side: TradeSideType,
34+
35+
@get:JsonProperty("size")
36+
val size: BigDecimal,
37+
38+
@get:JsonProperty("price")
39+
val price: BigDecimal,
40+
41+
@get:JsonProperty("tickDirection")
42+
val tickDirection: String,
43+
44+
@get:JsonProperty("trdMatchID")
45+
val trdMatchID: String,
46+
47+
@get:JsonProperty("grossValue")
48+
val grossValue: BigDecimal,
49+
50+
@get:JsonProperty("homeNotional")
51+
val homeNotional: BigDecimal,
52+
53+
@get:JsonProperty("foreignNotional")
54+
val foreignNotional: BigDecimal
55+
)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Configuration>
3+
<Properties>
4+
<Property name="LOG_PATTERN">
5+
%d{yyyy-MM-dd HH:mm:ss.SSS} %5p ${hostName} --- [%15.15t] %-40.40c{1.} : %m%n%ex
6+
</Property>
7+
</Properties>
8+
9+
<Appenders>
10+
<Console name="ConsoleAppender" target="SYSTEM_OUT" follow="true">
11+
<PatternLayout pattern="${LOG_PATTERN}"/>
12+
</Console>
13+
</Appenders>
14+
15+
<Loggers>
16+
<Root level="INFO">
17+
<AppenderRef ref="ConsoleAppender"/>
18+
</Root>
19+
</Loggers>
20+
</Configuration>

0 commit comments

Comments
 (0)