Skip to content

Commit

Permalink
feat: add new exchange 'coinall'
Browse files Browse the repository at this point in the history
closes #100
  • Loading branch information
namjug-kim committed Jul 22, 2019
1 parent 920acea commit c6222e6
Show file tree
Hide file tree
Showing 9 changed files with 417 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Support public market feature (tickData, orderBook)
| ![poloniex](https://user-images.githubusercontent.com/16334718/59551277-335a0900-8fb2-11e9-9d1e-4ab2a7574148.jpg) | Poloniex | POLONIEX | * | [ws](https://docs.poloniex.com/#websocket-api) |
| ![bitstamp](https://user-images.githubusercontent.com/16334718/59565122-2c062e80-908a-11e9-8a38-6264c26aa3c2.jpg) | Bitstamp | BITSTAMP | v2 | [ws](https://www.bitstamp.net/websocket/v2/) |
| ![korbotex](https://user-images.githubusercontent.com/16334718/59919092-82e07f00-9461-11e9-869a-d801dce68b08.jpg) | Korbot EX | KOTBOTEX | v3 | [ws](https://www.okex.com/docs/en/#spot_ws-all) |
| ![coinall](https://user-images.githubusercontent.com/16334718/61628742-2077da00-acbe-11e9-95c3-27a4960616f7.png) | Coinall | COINALL | v3 | [ws](https://www.okex.com/docs/en/#spot_ws-all) |

鈿狅笍 : Uses endpoints that are used by the official web. This is not an official api and should be used with care.

Expand Down
33 changes: 33 additions & 0 deletions reactive-crypto-coinall/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.
*/

apply plugin: 'kotlin'
apply plugin: 'org.jetbrains.kotlin.jvm'

version '1.0-SNAPSHOT'

dependencies {
compile project(':reactive-crypto-okex')

compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
}

compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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.coinall

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.OrderBook
import com.njkim.reactivecrypto.core.common.model.order.TickData
import com.njkim.reactivecrypto.okex.OkexWebsocketClient
import reactor.core.publisher.Flux

/**
* docs: https://www.coinall.com/docs/en/#ws_swap-README
*/
class CoinallWebsocketClient : OkexWebsocketClient("wss://okexcomreal.bafang.com:8443/ws/v3?brokerId=68") {
override fun createDepthSnapshot(subscribeTargets: List<CurrencyPair>): Flux<OrderBook> {
return super.createDepthSnapshot(subscribeTargets)
.map { it.copy(exchangeVendor = ExchangeVendor.COINALL) }
}

override fun createTradeWebsocket(subscribeTargets: List<CurrencyPair>): Flux<TickData> {
return super.createTradeWebsocket(subscribeTargets)
.map { it.copy(exchangeVendor = ExchangeVendor.COINALL) }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* 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.coinall;

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.OrderBook;
import com.njkim.reactivecrypto.core.common.model.order.TickData;
import org.junit.Test;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;

import java.math.BigDecimal;
import java.util.Collections;

import static org.assertj.core.api.Assertions.assertThat;

public class CoinallWebsocketClientJavaTest {
@Test
public void tick_data_subscribe() {
// given
CurrencyPair targetCurrencyPair = CurrencyPair.parse("BTC", "USDT");
Flux<TickData> tickDataFlux = new CoinallWebsocketClient()
.createTradeWebsocket(Collections.singletonList(targetCurrencyPair));

// when
StepVerifier.create(tickDataFlux.limitRequest(2))
// then
.assertNext(tickData -> {
assertThat(tickData).isNotNull();
assertThat(tickData.getCurrencyPair())
.isEqualTo(targetCurrencyPair);
assertThat(tickData.getExchangeVendor())
.isEqualTo(ExchangeVendor.COINALL);
assertThat(tickData.getPrice())
.isGreaterThan(BigDecimal.ZERO);
assertThat(tickData.getQuantity())
.isGreaterThan(BigDecimal.ZERO);
})
.assertNext(tickData -> {
assertThat(tickData).isNotNull();
assertThat(tickData.getCurrencyPair())
.isEqualTo(targetCurrencyPair);
assertThat(tickData.getExchangeVendor())
.isEqualTo(ExchangeVendor.COINALL);
assertThat(tickData.getPrice())
.isGreaterThan(BigDecimal.ZERO);
assertThat(tickData.getQuantity())
.isGreaterThan(BigDecimal.ZERO);
})
.verifyComplete();
}

@Test
public void orderBook_subscribe() {
// given
CurrencyPair targetCurrencyPair = CurrencyPair.parse("BTC", "USDT");
Flux<OrderBook> orderBookFlux = new CoinallWebsocketClient()
.createDepthSnapshot(Collections.singletonList(targetCurrencyPair));

// when
StepVerifier.create(orderBookFlux.limitRequest(2))
// then
.assertNext(orderBook -> {
assertThat(orderBook).isNotNull();
assertThat(orderBook.getCurrencyPair())
.isEqualTo(targetCurrencyPair);
assertThat(orderBook.getExchangeVendor())
.isEqualTo(ExchangeVendor.COINALL);
assertThat(orderBook.getAsks())
.isNotEmpty();
assertThat(orderBook.getBids())
.isNotEmpty();

assertThat(orderBook.getAsks().get(0).getQuantity())
.isGreaterThan(BigDecimal.ZERO);
assertThat(orderBook.getBids().get(0).getQuantity())
.isGreaterThan(BigDecimal.ZERO);

assertThat(orderBook.getAsks().get(0).getPrice())
.withFailMessage("ask price must be bigger than bid price")
.isGreaterThan(orderBook.getBids().get(0).getPrice());

assertThat(orderBook.getAsks().get(0).getPrice())
.withFailMessage("asks must be sorted by price asc")
.isLessThan(orderBook.getAsks().get(1).getPrice());
assertThat(orderBook.getBids().get(0).getPrice())
.withFailMessage("bids must be sorted by price desc")
.isGreaterThan(orderBook.getBids().get(1).getPrice());
})
.assertNext(orderBook -> {
assertThat(orderBook).isNotNull();
assertThat(orderBook.getCurrencyPair())
.isEqualTo(targetCurrencyPair);
assertThat(orderBook.getExchangeVendor())
.isEqualTo(ExchangeVendor.COINALL);
assertThat(orderBook.getAsks())
.isNotEmpty();
assertThat(orderBook.getBids())
.isNotEmpty();

assertThat(orderBook.getAsks().get(0).getQuantity())
.isGreaterThan(BigDecimal.ZERO);
assertThat(orderBook.getBids().get(0).getQuantity())
.isGreaterThan(BigDecimal.ZERO);

assertThat(orderBook.getAsks().get(0).getPrice())
.withFailMessage("ask price must be bigger than bid price")
.isGreaterThan(orderBook.getBids().get(0).getPrice());

assertThat(orderBook.getAsks().get(0).getPrice())
.withFailMessage("asks must be sorted by price asc")
.isLessThan(orderBook.getAsks().get(1).getPrice());
assertThat(orderBook.getBids().get(0).getPrice())
.withFailMessage("bids must be sorted by price desc")
.isGreaterThan(orderBook.getBids().get(1).getPrice());
})
.verifyComplete();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/*
* 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.coinall

import com.njkim.reactivecrypto.core.common.model.ExchangeVendor
import com.njkim.reactivecrypto.core.common.model.currency.CurrencyPair
import com.njkim.reactivecrypto.core.common.util.toEpochMilli
import mu.KotlinLogging
import org.assertj.core.api.Assertions
import org.junit.Test
import reactor.test.StepVerifier
import java.math.BigDecimal

class CoinallWebsocketClientTest {
private val log = KotlinLogging.logger {}

@Test
fun `tick data subscribe`() {
// given
val targetCurrencyPair = CurrencyPair.parse("BTC", "USDT")
val okexTickDataFlux = CoinallWebsocketClient()
.createTradeWebsocket(listOf(targetCurrencyPair))

// when
StepVerifier.create(okexTickDataFlux.limitRequest(2))
// then
.assertNext {
Assertions.assertThat(it).isNotNull
Assertions.assertThat(it.currencyPair)
.isEqualTo(targetCurrencyPair)
Assertions.assertThat(it.exchangeVendor)
.isEqualTo(ExchangeVendor.COINALL)
Assertions.assertThat(it.price)
.isGreaterThan(BigDecimal.ZERO)
Assertions.assertThat(it.quantity)
.isGreaterThan(BigDecimal.ZERO)
}
.assertNext {
Assertions.assertThat(it).isNotNull
Assertions.assertThat(it.currencyPair)
.isEqualTo(targetCurrencyPair)
Assertions.assertThat(it.exchangeVendor)
.isEqualTo(ExchangeVendor.COINALL)
Assertions.assertThat(it.price)
.isGreaterThan(BigDecimal.ZERO)
Assertions.assertThat(it.quantity)
.isGreaterThan(BigDecimal.ZERO)
}
.verifyComplete()
}

@Test
fun `orderBook subscribe`() {
// given
val targetCurrencyPair = CurrencyPair.parse("BTC", "USDT")
val orderBookFlux = CoinallWebsocketClient()
.createDepthSnapshot(listOf(targetCurrencyPair))
var prevTimestamp = 0L

// when
StepVerifier.create(orderBookFlux.limitRequest(5))
.expectNextCount(3)
// then
.assertNext {
prevTimestamp = it.eventTime.toEpochMilli()
Assertions.assertThat(it).isNotNull
Assertions.assertThat(it.currencyPair)
.isEqualTo(targetCurrencyPair)
Assertions.assertThat(it.exchangeVendor)
.isEqualTo(ExchangeVendor.COINALL)
Assertions.assertThat(it.asks)
.isNotEmpty
Assertions.assertThat(it.bids)
.isNotEmpty

Assertions.assertThat(it.asks[0].quantity)
.isGreaterThan(BigDecimal.ZERO)
Assertions.assertThat(it.bids[0].quantity)
.isGreaterThan(BigDecimal.ZERO)

Assertions.assertThat(it.asks[0].price)
.withFailMessage("ask price must be bigger than bid price")
.isGreaterThan(it.bids[0].price)

Assertions.assertThat(it.asks[0].price)
.withFailMessage("asks must be sorted by price asc")
.isLessThan(it.asks[1].price)
Assertions.assertThat(it.bids[0].price)
.withFailMessage("bids must be sorted by price desc")
.isGreaterThan(it.bids[1].price)
}
.assertNext {
Assertions.assertThat(prevTimestamp)
.isNotEqualTo(it.eventTime.toEpochMilli())
Assertions.assertThat(it).isNotNull
Assertions.assertThat(it.currencyPair)
.isEqualTo(targetCurrencyPair)
Assertions.assertThat(it.exchangeVendor)
.isEqualTo(ExchangeVendor.COINALL)
Assertions.assertThat(it.asks)
.isNotEmpty
Assertions.assertThat(it.bids)
.isNotEmpty

Assertions.assertThat(it.asks[0].quantity)
.isGreaterThan(BigDecimal.ZERO)
Assertions.assertThat(it.bids[0].quantity)
.isGreaterThan(BigDecimal.ZERO)

Assertions.assertThat(it.asks[0].price)
.withFailMessage("ask price must be bigger than bid price")
.isGreaterThan(it.bids[0].price)

Assertions.assertThat(it.asks[0].price)
.withFailMessage("asks must be sorted by price asc")
.isLessThan(it.asks[1].price)
Assertions.assertThat(it.bids[0].price)
.withFailMessage("bids must be sorted by price desc")
.isGreaterThan(it.bids[1].price)
}
.verifyComplete()
}
}
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.coinall

import com.njkim.reactivecrypto.core.ExchangeClientFactory
import com.njkim.reactivecrypto.core.common.model.ExchangeVendor
import com.njkim.reactivecrypto.core.websocket.ExchangeWebsocketClient
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test

class ExchangeClientFactoryTest {
@Test
fun `create websocket client`() {
val exchangeWebsocketClient = ExchangeClientFactory.websocket(ExchangeVendor.COINALL)

assertThat(exchangeWebsocketClient).isNotNull
assertThat(exchangeWebsocketClient).isInstanceOf(ExchangeWebsocketClient::class.java)
assertThat(exchangeWebsocketClient).isExactlyInstanceOf(CoinallWebsocketClient::class.java)
}
}

0 comments on commit c6222e6

Please sign in to comment.