Skip to content

Commit

Permalink
[Feature] - Withdrawal improvements (deposit id added, withdrawal sim…
Browse files Browse the repository at this point in the history
…ulation mode)
  • Loading branch information
sjanel committed Apr 22, 2024
1 parent dcb31a5 commit f5407c5
Show file tree
Hide file tree
Showing 21 changed files with 321 additions and 187 deletions.
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ Main features:
- [Trade asynchronous mode](#trade-asynchronous-mode)
- [Trade Price Strategy](#trade-price-strategy)
- [Trade Price Picker](#trade-price-picker)
- [Absolute price](#absolute-price)
- [Relative price](#relative-price)
- [Trade all](#trade-all)
- [Examples with explanation](#examples-with-explanation)
- [Multi Trade](#multi-trade)
Expand All @@ -124,6 +126,7 @@ Main features:
- [Withdraw options](#withdraw-options)
- [Withdraw refresh time](#withdraw-refresh-time)
- [Withdraw asynchronous mode](#withdraw-asynchronous-mode)
- [Simulation mode](#simulation-mode)
- [Dust sweeper](#dust-sweeper)
- [Syntax](#syntax-2)
- [Standard - full information](#standard---full-information-2)
Expand Down Expand Up @@ -678,7 +681,7 @@ Use command line option `trade` to make a trade from a departure amount.

In order to control more precisely the price of the order, `coincenter` supports custom price as well thanks to `--price` option. Note that this is not compatible with above **trade price strategy** option.
`--price` expects either an integer (!= 0) representing a **relative** price compared to the limit price, or a monetary amount representing an **absolute** fixed price (decimal amount with the price currency).
In fact, parser will recognize a relative price if the amount is without any decimals and without any currency. The price currency will be overriden by the engine, but you can add it for readability and to remove ambiguity of an integer price for the parser.
In fact, parser will recognize a relative price if the amount is without any decimals and without any currency. The price currency will be overridden by the engine, but you can add it for readability and to remove ambiguity of an integer price for the parser.

| `--price` value examples | Explanation |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
Expand All @@ -687,7 +690,7 @@ In fact, parser will recognize a relative price if the amount is without any dec
| `34.6` | *absolute* price (engine will take the currency from the market of the trade) |
| `25 XXX` | *absolute* price (engine will override the currency from the market of the trade and not consider `XXX`, no error will be raised) |

####### Absolute price
###### Absolute price

When requesting an absolute price, the order will be placed exactly at this price. Depending on the order book and the limit prices, order may or may not be matched instantly. Double check the price before launching your trade command!

Expand All @@ -696,7 +699,7 @@ When requesting an absolute price, the order will be placed exactly at this pric
- Such an order will not be compatible with [multi trade](#multi-trade) because an absolute price makes sense only for a specific market. However, if you ask a multi trade with a fixed price, no error will be raised, and `coincenter` will simply launch a single trade.
- Order price will not be continuously updated over time

####### Relative price
###### Relative price

The **relative price** makes it possible to easily settle a price anywhere in the orderbook. The chosen price is **relative** to the order book limit price - it represents how far away the price is related to the 'center' prices. The higher (or lower) the value, the more difficult it will be for the order to be bought - or sold.

Expand Down Expand Up @@ -1030,6 +1033,16 @@ Defaults to 5 seconds.
By default `coincenter` will exit withdraw process only once destination has received the funds.
You can change the behavior to an **asynchronous** mode thanks to `--async` option, which is like a fire and forget mode. Once the withdraw is initiated, withdraw process is finished and the withdraw is not followed anymore.

###### Simulation mode

Like trade, it is possible to launch a simulated withdrawal with `--sim` option (to test deposit information retrieval and withdrawal status of currency at source exchange for instance).

Example: Simulated withdrawal of 50 % of available BTC from account `user1` of Kraken to Upbit:

```bash
coincenter withdraw-apply 50%btc,kraken_user1-upbit --sim
```

#### Dust sweeper

If you are annoyed with this kind of balance:
Expand Down
25 changes: 18 additions & 7 deletions src/api-objects/include/recentdeposit.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,35 @@ class RecentDeposit {
TimePoint timePoint() const { return _timePoint; }

private:
friend class ClosestRecentDepositPicker;

MonetaryAmount _amount;
TimePoint _timePoint;
int _originalPos = -1;
};

class ClosestRecentDepositPicker {
private:
using RecentDepositVector = SmallVector<RecentDeposit, 4>;

public:
using value_type = RecentDepositVector::value_type;
using size_type = RecentDepositVector::size_type;

ClosestRecentDepositPicker() noexcept = default;

void addDeposit(const RecentDeposit &recentDeposit);
void push_back(const RecentDeposit &recentDeposit);

/// Given deposit information in parameters, return a RecentDeposit corresponding to the closest
/// deposit. Otherwise, if no matching deposit is found (added previously thanks to 'addDeposit' method),
/// a default RecentDeposit is returned.
RecentDeposit pickClosestRecentDepositOrDefault(const RecentDeposit &expectedDeposit);
void push_back(RecentDeposit &&recentDeposit);

private:
using RecentDepositVector = SmallVector<RecentDeposit, 4>;
void reserve(size_type sz) { _recentDeposits.reserve(sz); }

/// Given deposit information in parameters, return a position (0 indexed) corresponding to the closest
/// deposit that was pushed back. Otherwise, if no matching deposit is found (added previously thanks to 'push_back'
/// method), -1 is returned
int pickClosestRecentDepositPos(const RecentDeposit &expectedDeposit);

private:
/// Select the RecentDeposit among given ones which is the closest to 'this' object.
/// It may reorder the given vector but will not modify objects themselves.
/// Returns nullptr if no matching deposit has been found
Expand Down
39 changes: 32 additions & 7 deletions src/api-objects/include/withdrawinfo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,37 +59,62 @@ class SentWithdrawInfo {
Withdraw::Status _withdrawStatus = Withdraw::Status::kInitial;
};

class ReceivedWithdrawInfo {
public:
ReceivedWithdrawInfo() noexcept = default;

ReceivedWithdrawInfo(string depositId, MonetaryAmount receivedAmount, TimePoint receivedTime = Clock::now())
: _depositId(std::move(depositId)), _receivedAmount(receivedAmount), _receivedTime(receivedTime) {}

std::string_view depositId() const { return _depositId; }

MonetaryAmount receivedAmount() const { return _receivedAmount; }

TimePoint receivedTime() const { return _receivedTime; }

using trivially_relocatable = is_trivially_relocatable<string>::type;

private:
string _depositId;
MonetaryAmount _receivedAmount;
TimePoint _receivedTime; // time at which destination provides received funds as available for trade
};

} // namespace api

class DeliveredWithdrawInfo {
public:
/// Empty withdraw info, when no withdrawal has been done
explicit DeliveredWithdrawInfo(string &&msg = string()) : _initiatedWithdrawInfo(std::move(msg)) {}
DeliveredWithdrawInfo() = default;

/// Empty withdraw info, when no withdrawal has been done
explicit DeliveredWithdrawInfo(string &&msg) : _initiatedWithdrawInfo(std::move(msg)) {}

/// Constructs a withdraw info with all information
DeliveredWithdrawInfo(api::InitiatedWithdrawInfo &&initiatedWithdrawInfo, MonetaryAmount receivedAmount,
TimePoint receivedTime = Clock::now());
DeliveredWithdrawInfo(api::InitiatedWithdrawInfo &&initiatedWithdrawInfo,
api::ReceivedWithdrawInfo &&receivedWithdrawInfo);

TimePoint initiatedTime() const { return _initiatedWithdrawInfo.initiatedTime(); }

bool hasBeenInitiated() const { return initiatedTime() != TimePoint{}; }

TimePoint receivedTime() const { return _receivedTime; }
TimePoint receivedTime() const { return _receivedWithdrawInfo.receivedTime(); }

const Wallet &receivingWallet() const { return _initiatedWithdrawInfo.receivingWallet(); }

MonetaryAmount grossAmount() const { return _initiatedWithdrawInfo.grossEmittedAmount(); }

MonetaryAmount receivedAmount() const { return _receivedAmount; }
MonetaryAmount receivedAmount() const { return _receivedWithdrawInfo.receivedAmount(); }

std::string_view withdrawId() const;

std::string_view depositId() const { return _receivedWithdrawInfo.depositId(); }

using trivially_relocatable = is_trivially_relocatable<api::InitiatedWithdrawInfo>::type;

private:
api::InitiatedWithdrawInfo _initiatedWithdrawInfo;
TimePoint _receivedTime; // time at which destination provides received funds as available for trade
MonetaryAmount _receivedAmount; // fee deduced amount that destination will receive
api::ReceivedWithdrawInfo _receivedWithdrawInfo;
};

} // namespace cct
Expand Down
7 changes: 6 additions & 1 deletion src/api-objects/include/withdrawoptions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@ class WithdrawOptions {
public:
constexpr WithdrawOptions() noexcept = default;

WithdrawOptions(Duration withdrawRefreshTime, WithdrawSyncPolicy withdrawSyncPolicy);
enum class Mode : int8_t { kSimulation, kReal };

WithdrawOptions(Duration withdrawRefreshTime, WithdrawSyncPolicy withdrawSyncPolicy, Mode mode);

constexpr Duration withdrawRefreshTime() const { return _withdrawRefreshTime; }

constexpr WithdrawSyncPolicy withdrawSyncPolicy() const { return _withdrawSyncPolicy; }

std::string_view withdrawSyncPolicyStr() const;

constexpr Mode mode() const { return _mode; }

bool operator==(const WithdrawOptions &) const noexcept = default;

private:
Expand All @@ -33,5 +37,6 @@ class WithdrawOptions {

Duration _withdrawRefreshTime = kWithdrawRefreshTime;
WithdrawSyncPolicy _withdrawSyncPolicy = WithdrawSyncPolicy::kSynchronous;
Mode _mode = Mode::kReal;
};
} // namespace cct
14 changes: 10 additions & 4 deletions src/api-objects/src/recentdeposit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,22 @@

namespace cct::api {

void ClosestRecentDepositPicker::addDeposit(const RecentDeposit &recentDeposit) {
void ClosestRecentDepositPicker::push_back(const RecentDeposit &recentDeposit) {
_recentDeposits.push_back(recentDeposit);
_recentDeposits.back()._originalPos = static_cast<int>(_recentDeposits.size()) - 1;
}

RecentDeposit ClosestRecentDepositPicker::pickClosestRecentDepositOrDefault(const RecentDeposit &expectedDeposit) {
void ClosestRecentDepositPicker::push_back(RecentDeposit &&recentDeposit) {
recentDeposit._originalPos = static_cast<int>(_recentDeposits.size());
_recentDeposits.push_back(std::move(recentDeposit));
}

int ClosestRecentDepositPicker::pickClosestRecentDepositPos(const RecentDeposit &expectedDeposit) {
const RecentDeposit *pClosestRecentDeposit = selectClosestRecentDeposit(expectedDeposit);
if (pClosestRecentDeposit == nullptr) {
return {};
return -1;
}
return *pClosestRecentDeposit;
return pClosestRecentDeposit->_originalPos;
}

const RecentDeposit *ClosestRecentDepositPicker::selectClosestRecentDeposit(const RecentDeposit &expectedDeposit) {
Expand Down
5 changes: 2 additions & 3 deletions src/api-objects/src/withdrawinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,9 @@ InitiatedWithdrawInfo::InitiatedWithdrawInfo(Wallet receivingWallet, std::string
} // namespace api

DeliveredWithdrawInfo::DeliveredWithdrawInfo(api::InitiatedWithdrawInfo &&initiatedWithdrawInfo,
MonetaryAmount receivedAmount, TimePoint receivedTime)
api::ReceivedWithdrawInfo &&receivedWithdrawInfo)
: _initiatedWithdrawInfo(std::move(initiatedWithdrawInfo)),
_receivedTime(receivedTime),
_receivedAmount(receivedAmount) {}
_receivedWithdrawInfo(std::move(receivedWithdrawInfo)) {}

std::string_view DeliveredWithdrawInfo::withdrawId() const {
if (!hasBeenInitiated()) {
Expand Down
4 changes: 2 additions & 2 deletions src/api-objects/src/withdrawoptions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
#include "unreachable.hpp"

namespace cct {
WithdrawOptions::WithdrawOptions(Duration withdrawRefreshTime, WithdrawSyncPolicy withdrawSyncPolicy)
: _withdrawRefreshTime(withdrawRefreshTime), _withdrawSyncPolicy(withdrawSyncPolicy) {}
WithdrawOptions::WithdrawOptions(Duration withdrawRefreshTime, WithdrawSyncPolicy withdrawSyncPolicy, Mode mode)
: _withdrawRefreshTime(withdrawRefreshTime), _withdrawSyncPolicy(withdrawSyncPolicy), _mode(mode) {}

std::string_view WithdrawOptions::withdrawSyncPolicyStr() const {
switch (_withdrawSyncPolicy) {
Expand Down
53 changes: 25 additions & 28 deletions src/api-objects/test/recentdeposit_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,32 @@ class RecentDepositTest : public ::testing::Test {
protected:
void expectNotFound(MonetaryAmount ma) {
RecentDeposit expectedDeposit(ma, refTimePoint);
EXPECT_EQ(closestRecentDepositPicker.pickClosestRecentDepositOrDefault(expectedDeposit), RecentDeposit());
EXPECT_EQ(closestRecentDepositPicker.pickClosestRecentDepositPos(expectedDeposit), -1);
}

void expectFound(MonetaryAmount ma, const RecentDeposit &expectedDeposit) {
void expectFound(MonetaryAmount ma, int expectedDepositPos) {
RecentDeposit tested(ma, refTimePoint);
EXPECT_EQ(closestRecentDepositPicker.pickClosestRecentDepositOrDefault(tested), expectedDeposit);
EXPECT_EQ(closestRecentDepositPicker.pickClosestRecentDepositPos(tested), expectedDepositPos);
}

void setRecentDepositsSameAmount() {
closestRecentDepositPicker.addDeposit(RecentDeposit(MonetaryAmount(10), refTimePoint - std::chrono::days(4)));
closestRecentDepositPicker.addDeposit(RecentDeposit(MonetaryAmount(10), refTimePoint - std::chrono::days(3)));
closestRecentDepositPicker.addDeposit(RecentDeposit(MonetaryAmount(10), refTimePoint - std::chrono::hours(50)));
closestRecentDepositPicker.addDeposit(RecentDeposit(MonetaryAmount(10), refTimePoint - std::chrono::hours(52)));
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount(10), refTimePoint - std::chrono::days(4)));
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount(10), refTimePoint - std::chrono::days(3)));
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount(10), refTimePoint - std::chrono::hours(50)));
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount(10), refTimePoint - std::chrono::hours(52)));
}

void setRecentDepositsDifferentAmounts() {
closestRecentDepositPicker.addDeposit(RecentDeposit(MonetaryAmount(37), refTimePoint - seconds(6)));
closestRecentDepositPicker.addDeposit(RecentDeposit(MonetaryAmount("37.5"), refTimePoint - std::chrono::hours(2)));
closestRecentDepositPicker.addDeposit(RecentDeposit(MonetaryAmount(32), refTimePoint - std::chrono::hours(8)));
closestRecentDepositPicker.addDeposit(RecentDeposit(MonetaryAmount(32), refTimePoint - std::chrono::hours(1)));
closestRecentDepositPicker.addDeposit(RecentDeposit(MonetaryAmount(38), refTimePoint - std::chrono::hours(12)));
closestRecentDepositPicker.addDeposit(RecentDeposit(MonetaryAmount(38), refTimePoint - std::chrono::hours(1)));
closestRecentDepositPicker.addDeposit(RecentDeposit(MonetaryAmount(33), refTimePoint - std::chrono::minutes(1)));
closestRecentDepositPicker.addDeposit(
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount(37), refTimePoint - seconds(6)));
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount("37.5"), refTimePoint - std::chrono::hours(2)));
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount(32), refTimePoint - std::chrono::hours(8)));
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount(32), refTimePoint - std::chrono::hours(1)));
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount(38), refTimePoint - std::chrono::hours(12)));
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount(38), refTimePoint - std::chrono::hours(1)));
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount(33), refTimePoint - std::chrono::minutes(1)));
closestRecentDepositPicker.push_back(
RecentDeposit(MonetaryAmount("33.1"), refTimePoint - std::chrono::minutes(12)));
closestRecentDepositPicker.addDeposit(RecentDeposit(MonetaryAmount("27.5"), refTimePoint - std::chrono::days(3)));
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount("27.5"), refTimePoint - std::chrono::days(3)));
}

TimePoint refTimePoint{Clock::now()};
Expand All @@ -58,29 +58,27 @@ TEST_F(RecentDepositTest, ExactAmount) {
setRecentDepositsSameAmount();
expectNotFound(MonetaryAmount(10));
RecentDeposit newDeposit(MonetaryAmount(10), refTimePoint - std::chrono::hours(20));
closestRecentDepositPicker.addDeposit(newDeposit);
expectFound(MonetaryAmount(10), newDeposit);
closestRecentDepositPicker.push_back(newDeposit);
expectFound(MonetaryAmount(10), 4);
}

TEST_F(RecentDepositTest, CloseAmount) {
setRecentDepositsSameAmount();
expectNotFound(MonetaryAmount("10.001"));
RecentDeposit newDeposit(MonetaryAmount(10), refTimePoint - std::chrono::hours(30));
closestRecentDepositPicker.addDeposit(newDeposit);
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount(10), refTimePoint - std::chrono::hours(30)));
expectNotFound(MonetaryAmount("10.001"));
newDeposit = RecentDeposit(MonetaryAmount(10), refTimePoint - std::chrono::hours(20));
closestRecentDepositPicker.addDeposit(newDeposit);
expectFound(MonetaryAmount("10.001"), newDeposit);
closestRecentDepositPicker.push_back(RecentDeposit(MonetaryAmount(10), refTimePoint - std::chrono::hours(20)));
expectFound(MonetaryAmount("10.001"), 5);
}

TEST_F(RecentDepositTest, SelectClosestRecentDepositExactAmount1) {
setRecentDepositsDifferentAmounts();
expectFound(MonetaryAmount("37.5"), RecentDeposit(MonetaryAmount("37.5"), refTimePoint - std::chrono::hours(2)));
expectFound(MonetaryAmount("37.5"), 1);
}

TEST_F(RecentDepositTest, SelectClosestRecentDepositExactAmount2) {
setRecentDepositsDifferentAmounts();
expectFound(MonetaryAmount(32), RecentDeposit(MonetaryAmount(32), refTimePoint - std::chrono::hours(1)));
expectFound(MonetaryAmount(32), 3);
}

TEST_F(RecentDepositTest, SelectClosestRecentDepositExactAmountButTooOld) {
Expand All @@ -90,7 +88,7 @@ TEST_F(RecentDepositTest, SelectClosestRecentDepositExactAmountButTooOld) {

TEST_F(RecentDepositTest, SelectClosestRecentDepositCloseToAmount1) {
setRecentDepositsDifferentAmounts();
expectFound(MonetaryAmount("37.501"), RecentDeposit(MonetaryAmount("37.5"), refTimePoint - std::chrono::hours(2)));
expectFound(MonetaryAmount("37.501"), 1);
}

TEST_F(RecentDepositTest, SelectClosestRecentDepositCloseToAmount2) {
Expand All @@ -100,8 +98,7 @@ TEST_F(RecentDepositTest, SelectClosestRecentDepositCloseToAmount2) {

TEST_F(RecentDepositTest, SelectClosestRecentDepositCloseToAmount3) {
setRecentDepositsDifferentAmounts();
expectFound(MonetaryAmount("33.0998"),
RecentDeposit(MonetaryAmount("33.1"), refTimePoint - std::chrono::minutes(12)));
expectFound(MonetaryAmount("33.0998"), 7);
}

} // namespace cct::api
4 changes: 2 additions & 2 deletions src/api/common/include/exchangeprivateapi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,8 +159,8 @@ class ExchangePrivate : public ExchangeBase {

/// Check if withdraw has been received by 'this' exchange.
/// If so, return a non-default MonetaryAmount with the net received amount
virtual MonetaryAmount queryWithdrawDelivery(const InitiatedWithdrawInfo &initiatedWithdrawInfo,
const SentWithdrawInfo &sentWithdrawInfo);
virtual ReceivedWithdrawInfo queryWithdrawDelivery(const InitiatedWithdrawInfo &initiatedWithdrawInfo,
const SentWithdrawInfo &sentWithdrawInfo);

TradedAmounts marketTrade(MonetaryAmount from, const TradeOptions &tradeOptions, Market mk);

Expand Down
Loading

0 comments on commit f5407c5

Please sign in to comment.