Skip to content

Commit

Permalink
Bithumb - fix place of amount 0
Browse files Browse the repository at this point in the history
  • Loading branch information
sjanel committed May 14, 2023
1 parent dead93f commit 1cc12e0
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 24 deletions.
5 changes: 5 additions & 0 deletions src/api/exchanges/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ add_exchange_test(
test/bithumbapi_test.cpp
)

add_exchange_test(
bithumb_place_order_test
test/bithumb_place_order_test.cpp
)

add_exchange_test(
huobiapi_test
test/huobiapi_test.cpp
Expand Down
2 changes: 2 additions & 0 deletions src/api/exchanges/include/bithumbprivateapi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ class BithumbPrivate : public ExchangePrivate {
InitiatedWithdrawInfo launchWithdraw(MonetaryAmount grossAmount, Wallet&& destinationWallet) override;

private:
friend class BithumbPrivateAPIPlaceOrderTest;

struct DepositWalletFunc {
#ifndef CCT_AGGR_INIT_CXX20
DepositWalletFunc(CurlHandle& curlHandle, const APIKey& apiKey, BithumbPublic& exchangePublic)
Expand Down
51 changes: 27 additions & 24 deletions src/api/exchanges/src/bithumbprivateapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,12 @@ constexpr std::string_view kWalletAddressEndpointStr = "/info/wallet_address";

std::pair<string, Nonce> GetStrData(std::string_view endpoint, std::string_view postDataStr) {
Nonce nonce = Nonce_TimeSinceEpochInMs();
string strData(endpoint);
strData.reserve(strData.size() + 2U + postDataStr.size() + nonce.size());

static constexpr char kParChar = 1;
strData.push_back(kParChar);
strData.append(postDataStr);
strData.push_back(kParChar);
string strData(endpoint.size() + 2U + postDataStr.size() + nonce.size(), kParChar);

strData.append(nonce.begin(), nonce.end());
auto it = std::ranges::copy(endpoint, strData.begin()).out;
it = std::ranges::copy(postDataStr, it + 1).out;
it = std::ranges::copy(nonce, it + 1).out;
return std::make_pair(std::move(strData), std::move(nonce));
}

Expand Down Expand Up @@ -276,19 +273,21 @@ BithumbPrivate::BithumbPrivate(const CoincenterInfo& config, BithumbPublic& bith
_depositWalletsCache(
CachedResultOptions(exchangeInfo().getAPICallUpdateFrequency(kDepositWallet), _cachedResultVault),
_curlHandle, _apiKey, bithumbPublic) {
json data = GetBithumbCurrencyInfoMapCache(_coincenterInfo.dataDir()).readAllJson();
for (const auto& [currencyCodeStr, currencyOrderInfoJson] : data.items()) {
CurrencyOrderInfo currencyOrderInfo;

LoadCurrencyInfoField(currencyOrderInfoJson, kNbDecimalsStr, currencyOrderInfo.nbDecimals,
currencyOrderInfo.lastNbDecimalsUpdatedTime);
LoadCurrencyInfoField(currencyOrderInfoJson, kMinOrderSizeJsonKeyStr, currencyOrderInfo.minOrderSize,
currencyOrderInfo.lastMinOrderSizeUpdatedTime);
LoadCurrencyInfoField(currencyOrderInfoJson, kMinOrderPriceJsonKeyStr, currencyOrderInfo.minOrderPrice,
currencyOrderInfo.lastMinOrderPriceUpdatedTime);
LoadCurrencyInfoField(currencyOrderInfoJson, kMaxOrderPriceJsonKeyStr, currencyOrderInfo.maxOrderPrice,
currencyOrderInfo.lastMaxOrderPriceUpdatedTime);
_currencyOrderInfoMap.insert_or_assign(CurrencyCode(currencyCodeStr), std::move(currencyOrderInfo));
if (config.getRunMode() != settings::RunMode::kQueryResponseOverriden) {
json data = GetBithumbCurrencyInfoMapCache(_coincenterInfo.dataDir()).readAllJson();
for (const auto& [currencyCodeStr, currencyOrderInfoJson] : data.items()) {
CurrencyOrderInfo currencyOrderInfo;

LoadCurrencyInfoField(currencyOrderInfoJson, kNbDecimalsStr, currencyOrderInfo.nbDecimals,
currencyOrderInfo.lastNbDecimalsUpdatedTime);
LoadCurrencyInfoField(currencyOrderInfoJson, kMinOrderSizeJsonKeyStr, currencyOrderInfo.minOrderSize,
currencyOrderInfo.lastMinOrderSizeUpdatedTime);
LoadCurrencyInfoField(currencyOrderInfoJson, kMinOrderPriceJsonKeyStr, currencyOrderInfo.minOrderPrice,
currencyOrderInfo.lastMinOrderPriceUpdatedTime);
LoadCurrencyInfoField(currencyOrderInfoJson, kMaxOrderPriceJsonKeyStr, currencyOrderInfo.maxOrderPrice,
currencyOrderInfo.lastMaxOrderPriceUpdatedTime);
_currencyOrderInfoMap.insert_or_assign(CurrencyCode(currencyCodeStr), std::move(currencyOrderInfo));
}
}
}

Expand Down Expand Up @@ -757,17 +756,21 @@ PlaceOrderInfo BithumbPrivate::placeOrder(MonetaryAmount /*from*/, MonetaryAmoun
if (LoadCurrencyInfoField(result, kNbDecimalsStr, currencyOrderInfo.nbDecimals,
currencyOrderInfo.lastNbDecimalsUpdatedTime)) {
volume.truncate(currencyOrderInfo.nbDecimals);
if (volume == 0) {
log::warn("No trade of {} into {} because volume is 0 after truncating to {} decimals", volume,
toCurrencyCode, static_cast<int>(currencyOrderInfo.nbDecimals));
break;
}
placePostData.set("units", volume.amountStr());
} else if (LoadCurrencyInfoField(result, kMinOrderSizeJsonKeyStr, currencyOrderInfo.minOrderSize,
currencyOrderInfo.lastMinOrderSizeUpdatedTime)) {
if (isSimulationWithRealOrder && currencyOrderInfo.minOrderSize.currencyCode() == price.currencyCode()) {
volume = MonetaryAmount(currencyOrderInfo.minOrderSize / price, volume.currencyCode());
placePostData.set("units", volume.amountStr());
} else {
if (!isSimulationWithRealOrder || currencyOrderInfo.minOrderSize.currencyCode() != price.currencyCode()) {
log::warn("No trade of {} into {} because min order size is {} for this market", volume, toCurrencyCode,
currencyOrderInfo.minOrderSize);
break;
}
volume = MonetaryAmount(currencyOrderInfo.minOrderSize / price, volume.currencyCode());
placePostData.set("units", volume.amountStr());
} else if (LoadCurrencyInfoField(result, kMinOrderPriceJsonKeyStr, currencyOrderInfo.minOrderPrice,
currencyOrderInfo.lastMinOrderPriceUpdatedTime)) {
if (isSimulationWithRealOrder) {
Expand Down
69 changes: 69 additions & 0 deletions src/api/exchanges/test/bithumb_place_order_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include <gtest/gtest.h>

#include "apikeysprovider.hpp"
#include "bithumbprivateapi.hpp"
#include "bithumbpublicapi.hpp"
#include "coincenterinfo.hpp"
#include "fiatconverter.hpp"

namespace cct::api {
class BithumbPrivateAPIPlaceOrderTest : public ::testing::Test {
protected:
PlaceOrderInfo placeOrder(MonetaryAmount volume, MonetaryAmount price, TradeSide tradeSide) {
Market market{volume.currencyCode(), price.currencyCode()};
TradeContext tradeContext{market, tradeSide};
TradeOptions tradeOptions;
TradeInfo tradeInfo{tradeContext, tradeOptions};

return exchangePrivate.placeOrder(from, volume, price, tradeInfo);
}

void setOverridenQueryResponses(const std::map<string, string>& queryResponses) {
exchangePrivate._curlHandle.setOverridenQueryResponses(queryResponses);
}

settings::RunMode runMode = settings::RunMode::kQueryResponseOverriden;
LoadConfiguration loadConfig{kDefaultDataDir, LoadConfiguration::ExchangeConfigFileType::kTest};
CoincenterInfo coincenterInfo{runMode, loadConfig};
FiatConverter fiatConverter{coincenterInfo, Duration::max()}; // max to avoid real Fiat converter queries
CryptowatchAPI cryptowatchAPI{coincenterInfo, runMode};
BithumbPublic exchangePublic{coincenterInfo, fiatConverter, cryptowatchAPI};
APIKeysProvider apiKeysProvider{coincenterInfo.dataDir(), coincenterInfo.getRunMode()};
ExchangeName exchangeName{exchangePublic.name(), apiKeysProvider.getKeyNames(exchangePublic.name()).front()};
const APIKey& testKey = apiKeysProvider.get(exchangeName);
BithumbPrivate exchangePrivate{coincenterInfo, exchangePublic, testKey};

MonetaryAmount from;
};

TEST_F(BithumbPrivateAPIPlaceOrderTest, PlaceOrderShortenDecimals) {
setOverridenQueryResponses(
{/// Place order, with high number of decimals
{"/trade/"
"place?endpoint=%2Ftrade%2Fplace&order_currency=ETH&payment_currency=EUR&type=ask&price=1500&units=2.000001",
"{\"status\": \"5600\", \"message\":\"수량은 소수점 4자\"}"},
/// Replace order with decimals correctly truncated
{"/trade/"
"place?endpoint=%2Ftrade%2Fplace&order_currency=ETH&payment_currency=EUR&type=ask&price=1500&units=2",
"{\"status\": \"0000\", \"order_id\": \"ID0001\"}"},
/// Query once order info, order not matched
{"/info/orders?endpoint=%2Finfo%2Forders&order_currency=ETH&payment_currency=EUR&type=ask&order_id=ID0001",
"{\"status\": \"0000\", \"data\": [{\"order_id\": \"ID0001\"}]}"}});

PlaceOrderInfo placeOrderInfo =
placeOrder(MonetaryAmount("2.000001ETH"), MonetaryAmount("1500EUR"), TradeSide::kSell);
EXPECT_EQ(placeOrderInfo.orderId, "ID0001");
}

TEST_F(BithumbPrivateAPIPlaceOrderTest, NoPlaceOrderTooSmallAmount) {
setOverridenQueryResponses(
{/// Place order, with high number of decimals
{"/trade/"
"place?endpoint=%2Ftrade%2Fplace&order_currency=ETH&payment_currency=EUR&type=ask&price=1500&units=0.000001",
"{\"status\": \"5600\", \"message\":\"수량은 소수점 4자\"}"}});

PlaceOrderInfo placeOrderInfo =
placeOrder(MonetaryAmount("0.000001ETH"), MonetaryAmount("1500EUR"), TradeSide::kSell);
EXPECT_EQ(placeOrderInfo.orderId, "UndefinedId");
}
} // namespace cct::api

0 comments on commit 1cc12e0

Please sign in to comment.