Skip to content

Commit

Permalink
Simplify centralized trade algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
sjanel committed Feb 19, 2023
1 parent d6b075f commit e4e0dd6
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 87 deletions.
121 changes: 61 additions & 60 deletions src/api/common/src/exchangeprivateapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,75 +82,101 @@ TradedAmounts ExchangePrivate::trade(MonetaryAmount from, CurrencyCode toCurrenc

TradedAmounts ExchangePrivate::marketTrade(MonetaryAmount from, const TradeOptions &options, Market mk) {
const CurrencyCode fromCurrency = from.currencyCode();

std::optional<MonetaryAmount> optPrice = _exchangePublic.computeAvgOrderPrice(mk, from, options.priceOptions());
const CurrencyCode toCurrency = mk.opposite(fromCurrency);
TradedAmounts totalTradedAmounts(fromCurrency, toCurrency);
if (!optPrice) {
log::error("Impossible to compute {} average price on {}", _exchangePublic.name(), mk);
return totalTradedAmounts;
}

MonetaryAmount price = *optPrice;
const TimePoint timerStart = Clock::now();
const UserRefInt userRef =
static_cast<UserRefInt>(TimestampToS(timerStart) % static_cast<int64_t>(std::numeric_limits<UserRefInt>::max()));
const bool noEmergencyTime = options.maxTradeTime() == Duration::max();

const TradeSide side = fromCurrency == mk.base() ? TradeSide::kSell : TradeSide::kBuy;
TradeContext tradeContext(mk, side, userRef);
TradeInfo tradeInfo(tradeContext, options);
PlaceOrderInfo placeOrderInfo = placeOrderProcess(from, price, tradeInfo);

if (placeOrderInfo.isClosed()) {
log::debug("Order {} closed with traded amounts {}", placeOrderInfo.orderId, placeOrderInfo.tradedAmounts());
return placeOrderInfo.tradedAmounts();
}
enum class NextAction : int8_t { kPlaceInitialOrder, kPlaceLimitOrder, kPlaceMarketOrder, kWait };

TimePoint lastPriceUpdateTime;
MonetaryAmount price;
MonetaryAmount lastPrice;

OrderId orderId;

TradedAmounts totalTradedAmounts(fromCurrency, toCurrency);
for (NextAction nextAction = NextAction::kPlaceInitialOrder;;) {
switch (nextAction) {
case NextAction::kWait:
// Do nothing
break;
case NextAction::kPlaceMarketOrder:
tradeInfo.options.switchToTakerStrategy();
log::info("Reached emergency time, make a last taker order");
[[fallthrough]];
case NextAction::kPlaceInitialOrder: {
std::optional<MonetaryAmount> optAvgPrice =
_exchangePublic.computeAvgOrderPrice(mk, from, tradeInfo.options.priceOptions());
if (!optAvgPrice) {
log::error("Impossible to compute {} average price on {}", _exchangePublic.name(), mk);
// It's fine to return from there as we don't have a pending order still opened
return totalTradedAmounts;
}
price = *optAvgPrice;
[[fallthrough]];
}
case NextAction::kPlaceLimitOrder:
[[fallthrough]];
default: {
PlaceOrderInfo placeOrderInfo = placeOrderProcess(from, price, tradeInfo);

orderId = std::move(placeOrderInfo.orderId);

if (placeOrderInfo.isClosed()) {
totalTradedAmounts += placeOrderInfo.tradedAmounts();
log::debug("Order {} closed with last traded amounts {}", orderId, placeOrderInfo.tradedAmounts());
return totalTradedAmounts;
}

TimePoint lastPriceUpdateTime = Clock::now();
MonetaryAmount lastPrice = price;
lastPrice = price;
lastPriceUpdateTime = Clock::now();
nextAction = NextAction::kWait;
break;
}
}

while (true) {
OrderInfo orderInfo = queryOrderInfo(placeOrderInfo.orderId, tradeContext);
OrderInfo orderInfo = queryOrderInfo(orderId, tradeContext);
if (orderInfo.isClosed) {
totalTradedAmounts += orderInfo.tradedAmounts;
log::debug("Order {} closed with last traded amounts {}", placeOrderInfo.orderId, orderInfo.tradedAmounts);
log::debug("Order {} closed with last traded amounts {}", orderId, orderInfo.tradedAmounts);
break;
}

enum class NextAction : int8_t { kPlaceMarketOrder, kNewOrderLimitPrice, kWait };
NextAction nextAction = NextAction::kWait;

TimePoint nowTime = Clock::now();

const bool reachedEmergencyTime =
!noEmergencyTime && timerStart + options.maxTradeTime() < nowTime + std::chrono::seconds(1);
const bool reachedEmergencyTime = options.maxTradeTime() < TimeInS(1) + nowTime - timerStart;
bool updatePriceNeeded = false;
if (!options.isFixedPrice() && !reachedEmergencyTime &&
lastPriceUpdateTime + options.minTimeBetweenPriceUpdates() < nowTime) {
options.minTimeBetweenPriceUpdates() < nowTime - lastPriceUpdateTime) {
// Let's see if we need to change the price if limit price has changed.
optPrice = _exchangePublic.computeLimitOrderPrice(mk, fromCurrency, options.priceOptions());
if (optPrice) {
price = *optPrice;
std::optional<MonetaryAmount> optLimitPrice =
_exchangePublic.computeLimitOrderPrice(mk, fromCurrency, options.priceOptions());
if (optLimitPrice) {
price = *optLimitPrice;
updatePriceNeeded =
(side == TradeSide::kSell && price < lastPrice) || (side == TradeSide::kBuy && price > lastPrice);
}
}
if (reachedEmergencyTime || updatePriceNeeded) {
// Cancel
log::debug("Cancel order {}", placeOrderInfo.orderId);
OrderInfo cancelledOrderInfo = cancelOrder(placeOrderInfo.orderId, tradeContext);
log::debug("Cancel order {}", orderId);
OrderInfo cancelledOrderInfo = cancelOrder(orderId, tradeContext);
totalTradedAmounts += cancelledOrderInfo.tradedAmounts;
from -= cancelledOrderInfo.tradedAmounts.tradedFrom;
if (from == 0) {
log::debug("Order {} matched with last traded amounts {} while cancelling", placeOrderInfo.orderId,
log::debug("Order {} matched with last traded amounts {} while cancelling", orderId,
cancelledOrderInfo.tradedAmounts);
break;
}

if (reachedEmergencyTime) {
// timeout. Action depends on Strategy
if (timerStart + options.maxTradeTime() < nowTime) {
if (options.maxTradeTime() < nowTime - timerStart) {
log::warn("Time out reached, stop from there");
break;
}
Expand All @@ -162,33 +188,8 @@ TradedAmounts ExchangePrivate::marketTrade(MonetaryAmount from, const TradeOptio
}
} else {
// updatePriceNeeded
nextAction = NextAction::kNewOrderLimitPrice;
}
if (nextAction != NextAction::kWait) {
if (nextAction == NextAction::kPlaceMarketOrder) {
tradeInfo.options.switchToTakerStrategy();
optPrice = _exchangePublic.computeAvgOrderPrice(mk, from, tradeInfo.options.priceOptions());
if (!optPrice) {
throw exception("Impossible to compute new average order price");
}
price = *optPrice;
log::info("Reached emergency time, make a last taker order at price {}", price);
} else {
lastPriceUpdateTime = Clock::now();
log::info("Limit price changed from {} to {}, update order", lastPrice, price);
}

lastPrice = price;

// Compute new volume (price is either not needed in taker order, or already recomputed)
placeOrderInfo = placeOrderProcess(from, price, tradeInfo);

if (placeOrderInfo.isClosed()) {
totalTradedAmounts += placeOrderInfo.tradedAmounts();
log::debug("Order {} closed with last traded amounts {}", placeOrderInfo.orderId,
placeOrderInfo.tradedAmounts());
break;
}
nextAction = NextAction::kPlaceLimitOrder;
log::info("Limit price changed from {} to {}, update order", lastPrice, price);
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/tech/src/unitsparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ int64_t ParseNumberOfBytes(std::string_view sizeStr) {
if (endPos == std::string_view::npos) {
endPos = sizeStr.size();
}
int64_t v = FromString<int64_t>(std::string_view(sizeStr.begin(), sizeStr.begin() + endPos));
if (v < 0) {
int64_t nbBytes = FromString<int64_t>(std::string_view(sizeStr.begin(), sizeStr.begin() + endPos));
if (nbBytes < 0) {
throw exception("Number of bytes cannot be negative");
}
int64_t multiplier = 1;
Expand Down Expand Up @@ -40,7 +40,7 @@ int64_t ParseNumberOfBytes(std::string_view sizeStr) {
}
}

return v * multiplier;
return nbBytes * multiplier;
}

} // namespace cct
48 changes: 24 additions & 24 deletions src/tech/test/flatkeyvaluestring_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ TEST(FlatKeyValueStringTest, WithNullTerminatingCharAsSeparator) {
EXPECT_EQ(kvPairs.str(), std::string_view("tata:abc\0huhu:haha\0&newField:&&newValue&&"sv));

int kvPairPos = 0;
for (const auto &[k, v] : kvPairs) {
const char *kvPairPtr = k.data();
for (const auto &[key, val] : kvPairs) {
const char *kvPairPtr = key.data();
switch (kvPairPos++) {
case 0:
ASSERT_STREQ(kvPairPtr, "tata:abc");
Expand Down Expand Up @@ -137,40 +137,40 @@ TEST_F(CurlOptionsCase1, Get) {
}

TEST_F(CurlOptionsCase1, Iterator) {
int i = 0;
for (const auto &[k, v] : kvPairs) {
switch (i++) {
int itPos = 0;
for (const auto &[key, val] : kvPairs) {
switch (itPos++) {
case 0:
EXPECT_EQ(k, "units");
EXPECT_EQ(v, "0.11176");
EXPECT_EQ(key, "units");
EXPECT_EQ(val, "0.11176");
break;
case 1:
EXPECT_EQ(k, "price");
EXPECT_EQ(v, "357.78");
EXPECT_EQ(key, "price");
EXPECT_EQ(val, "357.78");
break;
case 2:
EXPECT_EQ(k, "777");
EXPECT_EQ(v, "encoredutravail?");
EXPECT_EQ(key, "777");
EXPECT_EQ(val, "encoredutravail?");
break;
case 3:
EXPECT_EQ(k, "hola");
EXPECT_EQ(v, "quetal");
EXPECT_EQ(key, "hola");
EXPECT_EQ(val, "quetal");
break;
case 4:
EXPECT_EQ(k, "array1");
EXPECT_EQ(v, "val1,,");
EXPECT_EQ(key, "array1");
EXPECT_EQ(val, "val1,,");
break;
case 5:
EXPECT_EQ(k, "array2");
EXPECT_EQ(v, ",val1,val2,value,");
EXPECT_EQ(key, "array2");
EXPECT_EQ(val, ",val1,val2,value,");
break;
case 6:
EXPECT_EQ(k, "emptyArray");
EXPECT_EQ(v, ",");
EXPECT_EQ(key, "emptyArray");
EXPECT_EQ(val, ",");
break;
}
}
EXPECT_EQ(i, 7);
EXPECT_EQ(itPos, 7);
}

TEST_F(CurlOptionsCase1, ConvertToJson) {
Expand Down Expand Up @@ -209,8 +209,8 @@ TEST_F(CurlOptionsCase1, ConvertToJson) {
TEST_F(CurlOptionsCase1, AppendIntegralValues) {
kvPairs.append("price1", 1957386078376L);
EXPECT_EQ(kvPairs.get("price1"), "1957386078376");
int8_t s = -116;
kvPairs.append("testu", s);
int8_t val = -116;
kvPairs.append("testu", val);
EXPECT_EQ(kvPairs.get("testu"), "-116");
}

Expand All @@ -223,8 +223,8 @@ TEST_F(CurlOptionsCase1, SetIntegralValues) {
EXPECT_EQ(
kvPairs.str(),
"units=0.11176&price=357.78&777=-666&hola=quetal&array1=val1,,&array2=,val1,val2,value,&emptyArray=,&price1=42");
int8_t s = -116;
kvPairs.set("testu", s);
int8_t val = -116;
kvPairs.set("testu", val);
EXPECT_EQ(kvPairs.get("testu"), "-116");
}

Expand Down

0 comments on commit e4e0dd6

Please sign in to comment.