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 32634ca

Browse files
committedFeb 9, 2025·
Live auto trade mode
1 parent 9fbd88e commit 32634ca

19 files changed

+636
-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

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

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

‎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-name-enum.hpp"
79
#include "exchange-names.hpp"
810
#include "exchangename.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_exception.hpp"
1112
#include "cct_invalid_argument_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_log.hpp"
1416
#include "coincenterinfo.hpp"
@@ -30,6 +32,7 @@
3032
#include "query-result-type-helpers.hpp"
3133
#include "queryresulttypes.hpp"
3234
#include "replay-options.hpp"
35+
#include "signal-handler.hpp"
3336
#include "time-window.hpp"
3437
#include "timedef.hpp"
3538
#include "withdrawsconstraints.hpp"
@@ -325,7 +328,8 @@ ReplayResults Coincenter::replay(const AbstractMarketTraderFactory &marketTrader
325328

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

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

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

368-
Coincenter::MarketTraderEngineVector Coincenter::createMarketTraderEngines(
397+
Coincenter::MarketTraderEngineVector Coincenter::createMarketTraderEnginesForReplay(
369398
const ReplayOptions &replayOptions, Market market, ExchangeNameEnumVector &exchangesWithThisMarketData) {
370399
const auto &automationConfig = _coincenterInfo.generalConfig().trading.automation;
371400
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
@@ -13,6 +13,7 @@
1313
#include <tuple>
1414
#include <utility>
1515

16+
#include "auto-trade-processor.hpp"
1617
#include "balanceoptions.hpp"
1718
#include "balanceportfolio.hpp"
1819
#include "cct_exception.hpp"

‎src/objects/src/marketorderbook.cpp

+2-3
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ MarketOrderBook::MarketOrderBook(TimePoint timeStamp, MonetaryAmount askPrice, M
115115
_isArtificiallyExtended(depth > 1),
116116
_volAndPriNbDecimals(volAndPriNbDecimals) {
117117
if (depth <= 0) {
118-
throw exception("Invalid depth, should be strictly positive");
118+
throw exception("Invalid depth {}, should be strictly positive", depth);
119119
}
120120

121121
static constexpr std::string_view kErrNegVolumeMsg = " for MarketOrderbook creation, should be strictly positive";
@@ -275,7 +275,6 @@ std::optional<MonetaryAmount> MarketOrderBook::averagePrice() const {
275275
case 1U:
276276
return MonetaryAmount(_orders.front().price, _market.quote(), _volAndPriNbDecimals.priNbDecimals);
277277
default:
278-
// std::midpoint computes safely the average of two values (without overflow)
279278
return MonetaryAmount(std::midpoint(_orders[_lowestAskPricePos].price, _orders[_highestBidPricePos].price),
280279
_market.quote(), _volAndPriNbDecimals.priNbDecimals);
281280
}
@@ -301,7 +300,7 @@ MonetaryAmount MarketOrderBook::computeCumulAmountSoldImmediatelyAt(MonetaryAmou
301300
std::optional<MonetaryAmount> MarketOrderBook::computeMaxPriceAtWhichAmountWouldBeBoughtImmediately(
302301
MonetaryAmount ma) const {
303302
if (ma.currencyCode() != _market.base()) {
304-
throw exception("Given amount should be in the base currency of this market");
303+
throw exception("Given amount {} should be in the base currency of this market {}", ma, _market);
305304
}
306305
AmountType integralAmountRep = 0;
307306
const std::optional<AmountType> integralTotalAmountOpt = ma.amount(_volAndPriNbDecimals.volNbDecimals);
+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#pragma once
2+
3+
#include <cstddef>
4+
#include <cstdint>
5+
#include <unordered_map>
6+
#include <utility>
7+
#include <variant>
8+
9+
#include "cct_fixedcapacityvector.hpp"
10+
#include "cct_smallvector.hpp"
11+
#include "cct_string.hpp"
12+
#include "cct_type_traits.hpp"
13+
#include "cct_vector.hpp"
14+
#include "duration-schema.hpp"
15+
#include "exchange-name-enum.hpp"
16+
#include "generic-object-json.hpp"
17+
#include "market.hpp"
18+
#include "monetaryamount.hpp"
19+
#include "timedef.hpp"
20+
21+
namespace cct::schema {
22+
23+
#define CCT_AUTO_TRADE_STOP_CRITERIA_TYPES duration, protectLoss, secureProfit
24+
25+
enum class AutoTradeStopCriteriaType : int8_t { CCT_AUTO_TRADE_STOP_CRITERIA_TYPES };
26+
27+
class AutoTradeStopCriteriaValue {
28+
public:
29+
AutoTradeStopCriteriaValue() = default;
30+
31+
explicit AutoTradeStopCriteriaValue(std::string_view valueStr);
32+
33+
::cct::Duration duration() const { return std::get<::cct::Duration>(_value); }
34+
35+
int maxEvolutionPercentage() const { return std::get<int>(_value); }
36+
37+
std::size_t strLen() const;
38+
39+
char *appendTo(char *buf) const;
40+
41+
auto operator<=>(const AutoTradeStopCriteriaValue &) const = default;
42+
43+
private:
44+
using Value = std::variant<int, ::cct::Duration>;
45+
46+
Value _value;
47+
};
48+
49+
struct AutoTradeStopCriterion {
50+
auto operator<=>(const AutoTradeStopCriterion &) const = default;
51+
52+
AutoTradeStopCriteriaType type;
53+
AutoTradeStopCriteriaValue value;
54+
};
55+
56+
struct AutoTradeMarketConfig {
57+
using trivially_relocatable = is_trivially_relocatable<string>::type;
58+
59+
SmallVector<string, 2> accounts;
60+
string algorithmName;
61+
Duration repeatTime{std::chrono::seconds(5)};
62+
MonetaryAmount baseStartAmount;
63+
MonetaryAmount quoteStartAmount;
64+
65+
vector<AutoTradeStopCriterion> stopCriteria;
66+
};
67+
68+
using AutoTradeExchangeConfig = std::unordered_map<Market, AutoTradeMarketConfig>;
69+
70+
using AutoTradeConfig =
71+
FixedCapacityVector<std::pair<ExchangeNameEnum, AutoTradeExchangeConfig>, kNbSupportedExchanges>;
72+
73+
} // namespace cct::schema
74+
75+
template <>
76+
struct glz::meta<::cct::schema::AutoTradeStopCriteriaType> {
77+
using enum ::cct::schema::AutoTradeStopCriteriaType;
78+
79+
static constexpr auto value = enumerate(CCT_AUTO_TRADE_STOP_CRITERIA_TYPES);
80+
};
81+
82+
#undef CCT_AUTO_TRADE_STOP_CRITERIA_TYPES
83+
84+
namespace glz::detail {
85+
template <>
86+
struct from<JSON, ::cct::schema::AutoTradeStopCriteriaValue> {
87+
template <auto Opts, class It, class End>
88+
static void op(auto &&value, is_context auto &&, It &&it, End &&end) noexcept {
89+
// used as a value. As a key, the first quote will not be present.
90+
auto endIt = std::find(*it == '"' ? ++it : it, end, '"');
91+
value = ::cct::schema::AutoTradeStopCriteriaValue(std::string_view(it, endIt));
92+
it = ++endIt;
93+
}
94+
};
95+
96+
template <>
97+
struct to<JSON, ::cct::schema::AutoTradeStopCriteriaValue> {
98+
template <auto Opts, is_context Ctx, class B, class IX>
99+
static void op(auto &&value, Ctx &&, B &&b, IX &&ix) {
100+
::cct::details::ToStrLikeJson<Opts>(value, b, ix);
101+
}
102+
};
103+
} // 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.