Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b931d83

Browse files
committedNov 19, 2024·
Live auto trade mode
1 parent 1d25e51 commit b931d83

22 files changed

+699
-9
lines changed
 

‎data/static/auto-trade-example.json

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
{
2+
"kraken": {
3+
"BTC-EUR": {
4+
"accounts": [
5+
"user1",
6+
"user2"
7+
],
8+
"algorithmName": "example-trader",
9+
"repeatTime": "5s",
10+
"baseStartAmount": "0.5BTC",
11+
"quoteStartAmount": "50%EUR",
12+
"stopCriteria": [
13+
{
14+
"type": "duration",
15+
"value": "4h"
16+
},
17+
{
18+
"type": "protectLoss",
19+
"value": "-30%"
20+
},
21+
{
22+
"type": "secureProfit",
23+
"value": "80%"
24+
}
25+
]
26+
},
27+
"ETH-EUR": {
28+
"accounts": [
29+
"user1"
30+
],
31+
"algorithmName": "example-trader",
32+
"repeatTime": "3s",
33+
"baseStartAmount": "45ETH",
34+
"quoteStartAmount": "50%EUR",
35+
"stopCriteria": [
36+
{
37+
"type": "duration",
38+
"value": "4h"
39+
},
40+
{
41+
"type": "protectLoss",
42+
"value": "-30%"
43+
},
44+
{
45+
"type": "secureProfit",
46+
"value": "80%"
47+
}
48+
]
49+
}
50+
},
51+
"binance": {
52+
"XRP-USDT": {
53+
"accounts": [
54+
"user1"
55+
],
56+
"algorithmName": "example-trader",
57+
"repeatTime": "1s",
58+
"baseStartAmount": "50000.56XRP",
59+
"quoteStartAmount": "100%USDT",
60+
"stopCriteria": []
61+
}
62+
}
63+
}

‎src/basic-objects/include/coincentercommandtype.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace cct {
1414
Balance, DepositInfo, OrdersClosed, OrdersOpened, OrdersCancel, RecentDeposits, RecentWithdraws, Trade, Buy, \
1515
Sell, Withdraw, DustSweeper, \
1616
\
17-
MarketData, Replay, ReplayMarkets
17+
MarketData, Replay, ReplayMarkets, AutoTrade
1818

1919
enum class CoincenterCommandType : int8_t { CCT_COINCENTER_COMMAND_TYPES };
2020

@@ -29,4 +29,4 @@ struct glz::meta<::cct::CoincenterCommandType> {
2929
static constexpr auto value = enumerate(CCT_COINCENTER_COMMAND_TYPES);
3030
};
3131

32-
#undef CCT_COINCENTER_COMMAND_TYPES
32+
#undef CCT_COINCENTER_COMMAND_TYPES

‎src/basic-objects/include/market.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ struct from<JSON, ::cct::Market> {
163163
static void op(auto &&value, is_context auto &&, It &&it, End &&end) noexcept {
164164
// used as a value. As a key, the first quote will not be present.
165165
auto endIt = std::find(*it == '"' ? ++it : it, end, '"');
166-
value = std::string_view(it, endIt);
166+
value = ::cct::Market(std::string_view(it, endIt));
167167
it = ++endIt;
168168
}
169169
};

‎src/basic-objects/test/market_test.cpp

+43
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <gtest/gtest.h>
44

55
#include "cct_exception.hpp"
6+
#include "cct_json-serialization.hpp"
67
#include "currencycode.hpp"
78

89
namespace cct {
@@ -65,4 +66,46 @@ TEST(MarketTest, StrLen) {
6566
market = Market("1INCH", "EUR", Market::Type::kFiatConversionMarket);
6667
EXPECT_EQ(market.strLen(), 10);
6768
}
69+
70+
struct Foo {
71+
bool operator==(const Foo &) const noexcept = default;
72+
73+
Market market;
74+
};
75+
76+
TEST(MarketTest, JsonSerializationValue) {
77+
Foo foo{Market{"DOGE", "BTC"}};
78+
79+
string buffer;
80+
auto res = json::write<json::opts{.raw_string = true}>(foo, buffer); // NOLINT(readability-implicit-bool-conversion)
81+
82+
EXPECT_FALSE(res);
83+
84+
EXPECT_EQ(buffer, R"({"market":"DOGE-BTC"})");
85+
}
86+
87+
using MarketMap = std::map<Market, bool>;
88+
89+
TEST(MarketTest, JsonSerializationKey) {
90+
MarketMap map{{Market{"DOGE", "BTC"}, true}, {Market{"BTC", "ETH"}, false}};
91+
92+
string buffer;
93+
auto res = json::write<json::opts{.raw_string = true}>(map, buffer); // NOLINT(readability-implicit-bool-conversion)
94+
95+
EXPECT_FALSE(res);
96+
97+
EXPECT_EQ(buffer, R"({"BTC-ETH":false,"DOGE-BTC":true})");
98+
}
99+
100+
TEST(MarketTest, JsonDeserialization) {
101+
Foo foo;
102+
103+
// NOLINTNEXTLINE(readability-implicit-bool-conversion)
104+
auto ec = json::read<json::opts{.raw_string = true}>(foo, R"({"market":"DOGE-ETH"})");
105+
106+
ASSERT_FALSE(ec);
107+
108+
EXPECT_EQ(foo, Foo{Market("DOGE", "ETH")});
109+
}
110+
68111
} // namespace cct
+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#pragma once
2+
3+
#include <map>
4+
5+
#include "auto-trade-config.hpp"
6+
#include "cct_const.hpp"
7+
#include "cct_fixedcapacityvector.hpp"
8+
#include "cct_smallvector.hpp"
9+
#include "cct_vector.hpp"
10+
#include "exchange-names.hpp"
11+
#include "exchangename.hpp"
12+
13+
namespace cct {
14+
15+
class AutoTradeOptions {
16+
public:
17+
using AccountAutoTradeOptionsPtrVector =
18+
SmallVector<const schema::AutoTradeExchangeConfig *, kTypicalNbPrivateAccounts>;
19+
20+
struct MarketExchanges {
21+
Market market;
22+
ExchangeNames privateExchangeNames;
23+
const schema::AutoTradeMarketConfig *pMarketAutoTradeOptions{};
24+
};
25+
26+
using MarketStatusVector = vector<MarketExchanges>;
27+
28+
struct MarketExchangeOptions {
29+
MarketStatusVector marketStatusVector;
30+
};
31+
32+
struct PublicExchangeMarketOptions {
33+
ExchangeName publicExchangeName;
34+
MarketExchangeOptions marketExchangeOptions;
35+
};
36+
37+
using PublicExchangeMarketOptionsVector = FixedCapacityVector<schema::AutoTradeExchangeConfig, kNbSupportedExchanges>;
38+
39+
AutoTradeOptions() noexcept = default;
40+
41+
explicit AutoTradeOptions(schema::AutoTradeConfig &&autoTradeConfig);
42+
43+
auto begin() const { return _autoTradeConfig.begin(); }
44+
auto end() const { return _autoTradeConfig.end(); }
45+
46+
ExchangeNames exchangeNames() const;
47+
48+
ExchangeNameEnumVector publicExchanges() const;
49+
50+
AccountAutoTradeOptionsPtrVector accountAutoTradeOptionsPtr(std::string_view publicExchangeName) const;
51+
52+
const schema::AutoTradeExchangeConfig &operator[](ExchangeNameEnum exchangeNameEnum) const;
53+
54+
private:
55+
schema::AutoTradeConfig _autoTradeConfig;
56+
};
57+
58+
} // namespace cct
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#pragma once
2+
3+
#include <compare>
4+
#include <functional>
5+
6+
#include "auto-trade-config.hpp"
7+
#include "cct_smallvector.hpp"
8+
#include "cct_vector.hpp"
9+
#include "exchange-names.hpp"
10+
#include "exchange.hpp"
11+
#include "market.hpp"
12+
#include "timedef.hpp"
13+
14+
namespace cct {
15+
class AutoTradeOptions;
16+
17+
class AutoTradeProcessor {
18+
public:
19+
explicit AutoTradeProcessor(const AutoTradeOptions& autoTradeOptions);
20+
21+
struct SelectedMarket {
22+
ExchangeNames privateExchangeNames;
23+
Market market;
24+
};
25+
26+
using SelectedMarketVector = SmallVector<SelectedMarket, kTypicalNbPrivateAccounts>;
27+
28+
SelectedMarketVector computeSelectedMarkets();
29+
30+
private:
31+
struct MarketStatus {
32+
ExchangeNames privateExchangeNames;
33+
Market market;
34+
TimePoint lastQueryTime;
35+
const schema::AutoTradeMarketConfig* pMarketAutoTradeOptions{};
36+
};
37+
38+
using MarketStatusVector = vector<MarketStatus>;
39+
40+
struct ExchangeStatus {
41+
MarketStatusVector marketStatusVector;
42+
const schema::AutoTradeExchangeConfig* pPublicExchangeAutoTradeOptions{};
43+
};
44+
45+
using ExchangeStatusVector = SmallVector<ExchangeStatus, kTypicalNbPrivateAccounts>;
46+
47+
ExchangeStatusVector _exchangeStatusVector;
48+
TimePoint _startTs = Clock::now();
49+
TimePoint _ts{_startTs};
50+
};
51+
} // namespace cct

‎src/engine/include/coincenter.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <span>
55

66
#include "apikeysprovider.hpp"
7+
#include "auto-trade-options.hpp"
78
#include "cct_const.hpp"
89
#include "cct_fixedcapacityvector.hpp"
910
#include "coincenterinfo.hpp"
@@ -149,6 +150,9 @@ class Coincenter {
149150
ReplayResults replay(const AbstractMarketTraderFactory &marketTraderFactory, const ReplayOptions &replayOptions,
150151
Market market, ExchangeNameSpan exchangeNames);
151152

153+
/// Run auto trade.
154+
void autoTrade(const AutoTradeOptions &autoTradeOptions);
155+
152156
/// Dumps the content of all file caches in data directory to save cURL queries.
153157
void updateFileCaches() const;
154158

‎src/engine/include/coincentercommand.hpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <cstdint>
44
#include <optional>
5+
#include <string_view>
56
#include <type_traits>
67
#include <variant>
78

@@ -47,6 +48,8 @@ class CoincenterCommand {
4748

4849
CoincenterCommand& setReplayOptions(ReplayOptions replayOptions);
4950

51+
CoincenterCommand& setJsonConfigFile(std::string_view jsonConfigFile);
52+
5053
CoincenterCommand& setPercentageAmount(bool value = true);
5154
CoincenterCommand& withBalanceInUse(bool value = true);
5255

@@ -79,6 +82,8 @@ class CoincenterCommand {
7982

8083
const ReplayOptions& replayOptions() const { return std::get<ReplayOptions>(_specialOptions); }
8184

85+
std::string_view getJsonConfigFile() const { return std::get<std::string_view>(_specialOptions); }
86+
8287
bool operator==(const CoincenterCommand&) const noexcept = default;
8388

8489
using trivially_relocatable =
@@ -89,7 +94,7 @@ class CoincenterCommand {
8994

9095
private:
9196
using SpecialOptions = std::variant<std::monostate, OrdersConstraints, WithdrawsOrDepositsConstraints, TradeOptions,
92-
WithdrawOptions, ReplayOptions>;
97+
WithdrawOptions, ReplayOptions, std::string_view>;
9398

9499
ExchangeNames _exchangeNames;
95100
SpecialOptions _specialOptions;

‎src/engine/include/coincenteroptions.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ class CoincenterCmdLineOptions {
104104

105105
std::string_view marketData;
106106

107+
std::string_view autoTrade;
108+
107109
std::optional<std::string_view> replay;
108110
std::string_view algorithmNames;
109111
std::string_view market;

‎src/engine/include/coincenteroptionsdef.hpp

+16
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,22 @@ struct CoincenterAllowedOptions : private CoincenterCmdLineOptionsDefinitions {
477477
"\nNominal replay will not validate input data to optimize performance, use this option to validate data once "
478478
"and for all."},
479479
&OptValueType::validateOnly},
480+
{{{"Automation", 8004},
481+
"auto-trade",
482+
"<path/to/json.conf>",
483+
"Automatic live trading mode. Once you have validated on historical market-data the performance of an "
484+
"algorithm, it's time to try it for real!\n"
485+
"This command has some particularities:\n"
486+
"- next commands will never be executed\n"
487+
"- repeat is ignored (the auto trade will continue until one of terminating signals defined in the "
488+
"configuration file is reached)\n"
489+
"Configuration will be loaded from given json file, with following options (check README to get full "
490+
"configuration schema):\n"
491+
"- 'algorithm' : algorithm name to use\n"
492+
"- 'market' : the market to trade onto\n"
493+
"- 'startAmount' : the starting amount in base currency (can be a percentage of available amount)\n"
494+
"- 'exchange' : exchange with account key (not needed if not ambiguous)"},
495+
&OptValueType::autoTrade},
480496
{{{"Monitoring", 9000},
481497
"--monitoring",
482498
"",

‎src/engine/include/exchangesorchestrator.hpp

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include <optional>
44
#include <span>
55

6+
#include "auto-trade-options.hpp"
7+
#include "auto-trade-processor.hpp"
68
#include "exchange-names.hpp"
79
#include "exchangename.hpp"
810
#include "exchangeretriever.hpp"

‎src/engine/src/auto-trade-options.cpp

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#include "auto-trade-options.hpp"
2+
3+
#include <utility>
4+
5+
#include "auto-trade-config.hpp"
6+
#include "cct_invalid_argument_exception.hpp"
7+
#include "cct_json-container.hpp"
8+
9+
namespace cct {
10+
11+
AutoTradeOptions::AutoTradeOptions(schema::AutoTradeConfig &&autoTradeConfig)
12+
: _autoTradeConfig(std::move(autoTradeConfig)) {}
13+
14+
ExchangeNames AutoTradeOptions::exchangeNames() const {
15+
ExchangeNames exchangeNames;
16+
for (const auto &[exchangeNameEnum, publicExchangeAutoTradeOptions] : _autoTradeConfig) {
17+
const int posPublicExchangeName = exchangeNames.size();
18+
for (const auto &[market, autoTradeMarketConfig] : publicExchangeAutoTradeOptions) {
19+
const int posMarket = exchangeNames.size();
20+
for (std::string_view account : autoTradeMarketConfig.accounts) {
21+
ExchangeName exchangeName(exchangeNameEnum, account);
22+
const auto it = std::find(exchangeNames.begin() + posPublicExchangeName, exchangeNames.end(), exchangeName);
23+
if (it == exchangeNames.end()) {
24+
exchangeNames.push_back(std::move(exchangeName));
25+
} else if (it >= exchangeNames.begin() + posMarket) {
26+
throw invalid_argument("Duplicated account {} for exchange {}", account, exchangeName.name());
27+
}
28+
}
29+
}
30+
}
31+
return exchangeNames;
32+
}
33+
34+
ExchangeNameEnumVector AutoTradeOptions::publicExchanges() const {
35+
ExchangeNameEnumVector exchanges;
36+
for (const auto &[publicExchangeName, _] : _autoTradeConfig) {
37+
exchanges.emplace_back(publicExchangeName);
38+
}
39+
std::ranges::sort(exchanges);
40+
return exchanges;
41+
}
42+
43+
AutoTradeOptions::AccountAutoTradeOptionsPtrVector AutoTradeOptions::accountAutoTradeOptionsPtr(
44+
std::string_view publicExchangeName) const {
45+
AccountAutoTradeOptionsPtrVector accountAutoTradeOptionsPtr;
46+
for (const auto &[exchangeNameEnum, publicExchangeAutoTradeOptions] : _autoTradeConfig) {
47+
ExchangeName exchangeName(exchangeNameEnum);
48+
if (kSupportedExchanges[static_cast<int>(exchangeNameEnum)] == publicExchangeName) {
49+
accountAutoTradeOptionsPtr.emplace_back(&publicExchangeAutoTradeOptions);
50+
}
51+
}
52+
return accountAutoTradeOptionsPtr;
53+
}
54+
55+
const schema::AutoTradeExchangeConfig &AutoTradeOptions::operator[](ExchangeNameEnum exchangeNameEnum) const {
56+
const auto it = _autoTradeConfig.find(exchangeNameEnum);
57+
if (it == _autoTradeConfig.end()) {
58+
throw exception("No auto trade options for exchange {}", kSupportedExchanges[static_cast<int>(exchangeNameEnum)]);
59+
}
60+
return it->second;
61+
}
62+
63+
} // namespace cct
+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#include "auto-trade-processor.hpp"
2+
3+
#include <thread>
4+
5+
#include "auto-trade-options.hpp"
6+
#include "cct_exception.hpp"
7+
#include "timestring.hpp"
8+
9+
namespace cct {
10+
11+
AutoTradeProcessor::AutoTradeProcessor(const AutoTradeOptions &autoTradeOptions)
12+
: _exchangeStatusVector(autoTradeOptions.publicExchanges().size()) {
13+
int publicExchangePos = 0;
14+
for (const auto &[exchangeNameEnum, publicExchangeAutoTradeOptions] : autoTradeOptions) {
15+
ExchangeStatus &selectedExchangesStatus = _exchangeStatusVector[publicExchangePos];
16+
selectedExchangesStatus.pPublicExchangeAutoTradeOptions = &publicExchangeAutoTradeOptions;
17+
for (const auto &[market, marketAutoTradeOptions] : publicExchangeAutoTradeOptions) {
18+
MarketStatus &marketStatus = selectedExchangesStatus.marketStatusVector.emplace_back();
19+
20+
marketStatus.market = market;
21+
marketStatus.pMarketAutoTradeOptions = &marketAutoTradeOptions;
22+
23+
for (std::string_view account : marketAutoTradeOptions.accounts) {
24+
marketStatus.privateExchangeNames.emplace_back(exchangeNameEnum, account);
25+
}
26+
}
27+
++publicExchangePos;
28+
}
29+
}
30+
31+
namespace {
32+
const auto &GetAutoTradeMarketConfig(Market market, const auto &publicExchangeAutoTradeOptions) {
33+
const auto it = publicExchangeAutoTradeOptions.find(market);
34+
if (it == publicExchangeAutoTradeOptions.end()) {
35+
throw exception("Should not happen - market not found in account auto trade options");
36+
}
37+
return it->second;
38+
}
39+
40+
bool IsQueryTooEarly(TimePoint nowTs, const auto &marketStatus, const auto &publicExchangeAutoTradeOptions) {
41+
const auto &marketAutoTradeOptions = GetAutoTradeMarketConfig(marketStatus.market, publicExchangeAutoTradeOptions);
42+
return marketStatus.lastQueryTime + marketAutoTradeOptions.repeatTime.duration > nowTs;
43+
}
44+
} // namespace
45+
46+
AutoTradeProcessor::SelectedMarketVector AutoTradeProcessor::computeSelectedMarkets() {
47+
SelectedMarketVector selectedMarketVector;
48+
49+
auto ts = Clock::now();
50+
51+
TimePoint earliestQueryTime = TimePoint::max();
52+
53+
for (ExchangeStatus &exchangeStatus : _exchangeStatusVector) {
54+
const auto &publicExchangeAutoTradeOptions = *exchangeStatus.pPublicExchangeAutoTradeOptions;
55+
56+
auto &marketStatusVector = exchangeStatus.marketStatusVector;
57+
58+
if (marketStatusVector.empty()) {
59+
continue;
60+
}
61+
62+
// Sort markets by ascending last query time, discarding those (placed at the end of the vector) which cannot be
63+
// queried right now
64+
std::ranges::sort(marketStatusVector,
65+
[ts, &publicExchangeAutoTradeOptions](const MarketStatus &lhs, const MarketStatus &rhs) {
66+
const bool lhsIsTooEarly = IsQueryTooEarly(ts, lhs, publicExchangeAutoTradeOptions);
67+
const bool rhsIsTooEarly = IsQueryTooEarly(ts, rhs, publicExchangeAutoTradeOptions);
68+
69+
if (lhsIsTooEarly != rhsIsTooEarly) {
70+
return !lhsIsTooEarly;
71+
}
72+
73+
return lhs.lastQueryTime < rhs.lastQueryTime;
74+
});
75+
76+
MarketStatus &selectedMarketStatus = marketStatusVector.front();
77+
if (IsQueryTooEarly(ts, selectedMarketStatus, publicExchangeAutoTradeOptions)) {
78+
const auto repeatTime =
79+
GetAutoTradeMarketConfig(selectedMarketStatus.market, publicExchangeAutoTradeOptions).repeatTime.duration;
80+
earliestQueryTime = std::min(earliestQueryTime, selectedMarketStatus.lastQueryTime + repeatTime);
81+
continue;
82+
}
83+
84+
selectedMarketStatus.lastQueryTime = ts;
85+
selectedMarketVector.emplace_back(selectedMarketStatus.privateExchangeNames, selectedMarketStatus.market);
86+
}
87+
88+
if (selectedMarketVector.empty() && earliestQueryTime != TimePoint::max()) {
89+
log::debug("Sleeping until {}", TimeToString(earliestQueryTime));
90+
std::this_thread::sleep_until(earliestQueryTime + std::chrono::milliseconds(1));
91+
selectedMarketVector = computeSelectedMarkets();
92+
if (selectedMarketVector.empty()) {
93+
throw exception("Waiting sufficient time should return at least one market for the next turn");
94+
}
95+
}
96+
97+
return selectedMarketVector;
98+
}
99+
100+
} // namespace cct

‎src/engine/src/coincenter-commands-processor.cpp

+17-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <thread>
77
#include <utility>
88

9+
#include "auto-trade-options.hpp"
910
#include "balanceoptions.hpp"
1011
#include "cct_const.hpp"
1112
#include "cct_exception.hpp"
@@ -22,11 +23,13 @@
2223
#include "exchange-names.hpp"
2324
#include "exchangename.hpp"
2425
#include "exchangepublicapi.hpp"
26+
#include "file.hpp"
2527
#include "market-trader-factory.hpp"
2628
#include "market.hpp"
2729
#include "monetaryamount.hpp"
2830
#include "queryresultprinter.hpp"
2931
#include "queryresulttypes.hpp"
32+
#include "read-json.hpp"
3033
#include "replay-options.hpp"
3134
#include "signal-handler.hpp"
3235
#include "timedef.hpp"
@@ -294,18 +297,18 @@ TransferableCommandResultVector CoincenterCommandsProcessor::processGroupedComma
294297
break;
295298
}
296299
case CoincenterCommandType::MarketData: {
297-
std::array<Market, kNbSupportedExchanges> marketPerPublicExchange;
300+
std::array<Market, kNbSupportedExchanges> marketPerPublicExchangePos;
298301
for (const auto &cmd : groupedCommands) {
299302
if (cmd.exchangeNames().empty()) {
300-
std::ranges::fill(marketPerPublicExchange, cmd.market());
303+
std::ranges::fill(marketPerPublicExchangePos, cmd.market());
301304
} else {
302305
for (const auto &exchangeName : cmd.exchangeNames()) {
303-
marketPerPublicExchange[exchangeName.publicExchangePos()] = cmd.market();
306+
marketPerPublicExchangePos[exchangeName.publicExchangePos()] = cmd.market();
304307
}
305308
}
306309
}
307-
// No return value here, this command is made only for storing purposes.
308-
_coincenter.queryMarketDataPerExchange(marketPerPublicExchange);
310+
// No need to retrieve the returned value here, this command is made only for storing purposes.
311+
_coincenter.queryMarketDataPerExchange(marketPerPublicExchangePos);
309312
break;
310313
}
311314
case CoincenterCommandType::Replay: {
@@ -329,6 +332,15 @@ TransferableCommandResultVector CoincenterCommandsProcessor::processGroupedComma
329332
_queryResultPrinter.printMarketsForReplay(firstCmd.replayOptions().timeWindow(), marketTimestampSetsPerExchange);
330333
break;
331334
}
335+
case CoincenterCommandType::AutoTrade: {
336+
const File configFile(firstCmd.getJsonConfigFile(), File::IfError::kThrow);
337+
schema::AutoTradeConfig autoTradeConfig;
338+
ReadJsonOrThrow(configFile.readAll(), autoTradeConfig);
339+
const AutoTradeOptions autoTradeOptions(std::move(autoTradeConfig));
340+
341+
_coincenter.autoTrade(autoTradeOptions);
342+
break;
343+
}
332344
default:
333345
throw exception("Unknown command type");
334346
}

‎src/engine/src/coincenter.cpp

+26
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "coincenter.hpp"
22

33
#include <algorithm>
4+
#include <array>
45
#include <csignal>
56
#include <optional>
67
#include <span>
@@ -9,6 +10,7 @@
910

1011
#include "abstract-market-trader-factory.hpp"
1112
#include "algorithm-name-iterator.hpp"
13+
#include "auto-trade-processor.hpp"
1214
#include "balanceoptions.hpp"
1315
#include "cct_const.hpp"
1416
#include "cct_log.hpp"
@@ -29,6 +31,7 @@
2931
#include "query-result-type-helpers.hpp"
3032
#include "queryresulttypes.hpp"
3133
#include "replay-options.hpp"
34+
#include "signal-handler.hpp"
3235
#include "time-window.hpp"
3336
#include "timedef.hpp"
3437
#include "withdrawsconstraints.hpp"
@@ -338,6 +341,29 @@ ReplayResults Coincenter::replay(const AbstractMarketTraderFactory &marketTrader
338341
return replayResults;
339342
}
340343

344+
void Coincenter::autoTrade(const AutoTradeOptions &autoTradeOptions) {
345+
AutoTradeProcessor autoTradeProcessor(autoTradeOptions);
346+
347+
while (!IsStopRequested()) {
348+
// 1: select exchanges positions for which we are allowed to send a request.
349+
AutoTradeProcessor::SelectedMarketVector selectedMarkets = autoTradeProcessor.computeSelectedMarkets();
350+
if (selectedMarkets.empty()) {
351+
break;
352+
}
353+
354+
// 2: Query order books for those exchanges
355+
std::array<Market, kNbSupportedExchanges> selectedMarketsPerPublicExchangePos;
356+
for (const AutoTradeProcessor::SelectedMarket &selectedMarket : selectedMarkets) {
357+
selectedMarketsPerPublicExchangePos[selectedMarket.privateExchangeNames.front().publicExchangePos()] =
358+
selectedMarket.market;
359+
}
360+
MarketDataPerExchange marketDataPerExchange = queryMarketDataPerExchange(selectedMarketsPerPublicExchangePos);
361+
362+
// 3: call algorithms and retrieve their actions
363+
// 4: perform actual actions (Trades, cancel, exit criteria)
364+
}
365+
}
366+
341367
MarketTradingGlobalResultPerExchange Coincenter::replayAlgorithm(
342368
const AbstractMarketTraderFactory &marketTraderFactory, std::string_view algorithmName,
343369
const ReplayOptions &replayOptions, std::span<MarketTraderEngine> marketTraderEngines,

‎src/engine/src/coincentercommand.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -131,4 +131,9 @@ CoincenterCommand& CoincenterCommand::setReplayOptions(ReplayOptions replayOptio
131131
return *this;
132132
}
133133

134+
CoincenterCommand& CoincenterCommand::setJsonConfigFile(std::string_view jsonConfigFile) {
135+
_specialOptions = jsonConfigFile;
136+
return *this;
137+
}
138+
134139
} // namespace cct

‎src/engine/src/coincentercommands.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,10 @@ void CoincenterCommands::addOption(const CoincenterCmdLineOptions &cmdLineOption
242242
.setExchangeNames(optionParser.parseExchanges());
243243
}
244244

245+
if (!cmdLineOptions.autoTrade.empty()) {
246+
_commands.emplace_back(CoincenterCommandType::AutoTrade).setJsonConfigFile(cmdLineOptions.autoTrade);
247+
}
248+
245249
optionParser.checkEndParsing(); // No more option part should be remaining
246250
}
247251

‎src/engine/src/exchangesorchestrator.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <tuple>
1313
#include <utility>
1414

15+
#include "auto-trade-processor.hpp"
1516
#include "balanceoptions.hpp"
1617
#include "balanceportfolio.hpp"
1718
#include "cct_const.hpp"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <string_view>
5+
#include <variant>
6+
7+
#include "timedef.hpp"
8+
9+
namespace cct {
10+
11+
class AutoTradeStopCriterion {
12+
public:
13+
enum class Type : int8_t { kDuration, kProtectLoss, kSecureProfit };
14+
15+
AutoTradeStopCriterion(std::string_view typeStr, std::string_view valueStr);
16+
17+
Duration duration() const { return std::get<Duration>(_value); }
18+
19+
int maxEvolutionPercentage() const { return std::get<int>(_value); }
20+
21+
Type type() const { return _type; }
22+
23+
private:
24+
using Value = std::variant<std::monostate, Duration, int>;
25+
26+
Type _type;
27+
Value _value;
28+
};
29+
30+
} // namespace cct
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#include "auto-trade-stop-criterion.hpp"
2+
3+
#include "cct_invalid_argument_exception.hpp"
4+
#include "durationstring.hpp"
5+
#include "stringconv.hpp"
6+
7+
namespace cct {
8+
9+
namespace {
10+
auto TypeFromStr(std::string_view typeStr) {
11+
if (typeStr == "duration") {
12+
return AutoTradeStopCriterion::Type::kDuration;
13+
}
14+
if (typeStr == "protectLoss") {
15+
return AutoTradeStopCriterion::Type::kProtectLoss;
16+
}
17+
if (typeStr == "secureProfit") {
18+
return AutoTradeStopCriterion::Type::kSecureProfit;
19+
}
20+
throw invalid_argument("Unknown stop criterion type {}", typeStr);
21+
}
22+
23+
auto PercentageIntFromStr(std::string_view valueStr) {
24+
const std::string_view integralStr = valueStr.substr(0, valueStr.find('%'));
25+
return StringToIntegral(integralStr);
26+
}
27+
28+
} // namespace
29+
30+
AutoTradeStopCriterion::AutoTradeStopCriterion(std::string_view typeStr, std::string_view valueStr)
31+
: _type(TypeFromStr(typeStr)), _value() {
32+
switch (_type) {
33+
case Type::kDuration:
34+
_value = ParseDuration(valueStr);
35+
break;
36+
case Type::kProtectLoss:
37+
[[fallthrough]];
38+
case Type::kSecureProfit:
39+
_value = PercentageIntFromStr(valueStr);
40+
break;
41+
default: {
42+
throw invalid_argument("Unknown stop criterion type {}", static_cast<int>(_type));
43+
}
44+
}
45+
}
46+
47+
} // namespace cct
+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#pragma once
2+
3+
#include <cstdint>
4+
#include <map>
5+
#include <variant>
6+
7+
#include "cct_const.hpp"
8+
#include "cct_json-serialization.hpp"
9+
#include "cct_smallvector.hpp"
10+
#include "cct_string.hpp"
11+
#include "cct_vector.hpp"
12+
#include "duration-schema.hpp"
13+
#include "generic-object-json.hpp"
14+
#include "market.hpp"
15+
#include "monetaryamount.hpp"
16+
#include "timedef.hpp"
17+
18+
namespace cct::schema {
19+
20+
#define CCT_AUTO_TRADE_STOP_CRITERIA_TYPES duration, protectLoss, secureProfit
21+
22+
enum class AutoTradeStopCriteriaType : int8_t { CCT_AUTO_TRADE_STOP_CRITERIA_TYPES };
23+
24+
class AutoTradeStopCriteriaValue {
25+
public:
26+
AutoTradeStopCriteriaValue() = default;
27+
28+
explicit AutoTradeStopCriteriaValue(std::string_view valueStr);
29+
30+
::cct::Duration duration() const { return std::get<::cct::Duration>(_value); }
31+
32+
int maxEvolutionPercentage() const { return std::get<int>(_value); }
33+
34+
std::size_t strLen() const;
35+
36+
char *appendTo(char *buf) const;
37+
38+
auto operator<=>(const AutoTradeStopCriteriaValue &) const = default;
39+
40+
private:
41+
using Value = std::variant<int, ::cct::Duration>;
42+
43+
Value _value;
44+
};
45+
46+
struct AutoTradeStopCriterion {
47+
AutoTradeStopCriteriaType type;
48+
AutoTradeStopCriteriaValue value;
49+
50+
auto operator<=>(const AutoTradeStopCriterion &) const = default;
51+
};
52+
53+
struct AutoTradeMarketConfig {
54+
SmallVector<string, 2> accounts;
55+
string algorithmName;
56+
Duration repeatTime{std::chrono::seconds(5)};
57+
MonetaryAmount baseStartAmount;
58+
MonetaryAmount quoteStartAmount;
59+
60+
vector<AutoTradeStopCriterion> stopCriteria;
61+
62+
using trivially_relocatable = is_trivially_relocatable<SmallVector<string, 2>>::type;
63+
};
64+
65+
using AutoTradeExchangeConfig = std::map<Market, AutoTradeMarketConfig>;
66+
67+
using AutoTradeConfig = std::map<ExchangeNameEnum, AutoTradeExchangeConfig>;
68+
69+
} // namespace cct::schema
70+
71+
template <>
72+
struct glz::meta<::cct::schema::AutoTradeStopCriteriaType> {
73+
using enum ::cct::schema::AutoTradeStopCriteriaType;
74+
static constexpr auto value = enumerate(CCT_AUTO_TRADE_STOP_CRITERIA_TYPES);
75+
};
76+
77+
#undef CCT_AUTO_TRADE_STOP_CRITERIA_TYPES
78+
79+
namespace glz::detail {
80+
template <>
81+
struct from<JSON, ::cct::schema::AutoTradeStopCriteriaValue> {
82+
template <auto Opts, class It, class End>
83+
static void op(auto &&value, is_context auto &&, It &&it, End &&end) noexcept {
84+
// used as a value. As a key, the first quote will not be present.
85+
auto endIt = std::find(*it == '"' ? ++it : it, end, '"');
86+
value = ::cct::schema::AutoTradeStopCriteriaValue(std::string_view(it, endIt));
87+
it = ++endIt;
88+
}
89+
};
90+
91+
template <>
92+
struct to<JSON, ::cct::schema::AutoTradeStopCriteriaValue> {
93+
template <auto Opts, is_context Ctx, class B, class IX>
94+
static void op(auto &&value, Ctx &&, B &&b, IX &&ix) {
95+
::cct::details::ToJson<Opts>(value, b, ix);
96+
}
97+
};
98+
} // namespace glz::detail

‎src/schema/src/auto-trade-config.cpp

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include "auto-trade-config.hpp"
2+
3+
#include <cstring>
4+
#include <variant>
5+
6+
#include "durationstring.hpp"
7+
#include "ndigits.hpp"
8+
#include "stringconv.hpp"
9+
10+
namespace cct::schema {
11+
12+
namespace {
13+
constexpr int kNbSignificantUnitsDuration = 10;
14+
}
15+
16+
AutoTradeStopCriteriaValue::AutoTradeStopCriteriaValue(std::string_view valueStr) {
17+
if (valueStr.empty()) {
18+
throw invalid_argument("Unexpected str {} to parse AutoTradeStopCriteriaValue", valueStr);
19+
}
20+
if (valueStr.back() == '%') {
21+
valueStr.remove_suffix(1);
22+
_value = StringToIntegral(valueStr);
23+
} else {
24+
_value = ParseDuration(valueStr);
25+
}
26+
}
27+
28+
char *AutoTradeStopCriteriaValue::appendTo(char *buf) const {
29+
return std::visit(
30+
[&buf](const auto &value) -> char * {
31+
if constexpr (std::is_same_v<std::decay_t<decltype(value)>, Duration>) {
32+
auto str = DurationToString(value, kNbSignificantUnitsDuration);
33+
std::memcpy(buf, str.data(), str.size());
34+
return buf + str.size();
35+
} else if constexpr (std::is_same_v<std::decay_t<decltype(value)>, int>) {
36+
auto str = IntegralToCharVector(value);
37+
std::memcpy(buf, str.data(), str.size());
38+
return buf + str.size();
39+
} else {
40+
return buf;
41+
}
42+
},
43+
_value);
44+
}
45+
46+
std::size_t AutoTradeStopCriteriaValue::strLen() const {
47+
return std::visit(
48+
[](const auto &value) -> std::size_t {
49+
if constexpr (std::is_same_v<std::decay_t<decltype(value)>, Duration>) {
50+
return DurationToString(value, kNbSignificantUnitsDuration).size();
51+
} else if constexpr (std::is_same_v<std::decay_t<decltype(value)>, int>) {
52+
return ndigits(value);
53+
} else {
54+
return 0;
55+
}
56+
},
57+
_value);
58+
}
59+
60+
} // namespace cct::schema

0 commit comments

Comments
 (0)
Please sign in to comment.