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 7df208e

Browse files
committedJan 18, 2025·
Live auto trade mode
1 parent 6c6adcc commit 7df208e

20 files changed

+634
-15
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
@@ -13,7 +13,7 @@ namespace cct {
1313
Balance, DepositInfo, OrdersClosed, OrdersOpened, OrdersCancel, RecentDeposits, RecentWithdraws, Trade, Buy, \
1414
Sell, Withdraw, DustSweeper, \
1515
\
16-
MarketData, Replay, ReplayMarkets
16+
MarketData, Replay, ReplayMarkets, AutoTrade
1717

1818
enum class CoincenterCommandType : int8_t { CCT_COINCENTER_COMMAND_TYPES };
1919

@@ -26,4 +26,4 @@ struct glz::meta<::cct::CoincenterCommandType> {
2626
static constexpr auto value = enumerate(CCT_COINCENTER_COMMAND_TYPES);
2727
};
2828

29-
#undef CCT_COINCENTER_COMMAND_TYPES
29+
#undef CCT_COINCENTER_COMMAND_TYPES
+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,60 @@
1+
#pragma once
2+
3+
#include <array>
4+
#include <compare>
5+
#include <functional>
6+
#include <unordered_map>
7+
8+
#include "auto-trade-config.hpp"
9+
#include "cct_smallvector.hpp"
10+
#include "cct_vector.hpp"
11+
#include "exchange-names.hpp"
12+
#include "exchange.hpp"
13+
#include "market-trader-engine.hpp"
14+
#include "market.hpp"
15+
#include "timedef.hpp"
16+
17+
namespace cct {
18+
class AutoTradeOptions;
19+
class CoincenterInfo;
20+
21+
class AutoTradeProcessor {
22+
public:
23+
using MarketTraderEngines = std::array<std::unordered_map<Market, MarketTraderEngine>, kNbSupportedExchanges>;
24+
25+
explicit AutoTradeProcessor(const AutoTradeOptions& autoTradeOptions);
26+
27+
struct SelectedMarket {
28+
ExchangeNames privateExchangeNames;
29+
Market market;
30+
};
31+
32+
using SelectedMarketVector = SmallVector<SelectedMarket, kTypicalNbPrivateAccounts>;
33+
34+
SelectedMarketVector computeSelectedMarkets();
35+
36+
MarketTraderEngines createMarketTraderEngines(const CoincenterInfo& coincenterInfo) const;
37+
38+
private:
39+
struct MarketStatus {
40+
ExchangeNames privateExchangeNames;
41+
Market market;
42+
TimePoint lastQueryTime;
43+
const schema::AutoTradeMarketConfig* pMarketAutoTradeOptions{};
44+
};
45+
46+
using MarketStatusVector = vector<MarketStatus>;
47+
48+
struct ExchangeStatus {
49+
MarketStatusVector marketStatusVector;
50+
const schema::AutoTradeExchangeConfig* pPublicExchangeAutoTradeOptions{};
51+
ExchangeNameEnum exchangeNameEnum;
52+
};
53+
54+
using ExchangeStatusVector = SmallVector<ExchangeStatus, kTypicalNbPrivateAccounts>;
55+
56+
ExchangeStatusVector _exchangeStatusVector;
57+
TimePoint _startTs = Clock::now();
58+
TimePoint _ts{_startTs};
59+
};
60+
} // namespace cct

‎src/engine/include/coincenter.hpp

+6-2
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

@@ -173,8 +177,8 @@ class Coincenter {
173177
const ExchangeNameEnumVector &exchangesWithThisMarketData);
174178

175179
// TODO: may be moved somewhere else?
176-
MarketTraderEngineVector createMarketTraderEngines(const ReplayOptions &replayOptions, Market market,
177-
ExchangeNameEnumVector &exchangesWithThisMarketData);
180+
MarketTraderEngineVector createMarketTraderEnginesForReplay(const ReplayOptions &replayOptions, Market market,
181+
ExchangeNameEnumVector &exchangesWithThisMarketData);
178182

179183
MarketTradeRangeStatsPerExchange tradingProcess(const ReplayOptions &replayOptions,
180184
std::span<MarketTraderEngine> marketTraderEngines,

‎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+
8+
namespace cct {
9+
10+
AutoTradeOptions::AutoTradeOptions(schema::AutoTradeConfig &&autoTradeConfig)
11+
: _autoTradeConfig(std::move(autoTradeConfig)) {}
12+
13+
ExchangeNames AutoTradeOptions::exchangeNames() const {
14+
ExchangeNames exchangeNames;
15+
for (const auto &[exchangeNameEnum, publicExchangeAutoTradeOptions] : _autoTradeConfig) {
16+
const int posPublicExchangeName = exchangeNames.size();
17+
for (const auto &[market, autoTradeMarketConfig] : publicExchangeAutoTradeOptions) {
18+
const int posMarket = exchangeNames.size();
19+
for (std::string_view account : autoTradeMarketConfig.accounts) {
20+
ExchangeName exchangeName(exchangeNameEnum, account);
21+
const auto it = std::find(exchangeNames.begin() + posPublicExchangeName, exchangeNames.end(), exchangeName);
22+
if (it == exchangeNames.end()) {
23+
exchangeNames.push_back(std::move(exchangeName));
24+
} else if (it >= exchangeNames.begin() + posMarket) {
25+
throw invalid_argument("Duplicated account {} for exchange {}", account, exchangeName.name());
26+
}
27+
}
28+
}
29+
}
30+
return exchangeNames;
31+
}
32+
33+
ExchangeNameEnumVector AutoTradeOptions::publicExchanges() const {
34+
ExchangeNameEnumVector exchanges;
35+
for (const auto &[publicExchangeName, _] : _autoTradeConfig) {
36+
exchanges.emplace_back(publicExchangeName);
37+
}
38+
std::ranges::sort(exchanges);
39+
return exchanges;
40+
}
41+
42+
AutoTradeOptions::AccountAutoTradeOptionsPtrVector AutoTradeOptions::accountAutoTradeOptionsPtr(
43+
std::string_view publicExchangeName) const {
44+
AccountAutoTradeOptionsPtrVector accountAutoTradeOptionsPtr;
45+
for (const auto &[exchangeNameEnum, publicExchangeAutoTradeOptions] : _autoTradeConfig) {
46+
if (kSupportedExchanges[static_cast<int>(exchangeNameEnum)] == publicExchangeName) {
47+
accountAutoTradeOptionsPtr.emplace_back(&publicExchangeAutoTradeOptions);
48+
}
49+
}
50+
return accountAutoTradeOptionsPtr;
51+
}
52+
53+
const schema::AutoTradeExchangeConfig &AutoTradeOptions::operator[](ExchangeNameEnum exchangeNameEnum) const {
54+
const auto it = std::ranges::find_if(_autoTradeConfig, [exchangeNameEnum](const auto &exchangeConfig) {
55+
return exchangeConfig.first == exchangeNameEnum;
56+
});
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
+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#include "auto-trade-processor.hpp"
2+
3+
#include <thread>
4+
5+
#include "auto-trade-options.hpp"
6+
#include "cct_exception.hpp"
7+
#include "coincenterinfo.hpp"
8+
#include "timestring.hpp"
9+
10+
namespace cct {
11+
12+
AutoTradeProcessor::AutoTradeProcessor(const AutoTradeOptions &autoTradeOptions)
13+
: _exchangeStatusVector(autoTradeOptions.publicExchanges().size()) {
14+
int publicExchangePos = 0;
15+
for (const auto &[exchangeNameEnum, publicExchangeAutoTradeOptions] : autoTradeOptions) {
16+
ExchangeStatus &selectedExchangesStatus = _exchangeStatusVector[publicExchangePos];
17+
selectedExchangesStatus.pPublicExchangeAutoTradeOptions = &publicExchangeAutoTradeOptions;
18+
selectedExchangesStatus.exchangeNameEnum = exchangeNameEnum;
19+
for (const auto &[market, marketAutoTradeOptions] : publicExchangeAutoTradeOptions) {
20+
MarketStatus &marketStatus = selectedExchangesStatus.marketStatusVector.emplace_back();
21+
22+
marketStatus.market = market;
23+
marketStatus.pMarketAutoTradeOptions = &marketAutoTradeOptions;
24+
25+
for (std::string_view account : marketAutoTradeOptions.accounts) {
26+
marketStatus.privateExchangeNames.emplace_back(exchangeNameEnum, account);
27+
}
28+
}
29+
++publicExchangePos;
30+
}
31+
}
32+
33+
namespace {
34+
const auto &GetAutoTradeMarketConfig(Market market, const auto &publicExchangeAutoTradeOptions) {
35+
const auto it = publicExchangeAutoTradeOptions.find(market);
36+
if (it == publicExchangeAutoTradeOptions.end()) {
37+
throw exception("Should not happen - market not found in account auto trade options");
38+
}
39+
return it->second;
40+
}
41+
42+
bool IsQueryTooEarly(TimePoint nowTs, const auto &marketStatus, const auto &publicExchangeAutoTradeOptions) {
43+
const auto &marketAutoTradeOptions = GetAutoTradeMarketConfig(marketStatus.market, publicExchangeAutoTradeOptions);
44+
return marketStatus.lastQueryTime + marketAutoTradeOptions.repeatTime.duration > nowTs;
45+
}
46+
} // namespace
47+
48+
AutoTradeProcessor::SelectedMarketVector AutoTradeProcessor::computeSelectedMarkets() {
49+
SelectedMarketVector selectedMarketVector;
50+
51+
const auto ts = Clock::now();
52+
53+
TimePoint earliestQueryTime = TimePoint::max();
54+
55+
for (ExchangeStatus &exchangeStatus : _exchangeStatusVector) {
56+
const auto &publicExchangeAutoTradeOptions = *exchangeStatus.pPublicExchangeAutoTradeOptions;
57+
58+
auto &marketStatusVector = exchangeStatus.marketStatusVector;
59+
60+
if (marketStatusVector.empty()) {
61+
continue;
62+
}
63+
64+
// Sort markets by ascending last query time, discarding those (placed at the end of the vector) which cannot be
65+
// queried right now
66+
std::ranges::sort(marketStatusVector,
67+
[ts, &publicExchangeAutoTradeOptions](const MarketStatus &lhs, const MarketStatus &rhs) {
68+
const bool lhsIsTooEarly = IsQueryTooEarly(ts, lhs, publicExchangeAutoTradeOptions);
69+
const bool rhsIsTooEarly = IsQueryTooEarly(ts, rhs, publicExchangeAutoTradeOptions);
70+
71+
if (lhsIsTooEarly != rhsIsTooEarly) {
72+
return !lhsIsTooEarly;
73+
}
74+
75+
return lhs.lastQueryTime < rhs.lastQueryTime;
76+
});
77+
78+
MarketStatus &selectedMarketStatus = marketStatusVector.front();
79+
if (IsQueryTooEarly(ts, selectedMarketStatus, publicExchangeAutoTradeOptions)) {
80+
const auto repeatTime =
81+
GetAutoTradeMarketConfig(selectedMarketStatus.market, publicExchangeAutoTradeOptions).repeatTime.duration;
82+
earliestQueryTime = std::min(earliestQueryTime, selectedMarketStatus.lastQueryTime + repeatTime);
83+
continue;
84+
}
85+
86+
selectedMarketStatus.lastQueryTime = ts;
87+
selectedMarketVector.emplace_back(selectedMarketStatus.privateExchangeNames, selectedMarketStatus.market);
88+
}
89+
90+
if (selectedMarketVector.empty() && earliestQueryTime != TimePoint::max()) {
91+
log::debug("Sleeping until {}", TimeToString(earliestQueryTime));
92+
std::this_thread::sleep_until(earliestQueryTime + std::chrono::milliseconds(1));
93+
selectedMarketVector = computeSelectedMarkets();
94+
if (selectedMarketVector.empty()) {
95+
log::error("Waiting sufficient time should return at least one market for the next turn");
96+
}
97+
}
98+
99+
return selectedMarketVector;
100+
}
101+
102+
AutoTradeProcessor::MarketTraderEngines AutoTradeProcessor::createMarketTraderEngines(
103+
const CoincenterInfo &coincenterInfo) const {
104+
MarketTraderEngines marketTraderEngines;
105+
106+
for (const ExchangeStatus &exchangeStatus : _exchangeStatusVector) {
107+
const schema::AutoTradeExchangeConfig &publicExchangeAutoTradeOptions =
108+
*exchangeStatus.pPublicExchangeAutoTradeOptions;
109+
110+
const ExchangeNameEnum exchangeNameEnum = exchangeStatus.exchangeNameEnum;
111+
const schema::ExchangeConfig &exchangeConfig = coincenterInfo.exchangeConfig(exchangeNameEnum);
112+
113+
auto &marketTraderEngineMap = marketTraderEngines[static_cast<int>(exchangeNameEnum)];
114+
115+
for (const MarketStatus &marketStatus : exchangeStatus.marketStatusVector) {
116+
const auto marketIt = publicExchangeAutoTradeOptions.find(marketStatus.market);
117+
if (marketIt == publicExchangeAutoTradeOptions.end()) {
118+
throw exception("Should not happen - market not found in account auto trade options");
119+
}
120+
const schema::AutoTradeMarketConfig &autoTradeMarketConfig = marketIt->second;
121+
122+
const auto [it, isInserted] = marketTraderEngineMap.emplace(
123+
marketStatus.market,
124+
MarketTraderEngine(exchangeConfig, marketStatus.market, autoTradeMarketConfig.baseStartAmount,
125+
autoTradeMarketConfig.quoteStartAmount));
126+
if (!isInserted) {
127+
throw exception("Should not happen - market already exists in market trader engines");
128+
}
129+
}
130+
}
131+
132+
return marketTraderEngines;
133+
}
134+
135+
} // 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"
@@ -303,18 +306,18 @@ TransferableCommandResultVector CoincenterCommandsProcessor::processGroupedComma
303306
break;
304307
}
305308
case CoincenterCommandType::MarketData: {
306-
std::array<Market, kNbSupportedExchanges> marketPerPublicExchange;
309+
std::array<Market, kNbSupportedExchanges> marketPerPublicExchangePos;
307310
for (const auto &cmd : groupedCommands) {
308311
if (cmd.exchangeNames().empty()) {
309-
std::ranges::fill(marketPerPublicExchange, cmd.market());
312+
std::ranges::fill(marketPerPublicExchangePos, cmd.market());
310313
} else {
311314
for (const auto &exchangeName : cmd.exchangeNames()) {
312-
marketPerPublicExchange[exchangeName.publicExchangePos()] = cmd.market();
315+
marketPerPublicExchangePos[exchangeName.publicExchangePos()] = cmd.market();
313316
}
314317
}
315318
}
316-
// No return value here, this command is made only for storing purposes.
317-
_coincenter.queryMarketDataPerExchange(marketPerPublicExchange);
319+
// No need to retrieve the returned value here, this command is made only for storing purposes.
320+
_coincenter.queryMarketDataPerExchange(marketPerPublicExchangePos);
318321
break;
319322
}
320323
case CoincenterCommandType::Replay: {
@@ -338,6 +341,15 @@ TransferableCommandResultVector CoincenterCommandsProcessor::processGroupedComma
338341
_queryResultPrinter.printMarketsForReplay(firstCmd.replayOptions().timeWindow(), marketTimestampSetsPerExchange);
339342
break;
340343
}
344+
case CoincenterCommandType::AutoTrade: {
345+
const File configFile(firstCmd.getJsonConfigFile(), File::IfError::kThrow);
346+
schema::AutoTradeConfig autoTradeConfig;
347+
ReadExactJsonOrThrow(configFile.readAll(), autoTradeConfig);
348+
const AutoTradeOptions autoTradeOptions(std::move(autoTradeConfig));
349+
350+
_coincenter.autoTrade(autoTradeOptions);
351+
break;
352+
}
341353
default:
342354
throw exception("Unknown command type");
343355
}

‎src/engine/src/coincenter.cpp

+31-2
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"
@@ -324,7 +327,8 @@ ReplayResults Coincenter::replay(const AbstractMarketTraderFactory &marketTrader
324327

325328
// Create the MarketTraderEngines based on this market, filtering out exchanges without available amount to
326329
// trade
327-
auto marketTraderEngines = createMarketTraderEngines(replayOptions, replayMarket, exchangesWithThisMarketData);
330+
auto marketTraderEngines =
331+
createMarketTraderEnginesForReplay(replayOptions, replayMarket, exchangesWithThisMarketData);
328332

329333
MarketTradingGlobalResultPerExchange marketTradingResultPerExchange = replayAlgorithm(
330334
marketTraderFactory, algorithmName, replayOptions, marketTraderEngines, exchangesWithThisMarketData);
@@ -338,6 +342,31 @@ ReplayResults Coincenter::replay(const AbstractMarketTraderFactory &marketTrader
338342
return replayResults;
339343
}
340344

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

367-
Coincenter::MarketTraderEngineVector Coincenter::createMarketTraderEngines(
396+
Coincenter::MarketTraderEngineVector Coincenter::createMarketTraderEnginesForReplay(
368397
const ReplayOptions &replayOptions, Market market, ExchangeNameEnumVector &exchangesWithThisMarketData) {
369398
const auto &automationConfig = _coincenterInfo.generalConfig().trading.automation;
370399
const auto startBaseAmountEquivalent = automationConfig.startingContext.startBaseAmountEquivalent;

‎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
@@ -257,6 +257,10 @@ void CoincenterCommands::addOption(const CoincenterCmdLineOptions &cmdLineOption
257257
.setExchangeNames(optionParser.parseExchanges());
258258
}
259259

260+
if (!cmdLineOptions.autoTrade.empty()) {
261+
_commands.emplace_back(CoincenterCommandType::AutoTrade).setJsonConfigFile(cmdLineOptions.autoTrade);
262+
}
263+
260264
optionParser.checkEndParsing(); // No more option part should be remaining
261265
}
262266

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

‎src/trading/common/include/market-trader-engine.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class MarketTraderEngine {
2525
MarketTraderEngine(const schema::ExchangeConfig &exchangeConfig, Market market, MonetaryAmount startAmountBase,
2626
MonetaryAmount startAmountQuote);
2727

28-
Market market() const { return {_startAmountBase.currencyCode(), _startAmountQuote.currencyCode()}; }
28+
Market market() const { return _market; }
2929

3030
void registerMarketTrader(std::unique_ptr<AbstractMarketTrader> marketTrader);
3131

‎src/trading/common/src/market-trader-engine.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ namespace {
5454

5555
template <class VectorType>
5656
TradeRangeResultsStats ValidateRange(VectorType &vec, TimePoint earliestPossibleTime) {
57-
using std::erase_if;
58-
5957
using ObjType = std::remove_cvref_t<decltype(*std::declval<VectorType>().begin())>;
6058

6159
static_assert(std::is_same_v<ObjType, MarketOrderBook> || std::is_same_v<ObjType, PublicTrade>);
@@ -66,6 +64,7 @@ TradeRangeResultsStats ValidateRange(VectorType &vec, TimePoint earliestPossible
6664

6765
stats.nbSuccessful = static_cast<decltype(stats.nbSuccessful)>(vec.size());
6866

67+
using std::erase_if;
6968
const auto nbInvalidObjects = erase_if(vec, [](const auto &obj) { return !obj.isValid(); });
7069
if (nbInvalidObjects != 0) {
7170
log::error("{} {}(s) with invalid data detected", nbInvalidObjects, kObjName);

0 commit comments

Comments
 (0)
Please sign in to comment.