diff --git a/pom.xml b/pom.xml index 405079c72f..ec7bc6a7de 100644 --- a/pom.xml +++ b/pom.xml @@ -50,6 +50,7 @@ xchange-bitcointoyou xchange-bitfinex xchange-bitflyer + xchange-bitget xchange-bithumb xchange-bitmex xchange-bitso diff --git a/xchange-bitget/.gitignore b/xchange-bitget/.gitignore new file mode 100644 index 0000000000..e0acc9b7bf --- /dev/null +++ b/xchange-bitget/.gitignore @@ -0,0 +1,5 @@ +# private properties for idea rest client +http-client.private.env.json + +# private properties for integration tests +integration-test.env.properties \ No newline at end of file diff --git a/xchange-bitget/README.md b/xchange-bitget/README.md new file mode 100644 index 0000000000..f728ba8eda --- /dev/null +++ b/xchange-bitget/README.md @@ -0,0 +1,21 @@ +## Using IntelliJ Idea HTTP client + +There are *.http files stored in `src/test/resources/rest` that can be used with IntelliJ Idea HTTP Client. + +Some requests need authorization, so the api credentials have to be stored in `http-client.private.env.json` in module's root. Sample content can be found in `example.http-client.private.env.json` + +> [!CAUTION] +> Never commit your api credentials to the repository! + + +[HTTP Client documentation](https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html) + +## Running integration tests that require API keys + +Integration tests that require API keys read them from environment variables. They can be defined in `integration-test.env.properties`. Sample content can be found in `example.integration-test.env.properties`. + +If no keys are provided the integration tests that need them are skipped. + +> [!CAUTION] +> Never commit your api credentials to the repository! + diff --git a/xchange-bitget/example.http-client.private.env.json b/xchange-bitget/example.http-client.private.env.json new file mode 100644 index 0000000000..6a0f22d976 --- /dev/null +++ b/xchange-bitget/example.http-client.private.env.json @@ -0,0 +1,7 @@ +{ + "default": { + "api_key": "change_me", + "api_secret": "change_me", + "api_passphrase": "change_me" + } +} \ No newline at end of file diff --git a/xchange-bitget/example.integration-test.env.properties b/xchange-bitget/example.integration-test.env.properties new file mode 100644 index 0000000000..9bd1822a2d --- /dev/null +++ b/xchange-bitget/example.integration-test.env.properties @@ -0,0 +1,3 @@ +apiKey=change_me +secretKey=change_me +passphrase=change_me diff --git a/xchange-bitget/http-client.env.json b/xchange-bitget/http-client.env.json new file mode 100644 index 0000000000..82e88360d1 --- /dev/null +++ b/xchange-bitget/http-client.env.json @@ -0,0 +1,5 @@ +{ + "default": { + "api_host": "https://api.bitget.com" + } +} \ No newline at end of file diff --git a/xchange-bitget/lombok.config b/xchange-bitget/lombok.config new file mode 100644 index 0000000000..22b090cc4b --- /dev/null +++ b/xchange-bitget/lombok.config @@ -0,0 +1,2 @@ +lombok.equalsAndHashCode.callSuper = call +lombok.tostring.callsuper = call \ No newline at end of file diff --git a/xchange-bitget/pom.xml b/xchange-bitget/pom.xml new file mode 100644 index 0000000000..ab88f4ced9 --- /dev/null +++ b/xchange-bitget/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + org.knowm.xchange + xchange-parent + 5.2.1-SNAPSHOT + + + xchange-bitget + + XChange Bitget + XChange implementation for the Bitget Exchange + + http://knowm.org/open-source/xchange/ + 2012 + + + Knowm Inc. + http://knowm.org/open-source/xchange/ + + + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + ${version.fasterxml} + + + + org.junit.jupiter + junit-jupiter-engine + test + + + + org.knowm.xchange + xchange-core + ${project.version} + + + + org.mockito + mockito-junit-jupiter + test + + + + org.wiremock + wiremock + test + + + + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + integration-test.env.properties + + + + + + + + diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/Bitget.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/Bitget.java new file mode 100644 index 0000000000..2ccacc26cc --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/Bitget.java @@ -0,0 +1,50 @@ +package org.knowm.xchange.bitget; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import java.io.IOException; +import java.util.List; +import org.knowm.xchange.bitget.dto.BitgetException; +import org.knowm.xchange.bitget.dto.BitgetResponse; +import org.knowm.xchange.bitget.dto.marketdata.BitgetCoinDto; +import org.knowm.xchange.bitget.dto.marketdata.BitgetMarketDepthDto; +import org.knowm.xchange.bitget.dto.marketdata.BitgetServerTime; +import org.knowm.xchange.bitget.dto.marketdata.BitgetSymbolDto; +import org.knowm.xchange.bitget.dto.marketdata.BitgetTickerDto; + +@Path("") +@Produces(MediaType.APPLICATION_JSON) +public interface Bitget { + + @GET + @Path("api/v2/public/time") + BitgetResponse serverTime() throws IOException, BitgetException; + + + @GET + @Path("api/v2/spot/public/coins") + BitgetResponse> coins(@QueryParam("coin") String coin) throws IOException, BitgetException; + + + @GET + @Path("api/v2/spot/public/symbols") + BitgetResponse> symbols(@QueryParam("symbol") String symbol) + throws IOException, BitgetException; + + + @GET + @Path("api/v2/spot/market/tickers") + BitgetResponse> tickers(@QueryParam("symbol") String symbol) + throws IOException, BitgetException; + + + @GET + @Path("api/v2/spot/market/orderbook") + BitgetResponse orderbook(@QueryParam("symbol") String symbol) + throws IOException, BitgetException; + + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/BitgetAdapters.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/BitgetAdapters.java new file mode 100644 index 0000000000..61ab482b91 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/BitgetAdapters.java @@ -0,0 +1,234 @@ +package org.knowm.xchange.bitget; + +import java.math.BigDecimal; +import java.time.Instant; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import lombok.experimental.UtilityClass; +import org.knowm.xchange.bitget.dto.account.BitgetBalanceDto; +import org.knowm.xchange.bitget.dto.marketdata.BitgetMarketDepthDto; +import org.knowm.xchange.bitget.dto.marketdata.BitgetSymbolDto; +import org.knowm.xchange.bitget.dto.marketdata.BitgetSymbolDto.Status; +import org.knowm.xchange.bitget.dto.marketdata.BitgetTickerDto; +import org.knowm.xchange.bitget.dto.trade.BitgetOrderInfoDto; +import org.knowm.xchange.bitget.dto.trade.BitgetOrderInfoDto.BitgetOrderStatus; +import org.knowm.xchange.bitget.dto.trade.BitgetPlaceOrderDto; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.Order.OrderStatus; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.account.Balance; +import org.knowm.xchange.dto.account.Wallet; +import org.knowm.xchange.dto.marketdata.OrderBook; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.dto.meta.InstrumentMetaData; +import org.knowm.xchange.dto.trade.LimitOrder; +import org.knowm.xchange.dto.trade.MarketOrder; +import org.knowm.xchange.instrument.Instrument; + +@UtilityClass +public class BitgetAdapters { + + private final Map SYMBOL_TO_CURRENCY_PAIR = new HashMap<>(); + + + public CurrencyPair toCurrencyPair(String symbol) { + return SYMBOL_TO_CURRENCY_PAIR.get(symbol); + } + + + public String toString(Instrument instrument) { + return instrument == null ? null : instrument.getBase().toString() + instrument.getCounter().toString(); + } + + + public String toString(Currency currency) { + return Optional.ofNullable(currency) + .map(Currency::getCurrencyCode) + .orElse(null); + } + + + public void putSymbolMapping(String symbol, CurrencyPair currencyPair) { + SYMBOL_TO_CURRENCY_PAIR.put(symbol, currencyPair); + } + + + public InstrumentMetaData toInstrumentMetaData(BitgetSymbolDto bitgetSymbolDto) { + InstrumentMetaData.Builder builder = new InstrumentMetaData.Builder() + .tradingFee(bitgetSymbolDto.getTakerFeeRate()) + .minimumAmount(bitgetSymbolDto.getMinTradeAmount()) + .maximumAmount(bitgetSymbolDto.getMaxTradeAmount()) + .volumeScale(bitgetSymbolDto.getQuantityPrecision()) + .priceScale(bitgetSymbolDto.getPricePrecision()) + .marketOrderEnabled(bitgetSymbolDto.getStatus() == Status.ONLINE); + + // set min quote amount for USDT + if (bitgetSymbolDto.getCurrencyPair().getCounter().equals(Currency.USDT)) { + builder.counterMinimumAmount(bitgetSymbolDto.getMinTradeUSDT()); + } + + return builder.build(); + } + + + public Ticker toTicker(BitgetTickerDto bitgetTickerDto) { + CurrencyPair currencyPair = toCurrencyPair(bitgetTickerDto.getSymbol()); + if (currencyPair == null) { + return null; + } + return new Ticker.Builder() + .instrument(currencyPair) + .open(bitgetTickerDto.getOpen24h()) + .last(bitgetTickerDto.getLastPrice()) + .bid(bitgetTickerDto.getBestBidPrice()) + .ask(bitgetTickerDto.getBestAskPrice()) + .high(bitgetTickerDto.getHigh24h()) + .low(bitgetTickerDto.getLow24h()) + .volume(bitgetTickerDto.getAssetVolume24h()) + .quoteVolume(bitgetTickerDto.getQuoteVolume24h()) + .timestamp(toDate(bitgetTickerDto.getTimestamp())) + .bidSize(bitgetTickerDto.getBestBidSize()) + .askSize(bitgetTickerDto.getBestAskSize()) + .percentageChange(bitgetTickerDto.getChange24h()) + .build(); + } + + + public Date toDate(Instant instant) { + return Optional.ofNullable(instant) + .map(Date::from) + .orElse(null); + } + + + public Balance toBalance(BitgetBalanceDto balance) { + return new Balance.Builder() + .currency(balance.getCurrency()) + .available(balance.getAvailable()) + .frozen(balance.getFrozen()) + .timestamp(toDate(balance.getTimestamp())) + .build(); + } + + + public Wallet toWallet(List bitgetBalanceDtos) { + List balances = bitgetBalanceDtos.stream() + .map(BitgetAdapters::toBalance) + .collect(Collectors.toList()); + + return Wallet.Builder.from(balances).id("spot").build(); + } + + + public OrderBook toOrderBook(BitgetMarketDepthDto bitgetMarketDepthDto, Instrument instrument) { + List asks = bitgetMarketDepthDto.getAsks().stream() + .map(priceSizeEntry -> + new LimitOrder( + OrderType.ASK, + priceSizeEntry.getSize(), + instrument, + null, + null, + priceSizeEntry.getPrice()) + ) + .collect(Collectors.toList()); + + List bids = bitgetMarketDepthDto.getBids().stream() + .map(priceSizeEntry -> + new LimitOrder( + OrderType.BID, + priceSizeEntry.getSize(), + instrument, + null, + null, + priceSizeEntry.getPrice()) + ) + .collect(Collectors.toList()); + + return new OrderBook(toDate(bitgetMarketDepthDto.getTimestamp()), asks, bids); + } + + + public Order toOrder(BitgetOrderInfoDto order) { + if (order == null) { + return null; + } + + Instrument instrument = toCurrencyPair(order.getSymbol()); + Objects.requireNonNull(instrument); + OrderType orderType = order.getOrderSide(); + + Order.Builder builder; + switch (order.getOrderType()) { + case MARKET: + builder = new MarketOrder.Builder(orderType, instrument); + break; + case LIMIT: + builder = new LimitOrder.Builder(orderType, instrument) + .limitPrice(order.getPrice()); + break; + default: + throw new IllegalArgumentException("Can't map " + order.getOrderType()); + } + + if (orderType == OrderType.BID) { + // buy orders fill quote + builder.cumulativeAmount(order.getQuoteVolume()); + } else if (orderType == OrderType.ASK) { + // sell orders fill asset + builder.cumulativeAmount(order.getBaseVolume()); + } else { + throw new IllegalArgumentException("Can't map " + orderType); + } + + BigDecimal fee = order.getFee(); + if (fee != null) { + builder.fee(fee); + } + + return builder + .id(String.valueOf(order.getOrderId())) + .averagePrice(order.getPriceAvg()) + .originalAmount(order.getSize()) + .userReference(order.getClientOid()) + .timestamp(toDate(order.getCreatedAt())) + .orderStatus(toOrderStatus(order.getOrderStatus())) + .build(); + } + + + public OrderStatus toOrderStatus(BitgetOrderStatus bitgetOrderStatus) { + switch (bitgetOrderStatus) { + case PENDING: + return OrderStatus.NEW; + case PARTIALLY_FILLED: + return OrderStatus.PARTIALLY_FILLED; + case FILLED: + return OrderStatus.FILLED; + case CANCELLED: + return OrderStatus.CANCELED; + default: + throw new IllegalArgumentException("Can't map " + bitgetOrderStatus); + } + } + + + public BitgetPlaceOrderDto toBitgetPlaceOrderDto(MarketOrder marketOrder) { + return BitgetPlaceOrderDto.builder() + .symbol(toString(marketOrder.getInstrument())) + .orderSide(marketOrder.getType()) + .orderType(BitgetOrderInfoDto.OrderType.MARKET) + .clientOid(marketOrder.getUserReference()) + .size(marketOrder.getOriginalAmount()) + .build(); + } + + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/BitgetAuthenticated.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/BitgetAuthenticated.java new file mode 100644 index 0000000000..a22f8f75a1 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/BitgetAuthenticated.java @@ -0,0 +1,58 @@ +package org.knowm.xchange.bitget; + +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import java.io.IOException; +import java.util.List; +import org.knowm.xchange.bitget.dto.BitgetException; +import org.knowm.xchange.bitget.dto.BitgetResponse; +import org.knowm.xchange.bitget.dto.account.BitgetBalanceDto; +import org.knowm.xchange.bitget.dto.trade.BitgetOrderInfoDto; +import org.knowm.xchange.bitget.dto.trade.BitgetPlaceOrderDto; +import si.mazi.rescu.ParamsDigest; +import si.mazi.rescu.SynchronizedValueFactory; + +@Path("") +@Produces(MediaType.APPLICATION_JSON) +public interface BitgetAuthenticated { + + @GET + @Path("api/v2/spot/account/assets") + BitgetResponse> balances( + @HeaderParam("ACCESS-KEY") String apiKey, + @HeaderParam("ACCESS-SIGN") ParamsDigest signer, + @HeaderParam("ACCESS-PASSPHRASE") String passphrase, + @HeaderParam("ACCESS-TIMESTAMP") SynchronizedValueFactory timestamp) + throws IOException, BitgetException; + + + @GET + @Path("api/v2/spot/trade/orderInfo") + BitgetResponse> orderInfo( + @HeaderParam("ACCESS-KEY") String apiKey, + @HeaderParam("ACCESS-SIGN") ParamsDigest signer, + @HeaderParam("ACCESS-PASSPHRASE") String passphrase, + @HeaderParam("ACCESS-TIMESTAMP") SynchronizedValueFactory timestamp, + @QueryParam("orderId") String orderId) + throws IOException, BitgetException; + + + @POST + @Path("api/v2/spot/trade/place-order") + @Consumes(MediaType.APPLICATION_JSON) + BitgetResponse createOrder( + @HeaderParam("ACCESS-KEY") String apiKey, + @HeaderParam("ACCESS-SIGN") ParamsDigest signer, + @HeaderParam("ACCESS-PASSPHRASE") String passphrase, + @HeaderParam("ACCESS-TIMESTAMP") SynchronizedValueFactory timestamp, + BitgetPlaceOrderDto bitgetPlaceOrderDto) + throws IOException, BitgetException; + + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/BitgetErrorAdapter.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/BitgetErrorAdapter.java new file mode 100644 index 0000000000..3d50923b04 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/BitgetErrorAdapter.java @@ -0,0 +1,37 @@ +package org.knowm.xchange.bitget; + +import lombok.experimental.UtilityClass; +import org.knowm.xchange.bitget.dto.BitgetException; +import org.knowm.xchange.exceptions.ExchangeException; +import org.knowm.xchange.exceptions.FundsExceededException; +import org.knowm.xchange.exceptions.InstrumentNotValidException; +import org.knowm.xchange.exceptions.OrderAmountUnderMinimumException; + +@UtilityClass +public class BitgetErrorAdapter { + + public final int INVALID_PARAMETER = 40034; + public final int MIN_ORDER_SIZE = 13008; + public final int MIN_ORDER_AMOUNT = 45110; + public final int MIN_ORDER_QTY = 45111; + public final int INSUFFICIENT_BALANCE = 43012; + + public ExchangeException adapt(BitgetException e) { + + switch (e.getCode()) { + case INVALID_PARAMETER: + return new InstrumentNotValidException(e.getMessage(), e); + + case MIN_ORDER_SIZE: + case MIN_ORDER_AMOUNT: + case MIN_ORDER_QTY: + return new OrderAmountUnderMinimumException(e.getMessage(), e); + + case INSUFFICIENT_BALANCE: + return new FundsExceededException(e.getMessage(), e); + + default: + return new ExchangeException(e.getMessage(), e); + } + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/BitgetExchange.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/BitgetExchange.java new file mode 100644 index 0000000000..b7f3ce80ac --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/BitgetExchange.java @@ -0,0 +1,57 @@ +package org.knowm.xchange.bitget; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.knowm.xchange.BaseExchange; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.bitget.dto.marketdata.BitgetSymbolDto; +import org.knowm.xchange.bitget.service.BitgetAccountService; +import org.knowm.xchange.bitget.service.BitgetMarketDataService; +import org.knowm.xchange.bitget.service.BitgetMarketDataServiceRaw; +import org.knowm.xchange.bitget.service.BitgetTradeService; +import org.knowm.xchange.dto.meta.ExchangeMetaData; +import org.knowm.xchange.dto.meta.InstrumentMetaData; +import org.knowm.xchange.instrument.Instrument; + +public class BitgetExchange extends BaseExchange { + + @Override + protected void initServices() { + accountService = new BitgetAccountService(this); + marketDataService = new BitgetMarketDataService(this); + tradeService = new BitgetTradeService(this); + } + + @Override + public ExchangeSpecification getDefaultExchangeSpecification() { + ExchangeSpecification specification = new ExchangeSpecification(getClass()); + specification.setSslUri("https://api.bitget.com"); + specification.setHost("www.bitget.com"); + specification.setExchangeName("Bitget"); + return specification; + } + + @Override + public void remoteInit() throws IOException { + BitgetMarketDataServiceRaw bitgetMarketDataServiceRaw = (BitgetMarketDataServiceRaw) marketDataService; + + // initialize symbol mappings + List bitgetSymbolDtos = bitgetMarketDataServiceRaw.getBitgetSymbolDtos(null); + bitgetSymbolDtos.forEach(bitgetSymbolDto -> { + BitgetAdapters.putSymbolMapping(bitgetSymbolDto.getSymbol(), bitgetSymbolDto.getCurrencyPair()); + }); + + + // initialize instrument metadata + Map instruments = bitgetSymbolDtos.stream() + .collect(Collectors.toMap( + BitgetSymbolDto::getCurrencyPair, + BitgetAdapters::toInstrumentMetaData) + ); + + exchangeMetaData = new ExchangeMetaData(instruments, null, null, null, null); + + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/BitgetJacksonObjectMapperFactory.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/BitgetJacksonObjectMapperFactory.java new file mode 100644 index 0000000000..350cf8e2b4 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/BitgetJacksonObjectMapperFactory.java @@ -0,0 +1,27 @@ +package org.knowm.xchange.bitget.config; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import si.mazi.rescu.serialization.jackson.DefaultJacksonObjectMapperFactory; + +public class BitgetJacksonObjectMapperFactory extends DefaultJacksonObjectMapperFactory { + + @Override + public void configureObjectMapper(ObjectMapper objectMapper) { + super.configureObjectMapper(objectMapper); + + // by default read timetamps as milliseconds + objectMapper.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false); + + // don't write nulls + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + // don't fail un unknown properties + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + // enable parsing to Instant + objectMapper.registerModule(new JavaTimeModule()); + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/Config.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/Config.java new file mode 100644 index 0000000000..f967008959 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/Config.java @@ -0,0 +1,20 @@ +package org.knowm.xchange.bitget.config; + +import java.time.Clock; +import lombok.Data; + +@Data +public final class Config { + + private Clock clock; + + private static Config instance = new Config(); + + private Config() { + clock = Clock.systemDefaultZone(); + } + + public static Config getInstance() { + return instance; + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/converter/OrderTypeToStringConverter.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/converter/OrderTypeToStringConverter.java new file mode 100644 index 0000000000..abe591c7f3 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/converter/OrderTypeToStringConverter.java @@ -0,0 +1,20 @@ +package org.knowm.xchange.bitget.config.converter; + +import com.fasterxml.jackson.databind.util.StdConverter; +import org.knowm.xchange.dto.Order.OrderType; + +/** Converts {@code OrderType} to string */ +public class OrderTypeToStringConverter extends StdConverter { + + @Override + public String convert(OrderType value) { + switch (value) { + case BID: + return "buy"; + case ASK: + return "sell"; + default: + throw new IllegalArgumentException("Can't map " + value); + } + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/converter/StringToBooleanConverter.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/converter/StringToBooleanConverter.java new file mode 100644 index 0000000000..abf89d8cc4 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/converter/StringToBooleanConverter.java @@ -0,0 +1,13 @@ +package org.knowm.xchange.bitget.config.converter; + +import com.fasterxml.jackson.databind.util.StdConverter; +import org.apache.commons.lang3.BooleanUtils; + +/** Converts string value to {@code Boolean} */ +public class StringToBooleanConverter extends StdConverter { + + @Override + public Boolean convert(final String value) { + return BooleanUtils.toBoolean(value); + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/converter/StringToCurrencyConverter.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/converter/StringToCurrencyConverter.java new file mode 100644 index 0000000000..00e2a5e870 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/converter/StringToCurrencyConverter.java @@ -0,0 +1,13 @@ +package org.knowm.xchange.bitget.config.converter; + +import com.fasterxml.jackson.databind.util.StdConverter; +import org.knowm.xchange.currency.Currency; + +/** Converts string value to {@code Currency} */ +public class StringToCurrencyConverter extends StdConverter { + + @Override + public Currency convert(String value) { + return Currency.getInstance(value); + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/converter/StringToOrderTypeConverter.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/converter/StringToOrderTypeConverter.java new file mode 100644 index 0000000000..0d747baa28 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/converter/StringToOrderTypeConverter.java @@ -0,0 +1,21 @@ +package org.knowm.xchange.bitget.config.converter; + +import com.fasterxml.jackson.databind.util.StdConverter; +import java.util.Locale; +import org.knowm.xchange.dto.Order.OrderType; + +/** Converts string to {@code OrderType} */ +public class StringToOrderTypeConverter extends StdConverter { + + @Override + public OrderType convert(String value) { + switch (value.toUpperCase(Locale.ROOT)) { + case "BUY": + return OrderType.BID; + case "SELL": + return OrderType.ASK; + default: + throw new IllegalArgumentException("Can't map " + value); + } + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/deserializer/FeeDetailDeserializer.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/deserializer/FeeDetailDeserializer.java new file mode 100644 index 0000000000..4628bd65e4 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/config/deserializer/FeeDetailDeserializer.java @@ -0,0 +1,19 @@ +package org.knowm.xchange.bitget.config.deserializer; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import org.knowm.xchange.bitget.dto.trade.BitgetOrderInfoDto.FeeDetail; + +public class FeeDetailDeserializer extends JsonDeserializer { + + @Override + public FeeDetail deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + String text = p.getText(); + ObjectMapper objectMapper = (ObjectMapper) p.getCodec(); + + return objectMapper.readValue(text, FeeDetail.class); + } +} \ No newline at end of file diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/BitgetException.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/BitgetException.java new file mode 100644 index 0000000000..34d33813bd --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/BitgetException.java @@ -0,0 +1,26 @@ +package org.knowm.xchange.bitget.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Instant; +import lombok.Builder; +import lombok.Value; +import lombok.extern.jackson.Jacksonized; + +@Value +@Builder +@Jacksonized +public class BitgetException extends RuntimeException { + + @JsonProperty("code") + Integer code; + + @JsonProperty("msg") + String message; + + @JsonProperty("requestTime") + Instant requestTime; + + @JsonProperty("data") + Object data; + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/BitgetResponse.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/BitgetResponse.java new file mode 100755 index 0000000000..82f44b7677 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/BitgetResponse.java @@ -0,0 +1,34 @@ +package org.knowm.xchange.bitget.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Instant; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import si.mazi.rescu.ExceptionalReturnContentException; + +@Data +@Builder +@Jacksonized +public class BitgetResponse { + + @JsonProperty("code") + private Integer code; + + @JsonProperty("msg") + private String message; + + @JsonProperty("requestTime") + private Instant requestTime; + + @JsonProperty("data") + private T data; + + public void setCode(Integer code) { + if (code != 0) { + throw new ExceptionalReturnContentException(String.valueOf(code)); + } + this.code = code; + } + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/account/BitgetBalanceDto.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/account/BitgetBalanceDto.java new file mode 100755 index 0000000000..5e4813d3dd --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/account/BitgetBalanceDto.java @@ -0,0 +1,37 @@ +package org.knowm.xchange.bitget.dto.account; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.math.BigDecimal; +import java.time.Instant; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.bitget.config.converter.StringToCurrencyConverter; +import org.knowm.xchange.currency.Currency; + +@Data +@Builder +@Jacksonized +public class BitgetBalanceDto { + + @JsonProperty("coin") + @JsonDeserialize(converter = StringToCurrencyConverter.class) + private Currency currency; + + @JsonProperty("available") + private BigDecimal available; + + @JsonProperty("frozen") + private BigDecimal frozen; + + @JsonProperty("locked") + private BigDecimal locked; + + @JsonProperty("limitAvailable") + private BigDecimal limitAvailable; + + @JsonProperty("uTime") + private Instant timestamp; + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetChainDto.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetChainDto.java new file mode 100755 index 0000000000..62139145de --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetChainDto.java @@ -0,0 +1,69 @@ +package org.knowm.xchange.bitget.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@Data +@Builder +@Jacksonized +public class BitgetChainDto { + + @JsonProperty("chain") + private String chain; + + @JsonProperty("needTag") + private Boolean needTag; + + @JsonProperty("withdrawable") + private Boolean isWithdrawEnabled; + + @JsonProperty("rechargeable") + private Boolean isDepositEnabled; + + @JsonProperty("withdrawFee") + private BigDecimal withdrawFee; + + @JsonProperty("extraWithdrawFee") + private BigDecimal extraWithdrawFee; + + @JsonProperty("depositConfirm") + private Integer depositConfirmBlockCount; + + @JsonProperty("withdrawConfirm") + private Integer withdrawConfirmBlockCount; + + @JsonProperty("minDepositAmount") + private BigDecimal minDepositAmount; + + @JsonProperty("minWithdrawAmount") + private BigDecimal minWithdrawAmount; + + @JsonProperty("browserUrl") + private String browserUrl; + + @JsonProperty("contractAddress") + private String contractAddress; + + @JsonProperty("withdrawStep") + private Integer withdrawStep; + + @JsonProperty("withdrawMinScale") + private Integer withdrawMinScale; + + @JsonProperty("congestion") + private Congestion congestion; + + + public static enum Congestion { + @JsonProperty("normal") + NORMAL, + + @JsonProperty("congested") + CONGESTED; + } + +} + diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetCoinDto.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetCoinDto.java new file mode 100755 index 0000000000..60217f85ae --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetCoinDto.java @@ -0,0 +1,35 @@ +package org.knowm.xchange.bitget.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.List; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.bitget.config.converter.StringToBooleanConverter; +import org.knowm.xchange.bitget.config.converter.StringToCurrencyConverter; +import org.knowm.xchange.currency.Currency; + +@Data +@Builder +@Jacksonized +public class BitgetCoinDto { + + @JsonProperty("coinId") + private String coinId; + + @JsonProperty("coin") + @JsonDeserialize(converter = StringToCurrencyConverter.class) + private Currency currency; + + @JsonProperty("transfer") + private Boolean canTransfer; + + @JsonProperty("areaCoin") + @JsonDeserialize(converter = StringToBooleanConverter.class) + private Boolean isAreaCoin; + + @JsonProperty("chains") + private List chains; + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetMarketDepthDto.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetMarketDepthDto.java new file mode 100644 index 0000000000..17746d1e79 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetMarketDepthDto.java @@ -0,0 +1,36 @@ +package org.knowm.xchange.bitget.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; +import java.time.Instant; +import java.util.List; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@Data +@Builder +@Jacksonized +public class BitgetMarketDepthDto { + + @JsonProperty("asks") + List asks; + + @JsonProperty("bids") + List bids; + + @JsonProperty("ts") + Instant timestamp; + + + @Data + @Builder + @Jacksonized + @JsonFormat(shape = JsonFormat.Shape.ARRAY) + public static class PriceSizeEntry { + BigDecimal price; + + BigDecimal size; + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetServerTime.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetServerTime.java new file mode 100644 index 0000000000..6af7180261 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetServerTime.java @@ -0,0 +1,17 @@ +package org.knowm.xchange.bitget.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.time.Instant; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@Data +@Builder +@Jacksonized +public class BitgetServerTime { + + @JsonProperty("serverTime") + private Instant serverTime; + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetSymbolDto.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetSymbolDto.java new file mode 100755 index 0000000000..da353d48c2 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetSymbolDto.java @@ -0,0 +1,86 @@ +package org.knowm.xchange.bitget.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.math.BigDecimal; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.bitget.config.converter.StringToBooleanConverter; +import org.knowm.xchange.bitget.config.converter.StringToCurrencyConverter; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.currency.CurrencyPair; + +@Data +@Builder +@Jacksonized +public class BitgetSymbolDto { + + @JsonProperty("symbol") + private String symbol; + + @JsonProperty("baseCoin") + @JsonDeserialize(converter = StringToCurrencyConverter.class) + private Currency base; + + @JsonProperty("quoteCoin") + @JsonDeserialize(converter = StringToCurrencyConverter.class) + private Currency quote; + + @JsonProperty("minTradeAmount") + private BigDecimal minTradeAmount; + + @JsonProperty("maxTradeAmount") + private BigDecimal maxTradeAmount; + + @JsonProperty("takerFeeRate") + private BigDecimal takerFeeRate; + + @JsonProperty("makerFeeRate") + private BigDecimal makerFeeRate; + + @JsonProperty("pricePrecision") + private Integer pricePrecision; + + @JsonProperty("quantityPrecision") + private Integer quantityPrecision; + + @JsonProperty("quotePrecision") + private Integer quotePrecision; + + @JsonProperty("status") + private Status status; + + @JsonProperty("minTradeUSDT") + private BigDecimal minTradeUSDT; + + @JsonProperty("buyLimitPriceRatio") + private BigDecimal buyLimitPriceRatio; + + @JsonProperty("sellLimitPriceRatio") + private BigDecimal sellLimitPriceRatio; + + @JsonProperty("areaSymbol") + @JsonDeserialize(converter = StringToBooleanConverter.class) + private Boolean isAreaSymbol; + + + public CurrencyPair getCurrencyPair() { + return new CurrencyPair(base, quote); + } + + + public static enum Status { + @JsonProperty("offline") + OFFLINE, + + @JsonProperty("gray") + GRAY, + + @JsonProperty("online") + ONLINE, + + @JsonProperty("halt") + HALT + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetTickerDto.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetTickerDto.java new file mode 100755 index 0000000000..2fd05042b3 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/marketdata/BitgetTickerDto.java @@ -0,0 +1,63 @@ +package org.knowm.xchange.bitget.dto.marketdata; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.math.BigDecimal; +import java.time.Instant; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; + +@Data +@Builder +@Jacksonized +public class BitgetTickerDto { + + @JsonProperty("symbol") + private String symbol; + + @JsonProperty("high24h") + private BigDecimal high24h; + + @JsonProperty("open") + private BigDecimal open24h; + + @JsonProperty("lastPr") + private BigDecimal lastPrice; + + @JsonProperty("low24h") + private BigDecimal low24h; + + @JsonProperty("quoteVolume") + private BigDecimal quoteVolume24h; + + @JsonProperty("baseVolume") + private BigDecimal assetVolume24h; + + @JsonProperty("usdtVolume") + private BigDecimal usdtVolume24h; + + @JsonProperty("bidPr") + private BigDecimal bestBidPrice; + + @JsonProperty("bidSz") + private BigDecimal bestBidSize; + + @JsonProperty("askPr") + private BigDecimal bestAskPrice; + + @JsonProperty("askSz") + private BigDecimal bestAskSize; + + @JsonProperty("openUtc") + private BigDecimal openUtc; + + @JsonProperty("ts") + private Instant timestamp; + + @JsonProperty("changeUtc24h") + private BigDecimal changeUtc24h; + + @JsonProperty("change24h") + private BigDecimal change24h; + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/trade/BitgetOrderInfoDto.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/trade/BitgetOrderInfoDto.java new file mode 100755 index 0000000000..3a6c9e2dc9 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/trade/BitgetOrderInfoDto.java @@ -0,0 +1,147 @@ +package org.knowm.xchange.bitget.dto.trade; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.math.BigDecimal; +import java.time.Instant; +import java.util.Optional; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.bitget.config.converter.StringToOrderTypeConverter; +import org.knowm.xchange.bitget.config.deserializer.FeeDetailDeserializer; +import org.knowm.xchange.dto.Order; + +@Data +@Builder +@Jacksonized +public class BitgetOrderInfoDto { + + @JsonProperty("userId") + private String acccountId; + + @JsonProperty("symbol") + private String symbol; + + @JsonProperty("orderId") + private String orderId; + + @JsonProperty("clientOid") + private String clientOid; + + @JsonProperty("price") + private BigDecimal price; + + @JsonProperty("size") + private BigDecimal size; + + @JsonProperty("orderType") + private OrderType orderType; + + @JsonProperty("side") + @JsonDeserialize(converter = StringToOrderTypeConverter.class) + private Order.OrderType orderSide; + + @JsonProperty("status") + private BitgetOrderStatus orderStatus; + + @JsonProperty("priceAvg") + private BigDecimal priceAvg; + + @JsonProperty("baseVolume") + private BigDecimal baseVolume; + + @JsonProperty("quoteVolume") + private BigDecimal quoteVolume; + + @JsonProperty("enterPointSource") + private String enterPointSource; + + @JsonProperty("cTime") + private Instant createdAt; + + @JsonProperty("uTime") + private Instant updatedAt; + + @JsonProperty("orderSource") + private OrderSource orderSource; + + @JsonProperty("feeDetail") + @JsonDeserialize(using = FeeDetailDeserializer.class) + private FeeDetail feeDetail; + + + public BigDecimal getFee() { + return Optional.ofNullable(feeDetail) + .map(FeeDetail::getNewFees) + .map(NewFees::getTotalFee) + .map(BigDecimal::abs) + .orElse(null); + } + + + public static enum OrderType { + @JsonProperty("limit") + LIMIT, + + @JsonProperty("market") + MARKET + } + + + public static enum BitgetOrderStatus { + @JsonProperty("live") + PENDING, + + @JsonProperty("partially_filled") + PARTIALLY_FILLED, + + @JsonProperty("filled") + FILLED, + + @JsonProperty("cancelled") + CANCELLED + + } + + + public static enum OrderSource { + @JsonProperty("normal") + NORMAL, + + @JsonProperty("market") + MARKET, + + @JsonProperty("spot_trader_buy") + SPOT_TRADER_BUY, + + @JsonProperty("spot_follower_buy") + SPOT_FOLLOWER_BUY, + + @JsonProperty("spot_trader_sell") + SPOT_TRADER_SELL, + + @JsonProperty("spot_follower_sell") + SPOT_FOLLOWER_SELL + + } + + + @Data + @Builder + @Jacksonized + public static class NewFees { + @JsonProperty("t") + private BigDecimal totalFee; + } + + + @Data + @Builder + @Jacksonized + public static class FeeDetail { + @JsonProperty("newFees") + private NewFees newFees; + } + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/trade/BitgetPlaceOrderDto.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/trade/BitgetPlaceOrderDto.java new file mode 100755 index 0000000000..b66048be66 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/dto/trade/BitgetPlaceOrderDto.java @@ -0,0 +1,111 @@ +package org.knowm.xchange.bitget.dto.trade; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import java.math.BigDecimal; +import java.time.Instant; +import lombok.Builder; +import lombok.Data; +import lombok.extern.jackson.Jacksonized; +import org.knowm.xchange.bitget.config.converter.OrderTypeToStringConverter; +import org.knowm.xchange.bitget.config.converter.StringToOrderTypeConverter; +import org.knowm.xchange.bitget.dto.trade.BitgetOrderInfoDto.OrderType; +import org.knowm.xchange.dto.Order; + +@Data +@Builder +@Jacksonized +public class BitgetPlaceOrderDto { + + @JsonProperty("symbol") + private String symbol; + + @JsonProperty("side") + @JsonDeserialize(converter = StringToOrderTypeConverter.class) + @JsonSerialize(converter = OrderTypeToStringConverter.class) + private Order.OrderType orderSide; + + @JsonProperty("orderType") + private OrderType orderType; + + @JsonProperty("force") + private TimeInForce timeInForce; + + @JsonProperty("price") + private BigDecimal price; + + @JsonProperty("size") + private BigDecimal size; + + @JsonProperty("clientOid") + private String clientOid; + + @JsonProperty("triggerPrice") + private BigDecimal triggerPrice; + + @JsonProperty("tpslType") + private TpSlType tpSlType; + + @JsonProperty("requestTime") + private Instant requestTime; + + @JsonProperty("receiveWindow") + private Instant receiveWindow; + + @JsonProperty("stpMode") + private StpMode stpMode; + + @JsonProperty("presetTakeProfitPrice") + private BigDecimal presetTakeProfitPrice; + + @JsonProperty("executeTakeProfitPrice") + private BigDecimal executeTakeProfitPrice; + + @JsonProperty("presetStopLossPrice") + private BigDecimal presetStopLossPrice; + + @JsonProperty("executeStopLossPrice") + private BigDecimal executeStopLossPrice; + + + public enum TimeInForce { + @JsonProperty("gtc") + GOOD_TIL_CANCELLED, + + @JsonProperty("post_only") + POST_ONLY, + + @JsonProperty("fok") + FILL_OR_KILL, + + @JsonProperty("ioc") + IMMEDIATE_OR_CANCEL + } + + + public enum TpSlType { + @JsonProperty("normal") + NORMAL, + + @JsonProperty("tpsl") + SPOT_TP_SL + } + + + public enum StpMode { + @JsonProperty("none") + NONE, + + @JsonProperty("cancel_taker") + CANCEL_TAKER, + + @JsonProperty("cancel_maker") + CANCEL_MAKER, + + @JsonProperty("cancel_both") + CANCEL_BOTH + } + + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetAccountService.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetAccountService.java new file mode 100644 index 0000000000..d8222013fd --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetAccountService.java @@ -0,0 +1,33 @@ +package org.knowm.xchange.bitget.service; + +import java.io.IOException; +import java.util.List; +import org.knowm.xchange.bitget.BitgetAdapters; +import org.knowm.xchange.bitget.BitgetErrorAdapter; +import org.knowm.xchange.bitget.BitgetExchange; +import org.knowm.xchange.bitget.dto.BitgetException; +import org.knowm.xchange.bitget.dto.account.BitgetBalanceDto; +import org.knowm.xchange.dto.account.AccountInfo; +import org.knowm.xchange.dto.account.Wallet; +import org.knowm.xchange.service.account.AccountService; + +public class BitgetAccountService extends BitgetAccountServiceRaw implements AccountService { + + public BitgetAccountService(BitgetExchange exchange) { + super(exchange); + } + + + @Override + public AccountInfo getAccountInfo() throws IOException { + try { + List spotBalances = getBitgetBalances(); + Wallet wallet = BitgetAdapters.toWallet(spotBalances); + return new AccountInfo(wallet); + + } catch (BitgetException e) { + throw BitgetErrorAdapter.adapt(e); + } + } + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetAccountServiceRaw.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetAccountServiceRaw.java new file mode 100644 index 0000000000..4ffc0195fe --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetAccountServiceRaw.java @@ -0,0 +1,18 @@ +package org.knowm.xchange.bitget.service; + +import java.io.IOException; +import java.util.List; +import org.knowm.xchange.bitget.BitgetExchange; +import org.knowm.xchange.bitget.dto.account.BitgetBalanceDto; + +public class BitgetAccountServiceRaw extends BitgetBaseService { + + public BitgetAccountServiceRaw(BitgetExchange exchange) { + super(exchange); + } + + + public List getBitgetBalances() throws IOException { + return bitgetAuthenticated.balances(apiKey, bitgetDigest, passphrase, exchange.getNonceFactory()).getData(); + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetBaseService.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetBaseService.java new file mode 100644 index 0000000000..5d1f6bace4 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetBaseService.java @@ -0,0 +1,35 @@ +package org.knowm.xchange.bitget.service; + +import org.knowm.xchange.bitget.Bitget; +import org.knowm.xchange.bitget.BitgetAuthenticated; +import org.knowm.xchange.bitget.BitgetExchange; +import org.knowm.xchange.bitget.config.BitgetJacksonObjectMapperFactory; +import org.knowm.xchange.client.ExchangeRestProxyBuilder; +import org.knowm.xchange.service.BaseExchangeService; +import org.knowm.xchange.service.BaseService; + +public class BitgetBaseService extends BaseExchangeService implements BaseService { + + protected final String apiKey; + protected final String passphrase; + protected final Bitget bitget; + protected final BitgetAuthenticated bitgetAuthenticated; + protected final BitgetDigest bitgetDigest; + + public BitgetBaseService(BitgetExchange exchange) { + super(exchange); + bitget = ExchangeRestProxyBuilder + .forInterface(Bitget.class, exchange.getExchangeSpecification()) + .clientConfigCustomizer(clientConfig -> clientConfig.setJacksonObjectMapperFactory(new BitgetJacksonObjectMapperFactory())) + .build(); + bitgetAuthenticated = ExchangeRestProxyBuilder + .forInterface(BitgetAuthenticated.class, exchange.getExchangeSpecification()) + .clientConfigCustomizer(clientConfig -> clientConfig.setJacksonObjectMapperFactory(new BitgetJacksonObjectMapperFactory())) + .build(); + + apiKey = exchange.getExchangeSpecification().getApiKey(); + passphrase = exchange.getExchangeSpecification().getPassword(); + bitgetDigest = BitgetDigest.createInstance(exchange.getExchangeSpecification().getSecretKey()); + + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetDigest.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetDigest.java new file mode 100644 index 0000000000..3d2a4ca24e --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetDigest.java @@ -0,0 +1,45 @@ +package org.knowm.xchange.bitget.service; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Locale; +import javax.crypto.Mac; +import lombok.SneakyThrows; +import org.apache.commons.lang3.StringUtils; +import org.knowm.xchange.service.BaseParamsDigest; +import si.mazi.rescu.RestInvocation; + +public final class BitgetDigest extends BaseParamsDigest { + + private BitgetDigest(String secretKeyBase64) { + super(secretKeyBase64, HMAC_SHA_256); + } + + + public static BitgetDigest createInstance(String secretKeyBase64) { + return secretKeyBase64 == null ? null : new BitgetDigest(secretKeyBase64); + } + + + @SneakyThrows + @Override + public String digestParams(RestInvocation restInvocation) { + String method = restInvocation.getHttpMethod().toUpperCase(Locale.ROOT); + String path = restInvocation.getPath(); + + String query = StringUtils.defaultIfEmpty(restInvocation.getQueryString(), ""); + if (StringUtils.isNotEmpty(query)) { + query = "?" + query; + } + String body = StringUtils.defaultIfEmpty(restInvocation.getRequestBody(), ""); + + String timestamp = restInvocation.getHttpHeadersFromParams().get("ACCESS-TIMESTAMP"); + + String payloadToSign = String.format("%s%s/%s%s%s", timestamp, method, path, query, body); + + Mac mac = getMac(); + mac.update(payloadToSign.getBytes(StandardCharsets.UTF_8)); + + return Base64.getEncoder().encodeToString(mac.doFinal()); + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetMarketDataService.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetMarketDataService.java new file mode 100644 index 0000000000..f02332cdf9 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetMarketDataService.java @@ -0,0 +1,96 @@ +package org.knowm.xchange.bitget.service; + +import java.io.IOException; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import org.knowm.xchange.bitget.BitgetAdapters; +import org.knowm.xchange.bitget.BitgetErrorAdapter; +import org.knowm.xchange.bitget.BitgetExchange; +import org.knowm.xchange.bitget.config.Config; +import org.knowm.xchange.bitget.dto.BitgetException; +import org.knowm.xchange.bitget.dto.marketdata.BitgetTickerDto; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.marketdata.OrderBook; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.dto.meta.ExchangeHealth; +import org.knowm.xchange.instrument.Instrument; +import org.knowm.xchange.service.marketdata.MarketDataService; +import org.knowm.xchange.service.marketdata.params.Params; + +public class BitgetMarketDataService extends BitgetMarketDataServiceRaw implements + MarketDataService { + + public BitgetMarketDataService(BitgetExchange exchange) { + super(exchange); + } + + @Override + public ExchangeHealth getExchangeHealth() { + try { + Instant serverTime = getBitgetServerTime().getServerTime(); + Instant localTime = Instant.now(Config.getInstance().getClock()); + + // timestamps shouldn't diverge by more than 10 minutes + if (Duration.between(serverTime, localTime).toMinutes() < 10) { + return ExchangeHealth.ONLINE; + } + } catch (BitgetException | IOException e) { + return ExchangeHealth.OFFLINE; + } + + return ExchangeHealth.OFFLINE; + } + + + @Override + public Ticker getTicker(CurrencyPair currencyPair, Object... args) throws IOException { + return getTicker((Instrument) currencyPair, args); + } + + + @Override + public Ticker getTicker(Instrument instrument, Object... args) throws IOException { + try { + List tickers = getBitgetTickerDtos(instrument); + return BitgetAdapters.toTicker(tickers.get(0)); + + } catch (BitgetException e) { + throw BitgetErrorAdapter.adapt(e); + } + } + + + @Override + public List getTickers(Params params) throws IOException { + try { + return getBitgetTickerDtos(null).stream() + .map(BitgetAdapters::toTicker) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + } catch (BitgetException e) { + throw BitgetErrorAdapter.adapt(e); + } + } + + + @Override + public OrderBook getOrderBook(CurrencyPair currencyPair, Object... args) throws IOException { + return getOrderBook((Instrument) currencyPair, args); + } + + + @Override + public OrderBook getOrderBook(Instrument instrument, Object... args) throws IOException { + Objects.requireNonNull(instrument); + + try { + return BitgetAdapters.toOrderBook(getBitgetMarketDepthDtos(instrument), instrument); + } catch (BitgetException e) { + throw BitgetErrorAdapter.adapt(e); + } + } +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetMarketDataServiceRaw.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetMarketDataServiceRaw.java new file mode 100644 index 0000000000..ee8882c941 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetMarketDataServiceRaw.java @@ -0,0 +1,48 @@ +package org.knowm.xchange.bitget.service; + +import java.io.IOException; +import java.util.List; +import org.knowm.xchange.bitget.BitgetAdapters; +import org.knowm.xchange.bitget.BitgetExchange; +import org.knowm.xchange.bitget.dto.marketdata.BitgetCoinDto; +import org.knowm.xchange.bitget.dto.marketdata.BitgetMarketDepthDto; +import org.knowm.xchange.bitget.dto.marketdata.BitgetServerTime; +import org.knowm.xchange.bitget.dto.marketdata.BitgetSymbolDto; +import org.knowm.xchange.bitget.dto.marketdata.BitgetTickerDto; +import org.knowm.xchange.currency.Currency; +import org.knowm.xchange.instrument.Instrument; + +public class BitgetMarketDataServiceRaw extends BitgetBaseService { + + + public BitgetMarketDataServiceRaw(BitgetExchange exchange) { + super(exchange); + } + + + public BitgetServerTime getBitgetServerTime() throws IOException { + return bitget.serverTime().getData(); + } + + + public List getBitgetCoinDtoList(Currency currency) throws IOException { + return bitget.coins(BitgetAdapters.toString(currency)).getData(); + } + + + public List getBitgetSymbolDtos(Instrument instrument) throws IOException { + return bitget.symbols(BitgetAdapters.toString(instrument)).getData(); + } + + + public List getBitgetTickerDtos(Instrument instrument) throws IOException { + return bitget.tickers(BitgetAdapters.toString(instrument)).getData(); + } + + + public BitgetMarketDepthDto getBitgetMarketDepthDtos(Instrument instrument) throws IOException { + return bitget.orderbook(BitgetAdapters.toString(instrument)).getData(); + } + + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetTradeService.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetTradeService.java new file mode 100644 index 0000000000..73a52bf549 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetTradeService.java @@ -0,0 +1,53 @@ +package org.knowm.xchange.bitget.service; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; +import org.apache.commons.lang3.Validate; +import org.knowm.xchange.bitget.BitgetAdapters; +import org.knowm.xchange.bitget.BitgetErrorAdapter; +import org.knowm.xchange.bitget.BitgetExchange; +import org.knowm.xchange.bitget.dto.BitgetException; +import org.knowm.xchange.bitget.dto.trade.BitgetOrderInfoDto; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.trade.MarketOrder; +import org.knowm.xchange.service.trade.TradeService; +import org.knowm.xchange.service.trade.params.orders.DefaultQueryOrderParam; +import org.knowm.xchange.service.trade.params.orders.OrderQueryParams; + +public class BitgetTradeService extends BitgetTradeServiceRaw implements TradeService { + + public BitgetTradeService(BitgetExchange exchange) { + super(exchange); + } + + + @Override + public Collection getOrder(OrderQueryParams... orderQueryParams) throws IOException { + Validate.validState(orderQueryParams.length == 1); + Validate.isInstanceOf(DefaultQueryOrderParam.class, orderQueryParams[0]); + DefaultQueryOrderParam params = (DefaultQueryOrderParam) orderQueryParams[0]; + + try { + BitgetOrderInfoDto orderStatus = bitgetOrderInfoDto(params.getOrderId()); + return Collections.singletonList(BitgetAdapters.toOrder(orderStatus)); + } + catch (BitgetException e) { + throw BitgetErrorAdapter.adapt(e); + } + + } + + + @Override + public String placeMarketOrder(MarketOrder marketOrder) throws IOException { + try { + return createOrder(BitgetAdapters.toBitgetPlaceOrderDto(marketOrder)).getOrderId(); + } + catch (BitgetException e) { + throw BitgetErrorAdapter.adapt(e); + } + } + + +} diff --git a/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetTradeServiceRaw.java b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetTradeServiceRaw.java new file mode 100644 index 0000000000..870d26c649 --- /dev/null +++ b/xchange-bitget/src/main/java/org/knowm/xchange/bitget/service/BitgetTradeServiceRaw.java @@ -0,0 +1,30 @@ +package org.knowm.xchange.bitget.service; + +import java.io.IOException; +import java.util.List; +import org.knowm.xchange.bitget.BitgetExchange; +import org.knowm.xchange.bitget.dto.trade.BitgetOrderInfoDto; +import org.knowm.xchange.bitget.dto.trade.BitgetPlaceOrderDto; + +public class BitgetTradeServiceRaw extends BitgetBaseService { + + public BitgetTradeServiceRaw(BitgetExchange exchange) { + super(exchange); + } + + + public BitgetOrderInfoDto bitgetOrderInfoDto(String orderId) throws IOException { + List results = bitgetAuthenticated.orderInfo( + apiKey, bitgetDigest, passphrase, exchange.getNonceFactory(), orderId).getData(); + if (results.size() != 1) { + return null; + } + return results.get(0); + } + + + public BitgetOrderInfoDto createOrder(BitgetPlaceOrderDto bitgetPlaceOrderDto) throws IOException { + return bitgetAuthenticated.createOrder(apiKey, bitgetDigest, passphrase, exchange.getNonceFactory(), bitgetPlaceOrderDto).getData(); + } + +} diff --git a/xchange-bitget/src/main/resources/bitget.json b/xchange-bitget/src/main/resources/bitget.json new file mode 100644 index 0000000000..2541d0ebf9 --- /dev/null +++ b/xchange-bitget/src/main/resources/bitget.json @@ -0,0 +1,5 @@ +{ + "currency_pairs" : {}, + "currencies" : {}, + "share_rate_limits" : false +} diff --git a/xchange-bitget/src/test/java/org/knowm/xchange/bitget/BitgetExchangeIntegration.java b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/BitgetExchangeIntegration.java new file mode 100644 index 0000000000..e22fc7207c --- /dev/null +++ b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/BitgetExchangeIntegration.java @@ -0,0 +1,21 @@ +package org.knowm.xchange.bitget; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.meta.InstrumentMetaData; +import org.knowm.xchange.instrument.Instrument; + +class BitgetExchangeIntegration extends BitgetIntegrationTestParent { + + @Test + void valid_metadata() { + assertThat(exchange.getExchangeMetaData()).isNotNull(); + Map instruments = exchange.getExchangeMetaData().getInstruments(); + assertThat(instruments).containsKey(CurrencyPair.BTC_USDT); + } + + +} \ No newline at end of file diff --git a/xchange-bitget/src/test/java/org/knowm/xchange/bitget/BitgetExchangeWiremock.java b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/BitgetExchangeWiremock.java new file mode 100644 index 0000000000..446f4e6a93 --- /dev/null +++ b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/BitgetExchangeWiremock.java @@ -0,0 +1,53 @@ +package org.knowm.xchange.bitget; + +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; + +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.recording.RecordSpecBuilder; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.knowm.xchange.ExchangeFactory; +import org.knowm.xchange.ExchangeSpecification; + +/** Sets up the wiremock for exchange */ +public abstract class BitgetExchangeWiremock { + + protected static BitgetExchange exchange; + +// private static final boolean IS_RECORDING = true; + private static final boolean IS_RECORDING = false; + + private static WireMockServer wireMockServer; + + @BeforeAll + public static void initExchange() { + wireMockServer = new WireMockServer(options().dynamicPort()); + wireMockServer.start(); + + ExchangeSpecification exSpec = new ExchangeSpecification(BitgetExchange.class); + exSpec.setSslUri("http://localhost:" + wireMockServer.port()); + exSpec.setApiKey("a"); + exSpec.setSecretKey("b"); + exSpec.setPassword("c"); + + if (IS_RECORDING) { + // use default url and record the requests + wireMockServer.startRecording( + new RecordSpecBuilder() + .forTarget("https://api.bitget.com") + .matchRequestBodyWithEqualToJson() + .extractTextBodiesOver(1L) + .chooseBodyMatchTypeAutomatically()); + } + + exchange = (BitgetExchange) ExchangeFactory.INSTANCE.createExchange(exSpec); + } + + @AfterAll + public static void stop() { + if (IS_RECORDING) { + wireMockServer.stopRecording(); + } + wireMockServer.stop(); + } +} diff --git a/xchange-bitget/src/test/java/org/knowm/xchange/bitget/BitgetIntegrationTestParent.java b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/BitgetIntegrationTestParent.java new file mode 100644 index 0000000000..52bee0a28a --- /dev/null +++ b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/BitgetIntegrationTestParent.java @@ -0,0 +1,26 @@ +package org.knowm.xchange.bitget; + +import static org.assertj.core.api.Assumptions.assumeThat; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.knowm.xchange.ExchangeFactory; +import org.knowm.xchange.dto.meta.ExchangeHealth; + +public class BitgetIntegrationTestParent { + + protected static BitgetExchange exchange; + + @BeforeAll + static void init() { + exchange = ExchangeFactory.INSTANCE.createExchange(BitgetExchange.class); + } + + @BeforeEach + void exchange_online() { + // skip if offline + assumeThat(exchange.getMarketDataService().getExchangeHealth()).isEqualTo(ExchangeHealth.ONLINE); + } + + +} diff --git a/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetAccountServiceIntegration.java b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetAccountServiceIntegration.java new file mode 100644 index 0000000000..637d669ef9 --- /dev/null +++ b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetAccountServiceIntegration.java @@ -0,0 +1,44 @@ +package org.knowm.xchange.bitget.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assumptions.assumeThat; + +import java.io.IOException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.knowm.xchange.ExchangeFactory; +import org.knowm.xchange.ExchangeSpecification; +import org.knowm.xchange.bitget.BitgetExchange; +import org.knowm.xchange.dto.account.AccountInfo; + +class BitgetAccountServiceIntegration { + + static BitgetExchange exchange; + + + @BeforeAll + public static void credentialsPresent() { + // skip if there are no credentials + String apiKey = System.getProperty("apiKey"); + String secretKey = System.getProperty("secretKey"); + String passphrase = System.getProperty("passphrase"); + assumeThat(apiKey).isNotEmpty(); + assumeThat(secretKey).isNotEmpty(); + assumeThat(passphrase).isNotEmpty(); + + ExchangeSpecification exSpec = new ExchangeSpecification(BitgetExchange.class); + exSpec.setApiKey(apiKey); + exSpec.setSecretKey(secretKey); + exSpec.setPassword(passphrase); + exchange = (BitgetExchange) ExchangeFactory.INSTANCE.createExchange(exSpec); + } + + + @Test + void valid_balances() throws IOException { + AccountInfo accountInfo = exchange.getAccountService().getAccountInfo(); + assertThat(accountInfo.getWallet("spot").getBalances()).isNotEmpty(); + } + + +} \ No newline at end of file diff --git a/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetDigestTest.java b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetDigestTest.java new file mode 100644 index 0000000000..75fbd049f4 --- /dev/null +++ b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetDigestTest.java @@ -0,0 +1,40 @@ +package org.knowm.xchange.bitget.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import si.mazi.rescu.RestInvocation; + +@ExtendWith(MockitoExtension.class) +class BitgetDigestTest { + + @Mock + RestInvocation restInvocation; + + + @Test + void signature_no_query_params() { + BitgetDigest bitgetDigest = BitgetDigest.createInstance("secret"); + + when(restInvocation.getHttpMethod()).thenReturn("get"); + when(restInvocation.getPath()).thenReturn("api/v2/spot/account/assets"); + when(restInvocation.getQueryString()).thenReturn(""); + when(restInvocation.getRequestBody()).thenReturn(null); + Map headers = new HashMap<>(); + headers.put("ACCESS-TIMESTAMP", "1725040472073"); + when(restInvocation.getHttpHeadersFromParams()).thenReturn(headers); + + String actual = bitgetDigest.digestParams(restInvocation); + String expected = "p8vWylxL4JJCyy6B81n82ZEySiWChClCw99FTDocviA="; + + assertThat(actual).isEqualTo(expected); + } + + +} \ No newline at end of file diff --git a/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetMarketDataServiceIntegration.java b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetMarketDataServiceIntegration.java new file mode 100644 index 0000000000..0a0d09f0bf --- /dev/null +++ b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetMarketDataServiceIntegration.java @@ -0,0 +1,78 @@ +package org.knowm.xchange.bitget.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import java.io.IOException; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.knowm.xchange.bitget.BitgetIntegrationTestParent; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.marketdata.OrderBook; +import org.knowm.xchange.dto.marketdata.Ticker; +import org.knowm.xchange.exceptions.InstrumentNotValidException; + +class BitgetMarketDataServiceIntegration extends BitgetIntegrationTestParent { + + @Test + void valid_single_ticker() throws IOException { + Ticker ticker = exchange.getMarketDataService().getTicker(CurrencyPair.BTC_USDT); + + assertThat(ticker.getInstrument()).isEqualTo(CurrencyPair.BTC_USDT); + assertThat(ticker.getLast()).isNotNull(); + + if (ticker.getBid().signum() > 0 && ticker.getAsk().signum() > 0) { + assertThat(ticker.getBid()).isLessThan(ticker.getAsk()); + } + + } + + + @Test + void check_exceptions() { + assertThatExceptionOfType(InstrumentNotValidException.class) + .isThrownBy(() -> + exchange.getMarketDataService().getTicker(new CurrencyPair("NONEXISTING/NONEXISTING"))); + + } + + + @Test + void valid_tickers() throws IOException { + List tickers = exchange.getMarketDataService().getTickers(null); + assertThat(tickers).isNotEmpty(); + + assertThat(tickers).allSatisfy(ticker -> { + assertThat(ticker.getInstrument()).isNotNull(); + assertThat(ticker.getLast()).isNotNull(); + + if (ticker.getBid().signum() > 0 && ticker.getAsk().signum() > 0) { + assertThat(ticker.getBid()).isLessThan(ticker.getAsk()); + } + }); + } + + + @Test + void valid_orderbook() throws IOException { + OrderBook orderBook = exchange.getMarketDataService().getOrderBook(CurrencyPair.BTC_USDT); + + assertThat(orderBook.getBids()).isNotEmpty(); + assertThat(orderBook.getAsks()).isNotEmpty(); + + assertThat(orderBook.getAsks().get(0).getLimitPrice()).isGreaterThan(orderBook.getBids().get(0).getLimitPrice()); + + assertThat(orderBook.getBids()).allSatisfy(limitOrder -> { + assertThat(limitOrder.getInstrument()).isEqualTo(CurrencyPair.BTC_USDT); + assertThat(limitOrder.getType()).isEqualTo(OrderType.BID); + }); + + assertThat(orderBook.getAsks()).allSatisfy(limitOrder -> { + assertThat(limitOrder.getInstrument()).isEqualTo(CurrencyPair.BTC_USDT); + assertThat(limitOrder.getType()).isEqualTo(OrderType.ASK); + }); + } + + +} \ No newline at end of file diff --git a/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetMarketDataServiceRawIntegration.java b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetMarketDataServiceRawIntegration.java new file mode 100644 index 0000000000..ee24c10a9b --- /dev/null +++ b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetMarketDataServiceRawIntegration.java @@ -0,0 +1,85 @@ +package org.knowm.xchange.bitget.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.knowm.xchange.currency.CurrencyPair.BTC_USDT; + +import java.io.IOException; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.knowm.xchange.bitget.BitgetIntegrationTestParent; +import org.knowm.xchange.bitget.dto.marketdata.BitgetCoinDto; +import org.knowm.xchange.bitget.dto.marketdata.BitgetSymbolDto; +import org.knowm.xchange.currency.Currency; + +class BitgetMarketDataServiceRawIntegration extends BitgetIntegrationTestParent { + + BitgetMarketDataServiceRaw bitgetMarketDataServiceRaw = (BitgetMarketDataServiceRaw) exchange.getMarketDataService(); + + @Test + void valid_coins() throws IOException { + List coins = bitgetMarketDataServiceRaw.getBitgetCoinDtoList(null); + + assertThat(coins).isNotEmpty(); + + // validate coins + assertThat(coins).allSatisfy(coin -> { + assertThat(coin.getCoinId()).isNotNull(); + assertThat(coin.getCurrency()).isNotNull(); + + // validate each chain + assertThat(coin.getChains()).allSatisfy(chain -> { + assertThat(chain.getChain()).isNotNull(); + }); + + }); + } + + + @Test + void valid_coin() throws IOException { + List coins = bitgetMarketDataServiceRaw.getBitgetCoinDtoList(Currency.USDT); + + assertThat(coins).hasSize(1); + + assertThat(coins.get(0).getCurrency()).isEqualTo(Currency.USDT); + assertThat(coins.get(0).getCoinId()).isNotNull(); + assertThat(coins.get(0).getChains()).allSatisfy(chain -> { + assertThat(chain.getChain()).isNotNull(); + }); + + } + + + @Test + void valid_symbol() throws IOException { + List symbols = bitgetMarketDataServiceRaw.getBitgetSymbolDtos(BTC_USDT); + + assertThat(symbols).hasSize(1); + + BitgetSymbolDto symbol = symbols.get(0); + assertThat(symbol.getCurrencyPair()).isEqualTo(BTC_USDT); + assertThat(symbol.getPricePrecision()).isPositive(); + assertThat(symbol.getQuantityPrecision()).isPositive(); + assertThat(symbol.getQuotePrecision()).isPositive(); + + } + + + @Test + void valid_symbols() throws IOException { + List symbols = bitgetMarketDataServiceRaw.getBitgetSymbolDtos(null); + + assertThat(symbols).isNotEmpty(); + + // validate symbols + assertThat(symbols).allSatisfy(symbol -> { + assertThat(symbol.getCurrencyPair()).isNotNull(); + assertThat(symbol.getBase()).isNotNull(); + assertThat(symbol.getQuote()).isNotNull(); + + }); + + } + + +} \ No newline at end of file diff --git a/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetTradeServiceTest.java b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetTradeServiceTest.java new file mode 100644 index 0000000000..50356073bd --- /dev/null +++ b/xchange-bitget/src/test/java/org/knowm/xchange/bitget/service/BitgetTradeServiceTest.java @@ -0,0 +1,95 @@ +package org.knowm.xchange.bitget.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.math.BigDecimal; +import java.time.Instant; +import java.util.Collection; +import java.util.Date; +import org.junit.jupiter.api.Test; +import org.knowm.xchange.bitget.BitgetExchangeWiremock; +import org.knowm.xchange.currency.CurrencyPair; +import org.knowm.xchange.dto.Order; +import org.knowm.xchange.dto.Order.OrderStatus; +import org.knowm.xchange.dto.Order.OrderType; +import org.knowm.xchange.dto.trade.MarketOrder; +import org.knowm.xchange.service.trade.TradeService; +import org.knowm.xchange.service.trade.params.orders.DefaultQueryOrderParamInstrument; + +class BitgetTradeServiceTest extends BitgetExchangeWiremock { + + TradeService tradeService = exchange.getTradeService(); + + + @Test + void sell_order_details() throws IOException { + MarketOrder expected = + new MarketOrder.Builder(OrderType.ASK, new CurrencyPair("BGB/USDT")) + .id("1214193970718347264") + .userReference("t-valid-market-sell-order") + .timestamp(Date.from(Instant.parse("2024-09-01T17:38:41.929Z"))) + .originalAmount(new BigDecimal("2")) + .orderStatus(OrderStatus.FILLED) + .cumulativeAmount(new BigDecimal("2")) + .averagePrice(new BigDecimal("0.9649")) + .fee(new BigDecimal("0.0019298")) + .build(); + + Collection orders = tradeService.getOrder("1214193970718347264"); + assertThat(orders).hasSize(1); + assertThat(orders).first() + .usingComparatorForType(BigDecimal::compareTo, BigDecimal.class) + .usingRecursiveComparison().isEqualTo(expected); + } + + + @Test + void buy_order_details() throws IOException { + MarketOrder expected = + new MarketOrder.Builder(OrderType.BID, new CurrencyPair("BGB/USDT")) + .id("1214189703404097539") + .userReference("t-valid-market-buy-order") + .timestamp(Date.from(Instant.parse("2024-09-01T17:21:44.522Z"))) + .originalAmount(new BigDecimal("2")) + .orderStatus(OrderStatus.FILLED) + .cumulativeAmount(new BigDecimal("1.9999925400000000")) + .averagePrice(new BigDecimal("0.9659000000000000")) + .fee(new BigDecimal("0.0020706")) + .build(); + + Collection orders = tradeService.getOrder(new DefaultQueryOrderParamInstrument(null, "1214189703404097539")); + assertThat(orders).hasSize(1); + assertThat(orders).first() + .usingComparatorForType(BigDecimal::compareTo, BigDecimal.class) + .usingRecursiveComparison().isEqualTo(expected); + } + + + @Test + void place_market_buy_order() throws IOException { + MarketOrder marketOrder = + new MarketOrder.Builder(OrderType.BID, new CurrencyPair("BGB/USDT")) + .userReference("t-valid-market-buy-order") + .originalAmount(BigDecimal.valueOf(2)) + .build(); + + String actualResponse = exchange.getTradeService().placeMarketOrder(marketOrder); + assertThat(actualResponse).isEqualTo("1214189703404097539"); + } + + + @Test + void place_market_sell_order() throws IOException { + MarketOrder marketOrder = + new MarketOrder.Builder(OrderType.ASK, new CurrencyPair("BGB/USDT")) + .userReference("t-valid-market-sell-order") + .originalAmount(BigDecimal.valueOf(2)) + .build(); + + String actualResponse = exchange.getTradeService().placeMarketOrder(marketOrder); + assertThat(actualResponse).isEqualTo("1214193970718347264"); + } + + +} \ No newline at end of file diff --git a/xchange-bitget/src/test/resources/__files/api_v2_spot_public_symbols.json b/xchange-bitget/src/test/resources/__files/api_v2_spot_public_symbols.json new file mode 100644 index 0000000000..7c262633a4 --- /dev/null +++ b/xchange-bitget/src/test/resources/__files/api_v2_spot_public_symbols.json @@ -0,0 +1,58 @@ +{ + "code": "00000", + "msg": "success", + "requestTime": 1725192127389, + "data": [ + { + "symbol": "BTCUSDT", + "baseCoin": "BTC", + "quoteCoin": "USDT", + "minTradeAmount": "0", + "maxTradeAmount": "10000000000", + "takerFeeRate": "0.002", + "makerFeeRate": "0.002", + "pricePrecision": "2", + "quantityPrecision": "6", + "quotePrecision": "6", + "status": "online", + "minTradeUSDT": "1", + "buyLimitPriceRatio": "0.05", + "sellLimitPriceRatio": "0.05", + "areaSymbol": "no" + }, + { + "symbol": "ETHUSDT", + "baseCoin": "ETH", + "quoteCoin": "USDT", + "minTradeAmount": "0", + "maxTradeAmount": "10000000000", + "takerFeeRate": "0.002", + "makerFeeRate": "0.002", + "pricePrecision": "2", + "quantityPrecision": "4", + "quotePrecision": "6", + "status": "online", + "minTradeUSDT": "1", + "buyLimitPriceRatio": "0.05", + "sellLimitPriceRatio": "0.05", + "areaSymbol": "no" + }, + { + "symbol": "BGBUSDT", + "baseCoin": "BGB", + "quoteCoin": "USDT", + "minTradeAmount": "0", + "maxTradeAmount": "10000000000", + "takerFeeRate": "0.001", + "makerFeeRate": "0.001", + "pricePrecision": "4", + "quantityPrecision": "4", + "quotePrecision": "6", + "status": "online", + "minTradeUSDT": "1", + "buyLimitPriceRatio": "0.05", + "sellLimitPriceRatio": "0.05", + "areaSymbol": "no" + } + ] +} \ No newline at end of file diff --git a/xchange-bitget/src/test/resources/__files/api_v2_spot_trade_orderinfo-t-valid-market-buy-order.json b/xchange-bitget/src/test/resources/__files/api_v2_spot_trade_orderinfo-t-valid-market-buy-order.json new file mode 100644 index 0000000000..6c03d91113 --- /dev/null +++ b/xchange-bitget/src/test/resources/__files/api_v2_spot_trade_orderinfo-t-valid-market-buy-order.json @@ -0,0 +1,30 @@ +{ + "code": "00000", + "msg": "success", + "requestTime": 1725211448919, + "data": [ + { + "userId": "1548914322", + "symbol": "BGBUSDT", + "orderId": "1214189703404097539", + "clientOid": "t-valid-market-buy-order", + "price": "0", + "size": "2.0000000000000000", + "orderType": "market", + "side": "buy", + "status": "filled", + "priceAvg": "0.9659000000000000", + "baseVolume": "2.0706000000000000", + "quoteVolume": "1.9999925400000000", + "enterPointSource": "API", + "feeDetail": "{\"BGB\":{\"deduction\":false,\"feeCoinCode\":\"BGB\",\"totalDeductionFee\":0,\"totalFee\":-0.0020706000000000},\"newFees\":{\"c\":0,\"d\":0,\"deduction\":false,\"r\":-0.0020706,\"t\":-0.0020706,\"totalDeductionFee\":0}}", + "orderSource": "market", + "tpslType": "normal", + "triggerPrice": null, + "quoteCoin": "USDT", + "baseCoin": "BGB", + "cTime": "1725211304522", + "uTime": "1725211304609" + } + ] +} \ No newline at end of file diff --git a/xchange-bitget/src/test/resources/__files/api_v2_spot_trade_orderinfo-t-valid-market-sell-order.json b/xchange-bitget/src/test/resources/__files/api_v2_spot_trade_orderinfo-t-valid-market-sell-order.json new file mode 100644 index 0000000000..256eae5349 --- /dev/null +++ b/xchange-bitget/src/test/resources/__files/api_v2_spot_trade_orderinfo-t-valid-market-sell-order.json @@ -0,0 +1,30 @@ +{ + "code": "00000", + "msg": "success", + "requestTime": 1725212468324, + "data": [ + { + "userId": "1548914322", + "symbol": "BGBUSDT", + "orderId": "1214193970718347264", + "clientOid": "t-valid-market-sell-order", + "price": "0", + "size": "2.0000000000000000", + "orderType": "market", + "side": "sell", + "status": "filled", + "priceAvg": "0.9649000000000000", + "baseVolume": "2.0000000000000000", + "quoteVolume": "1.9298000000000000", + "enterPointSource": "API", + "feeDetail": "{\"newFees\":{\"c\":0,\"d\":0,\"deduction\":false,\"r\":-0.0019298,\"t\":-0.0019298,\"totalDeductionFee\":0},\"USDT\":{\"deduction\":false,\"feeCoinCode\":\"USDT\",\"totalDeductionFee\":0,\"totalFee\":-0.0019298000000000}}", + "orderSource": "market", + "tpslType": "normal", + "triggerPrice": null, + "quoteCoin": "USDT", + "baseCoin": "BGB", + "cTime": "1725212321929", + "uTime": "1725212322028" + } + ] +} \ No newline at end of file diff --git a/xchange-bitget/src/test/resources/__files/api_v2_spot_trade_place-buy-order.json b/xchange-bitget/src/test/resources/__files/api_v2_spot_trade_place-buy-order.json new file mode 100644 index 0000000000..7a3a3b197b --- /dev/null +++ b/xchange-bitget/src/test/resources/__files/api_v2_spot_trade_place-buy-order.json @@ -0,0 +1,9 @@ +{ + "code": "00000", + "msg": "success", + "requestTime": 1725211304517, + "data": { + "orderId": "1214189703404097539", + "clientOid": "t-valid-market-buy-order" + } +} \ No newline at end of file diff --git a/xchange-bitget/src/test/resources/__files/api_v2_spot_trade_place-sell-order.json b/xchange-bitget/src/test/resources/__files/api_v2_spot_trade_place-sell-order.json new file mode 100644 index 0000000000..0a7cf32d17 --- /dev/null +++ b/xchange-bitget/src/test/resources/__files/api_v2_spot_trade_place-sell-order.json @@ -0,0 +1,9 @@ +{ + "code": "00000", + "msg": "success", + "requestTime": 1725212321924, + "data": { + "orderId": "1214193970718347264", + "clientOid": "t-valid-market-sell-order" + } +} \ No newline at end of file diff --git a/xchange-bitget/src/test/resources/logback.xml b/xchange-bitget/src/test/resources/logback.xml new file mode 100644 index 0000000000..c823ac40a9 --- /dev/null +++ b/xchange-bitget/src/test/resources/logback.xml @@ -0,0 +1,23 @@ + + + + + + + + + %d{HH:mm:ss.SSS} [%contextName] [%thread] %-5level %logger{36} - %msg %xEx%n + + + + + + + + + + + + + + diff --git a/xchange-bitget/src/test/resources/mappings/api_v2_spot_public_symbols.json b/xchange-bitget/src/test/resources/mappings/api_v2_spot_public_symbols.json new file mode 100644 index 0000000000..428ffbcb55 --- /dev/null +++ b/xchange-bitget/src/test/resources/mappings/api_v2_spot_public_symbols.json @@ -0,0 +1,15 @@ +{ + "id" : "1a8a3149-0eb1-429f-a06a-4a2242e18d42", + "name" : "api_v2_spot_public_symbols", + "request" : { + "url" : "/api/v2/spot/public/symbols", + "method" : "GET" + }, + "response" : { + "status" : 200, + "bodyFileName" : "api_v2_spot_public_symbols.json" + }, + "uuid" : "1a8a3149-0eb1-429f-a06a-4a2242e18d42", + "persistent" : true, + "insertionIndex" : 2 +} \ No newline at end of file diff --git a/xchange-bitget/src/test/resources/mappings/api_v2_spot_trade_orderinfo-t-valid-market-buy-order.json b/xchange-bitget/src/test/resources/mappings/api_v2_spot_trade_orderinfo-t-valid-market-buy-order.json new file mode 100644 index 0000000000..f7d02b466a --- /dev/null +++ b/xchange-bitget/src/test/resources/mappings/api_v2_spot_trade_orderinfo-t-valid-market-buy-order.json @@ -0,0 +1,15 @@ +{ + "id" : "aebaa168-bbc9-4819-98f5-c2336d01ca43", + "name" : "api_v2_spot_trade_orderinfo", + "request" : { + "url" : "/api/v2/spot/trade/orderInfo?orderId=1214189703404097539", + "method" : "GET" + }, + "response" : { + "status" : 200, + "bodyFileName" : "api_v2_spot_trade_orderinfo-t-valid-market-buy-order.json" + }, + "uuid" : "aebaa168-bbc9-4819-98f5-c2336d01ca43", + "persistent" : true, + "insertionIndex" : 1 +} \ No newline at end of file diff --git a/xchange-bitget/src/test/resources/mappings/api_v2_spot_trade_orderinfo-t-valid-market-sell-order.json b/xchange-bitget/src/test/resources/mappings/api_v2_spot_trade_orderinfo-t-valid-market-sell-order.json new file mode 100644 index 0000000000..e9789b0feb --- /dev/null +++ b/xchange-bitget/src/test/resources/mappings/api_v2_spot_trade_orderinfo-t-valid-market-sell-order.json @@ -0,0 +1,15 @@ +{ + "id" : "aebaa168-bbc9-4819-98f5-c2336d01ca43", + "name" : "api_v2_spot_trade_orderinfo", + "request" : { + "url" : "/api/v2/spot/trade/orderInfo?orderId=1214193970718347264", + "method" : "GET" + }, + "response" : { + "status" : 200, + "bodyFileName" : "api_v2_spot_trade_orderinfo-t-valid-market-sell-order.json" + }, + "uuid" : "aebaa168-bbc9-4819-98f5-c2336d01ca43", + "persistent" : true, + "insertionIndex" : 1 +} \ No newline at end of file diff --git a/xchange-bitget/src/test/resources/mappings/api_v2_spot_trade_place-buy-order.json b/xchange-bitget/src/test/resources/mappings/api_v2_spot_trade_place-buy-order.json new file mode 100644 index 0000000000..a6b55d3d18 --- /dev/null +++ b/xchange-bitget/src/test/resources/mappings/api_v2_spot_trade_place-buy-order.json @@ -0,0 +1,20 @@ +{ + "id" : "bf637b08-636e-4c43-af21-90ccb86586f4", + "name" : "api_v2_spot_trade_place-order", + "request" : { + "url" : "/api/v2/spot/trade/place-order", + "method" : "POST", + "bodyPatterns" : [ { + "equalToJson" : "{\"symbol\":\"BGBUSDT\",\"side\":\"buy\",\"orderType\":\"market\",\"size\":2,\"clientOid\":\"t-valid-market-buy-order\"}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : true + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "api_v2_spot_trade_place-buy-order.json" + }, + "uuid" : "bf637b08-636e-4c43-af21-90ccb86586f4", + "persistent" : true, + "insertionIndex" : 3 +} \ No newline at end of file diff --git a/xchange-bitget/src/test/resources/mappings/api_v2_spot_trade_place-sell-order.json b/xchange-bitget/src/test/resources/mappings/api_v2_spot_trade_place-sell-order.json new file mode 100644 index 0000000000..a951b592db --- /dev/null +++ b/xchange-bitget/src/test/resources/mappings/api_v2_spot_trade_place-sell-order.json @@ -0,0 +1,20 @@ +{ + "id" : "c04a9a18-7a57-4f15-86e4-b32cf57cea4e", + "name" : "api_v2_spot_trade_place-order", + "request" : { + "url" : "/api/v2/spot/trade/place-order", + "method" : "POST", + "bodyPatterns" : [ { + "equalToJson" : "{\"symbol\":\"BGBUSDT\",\"side\":\"sell\",\"orderType\":\"market\",\"size\":2,\"clientOid\":\"t-valid-market-sell-order\"}", + "ignoreArrayOrder" : true, + "ignoreExtraElements" : true + } ] + }, + "response" : { + "status" : 200, + "bodyFileName" : "api_v2_spot_trade_place-sell-order.json" + }, + "uuid" : "c04a9a18-7a57-4f15-86e4-b32cf57cea4e", + "persistent" : true, + "insertionIndex" : 5 +} \ No newline at end of file diff --git a/xchange-bitget/src/test/resources/rest/common.http b/xchange-bitget/src/test/resources/rest/common.http new file mode 100644 index 0000000000..41acdbe899 --- /dev/null +++ b/xchange-bitget/src/test/resources/rest/common.http @@ -0,0 +1,3 @@ +### Get Server Time +GET {{api_host}}/api/v2/public/time + diff --git a/xchange-bitget/src/test/resources/rest/sign.js b/xchange-bitget/src/test/resources/rest/sign.js new file mode 100644 index 0000000000..2daee39999 --- /dev/null +++ b/xchange-bitget/src/test/resources/rest/sign.js @@ -0,0 +1,22 @@ +export function gen_sign(method, request) { + const pattern = RegExp("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?"); + const url = request.url.tryGetSubstituted(); + const matches = url.match(pattern); + + const path = matches[5]; + + let query = matches[7] || ""; + if (query !== "") { + query = "?" + query; + } + + const body = request.body.tryGetSubstituted() || ""; + const timestamp = Math.floor(Date.now()).toFixed(); + + const payloadToSign = `${timestamp}${method}${path}${query}${body}`; + const apiSecret = request.environment.get("api_secret"); + const sign = crypto.hmac.sha256().withTextSecret(apiSecret).updateWithText(payloadToSign).digest().toBase64(); + + request.variables.set("timestamp", timestamp); + request.variables.set("sign", sign); +} \ No newline at end of file diff --git a/xchange-bitget/src/test/resources/rest/spot.http b/xchange-bitget/src/test/resources/rest/spot.http new file mode 100644 index 0000000000..1fb8850c39 --- /dev/null +++ b/xchange-bitget/src/test/resources/rest/spot.http @@ -0,0 +1,94 @@ +### Get Coin Info +GET {{api_host}}/api/v2/spot/public/coins?coin=usdt + + +### Get OrderBook Depth +GET {{api_host}}/api/v2/spot/market/orderbook?symbol=btcusdt + + +### Get Symbol Info +GET {{api_host}}/api/v2/spot/public/symbols + + +### Get Ticker Information +GET {{api_host}}/api/v2/spot/market/tickers + + +### Get Account Information +< {% + import {gen_sign} from 'sign.js' + gen_sign("GET", request); +%} +GET {{api_host}}/api/v2/spot/account/info +ACCESS-KEY: {{api_key}} +ACCESS-SIGN: {{sign}} +ACCESS-TIMESTAMP: {{timestamp}} +ACCESS-PASSPHRASE: {{api_passphrase}} + + +### Get Account assets +< {% + import {gen_sign} from 'sign.js' + gen_sign("GET", request); +%} +GET {{api_host}}/api/v2/spot/account/assets +ACCESS-KEY: {{api_key}} +ACCESS-SIGN: {{sign}} +ACCESS-TIMESTAMP: {{timestamp}} +ACCESS-PASSPHRASE: {{api_passphrase}} + + +### Get Order Info +< {% + import {gen_sign} from 'sign.js' + gen_sign("GET", request); +%} +GET {{api_host}}/api/v2/spot/trade/orderInfo?orderId=1213530920130613257 +ACCESS-KEY: {{api_key}} +ACCESS-SIGN: {{sign}} +ACCESS-TIMESTAMP: {{timestamp}} +ACCESS-PASSPHRASE: {{api_passphrase}} + + +### Place Market Buy Order +< {% + import {gen_sign} from 'sign.js' + gen_sign("POST", request); +%} +POST {{api_host}}/api/v2/spot/trade/place-order +ACCESS-KEY: {{api_key}} +ACCESS-SIGN: {{sign}} +ACCESS-TIMESTAMP: {{timestamp}} +ACCESS-PASSPHRASE: {{api_passphrase}} +Content-Type: application/json + +{ + "symbol": "BGBUSDT", + "side": "buy", + "orderType": "market", + "size": "2", + "clientOid": "{{$random.uuid}}" +} + + +### Place Market Sell Order +< {% + import {gen_sign} from 'sign.js' + gen_sign("POST", request); +%} +POST {{api_host}}/api/v2/spot/trade/place-order +ACCESS-KEY: {{api_key}} +ACCESS-SIGN: {{sign}} +ACCESS-TIMESTAMP: {{timestamp}} +ACCESS-PASSPHRASE: {{api_passphrase}} +Content-Type: application/json + +{ + "symbol": "BGBUSDT", + "side": "sell", + "orderType": "market", + "size": "3", + "clientOid": "{{$random.uuid}}" +} + +