From 913ad9df50568278b74ef50dc80a03cc4a2b72d7 Mon Sep 17 00:00:00 2001 From: Stephane Janel Date: Fri, 12 Jul 2024 21:59:30 +0200 Subject: [PATCH] Live auto trade mode --- data/static/auto-trade-example.json | 63 +++++++++++ .../include/coincentercommandtype.hpp | 4 +- src/basic-objects/include/market.hpp | 2 +- src/basic-objects/test/market_test.cpp | 43 ++++++++ src/engine/include/auto-trade-options.hpp | 58 ++++++++++ src/engine/include/auto-trade-processor.hpp | 51 +++++++++ src/engine/include/coincenter.hpp | 4 + src/engine/include/coincentercommand.hpp | 7 +- src/engine/include/coincenteroptions.hpp | 2 + src/engine/include/coincenteroptionsdef.hpp | 16 +++ src/engine/include/exchangesorchestrator.hpp | 2 + src/engine/src/auto-trade-options.cpp | 61 +++++++++++ src/engine/src/auto-trade-processor.cpp | 100 ++++++++++++++++++ .../src/coincenter-commands-processor.cpp | 22 +++- src/engine/src/coincenter.cpp | 26 +++++ src/engine/src/coincentercommand.cpp | 5 + src/engine/src/coincentercommands.cpp | 4 + src/engine/src/exchangesorchestrator.cpp | 1 + src/schema/include/auto-trade-config.hpp | 98 +++++++++++++++++ src/schema/src/auto-trade-config.cpp | 60 +++++++++++ 20 files changed, 620 insertions(+), 9 deletions(-) create mode 100644 data/static/auto-trade-example.json create mode 100644 src/engine/include/auto-trade-options.hpp create mode 100644 src/engine/include/auto-trade-processor.hpp create mode 100644 src/engine/src/auto-trade-options.cpp create mode 100644 src/engine/src/auto-trade-processor.cpp create mode 100644 src/schema/include/auto-trade-config.hpp create mode 100644 src/schema/src/auto-trade-config.cpp diff --git a/data/static/auto-trade-example.json b/data/static/auto-trade-example.json new file mode 100644 index 00000000..2f91d19a --- /dev/null +++ b/data/static/auto-trade-example.json @@ -0,0 +1,63 @@ +{ + "kraken": { + "BTC-EUR": { + "accounts": [ + "user1", + "user2" + ], + "algorithmName": "example-trader", + "repeatTime": "5s", + "baseStartAmount": "0.5BTC", + "quoteStartAmount": "50%EUR", + "stopCriteria": [ + { + "type": "duration", + "value": "4h" + }, + { + "type": "protectLoss", + "value": "-30%" + }, + { + "type": "secureProfit", + "value": "80%" + } + ] + }, + "ETH-EUR": { + "accounts": [ + "user1" + ], + "algorithmName": "example-trader", + "repeatTime": "3s", + "baseStartAmount": "45ETH", + "quoteStartAmount": "50%EUR", + "stopCriteria": [ + { + "type": "duration", + "value": "4h" + }, + { + "type": "protectLoss", + "value": "-30%" + }, + { + "type": "secureProfit", + "value": "80%" + } + ] + } + }, + "binance": { + "XRP-USDT": { + "accounts": [ + "user1" + ], + "algorithmName": "example-trader", + "repeatTime": "1s", + "baseStartAmount": "50000.56XRP", + "quoteStartAmount": "100%USDT", + "stopCriteria": [] + } + } +} \ No newline at end of file diff --git a/src/basic-objects/include/coincentercommandtype.hpp b/src/basic-objects/include/coincentercommandtype.hpp index 83b76d18..cc3f69f5 100644 --- a/src/basic-objects/include/coincentercommandtype.hpp +++ b/src/basic-objects/include/coincentercommandtype.hpp @@ -14,7 +14,7 @@ namespace cct { Balance, DepositInfo, OrdersClosed, OrdersOpened, OrdersCancel, RecentDeposits, RecentWithdraws, Trade, Buy, \ Sell, Withdraw, DustSweeper, \ \ - MarketData, Replay, ReplayMarkets + MarketData, Replay, ReplayMarkets, AutoTrade enum class CoincenterCommandType : int8_t { CCT_COINCENTER_COMMAND_TYPES }; @@ -29,4 +29,4 @@ struct glz::meta<::cct::CoincenterCommandType> { static constexpr auto value = enumerate(CCT_COINCENTER_COMMAND_TYPES); }; -#undef CCT_COINCENTER_COMMAND_TYPES \ No newline at end of file +#undef CCT_COINCENTER_COMMAND_TYPES diff --git a/src/basic-objects/include/market.hpp b/src/basic-objects/include/market.hpp index a9ff8ea3..b61c3221 100644 --- a/src/basic-objects/include/market.hpp +++ b/src/basic-objects/include/market.hpp @@ -163,7 +163,7 @@ struct from { static void op(auto &&value, is_context auto &&, It &&it, End &&end) noexcept { // used as a value. As a key, the first quote will not be present. auto endIt = std::find(*it == '"' ? ++it : it, end, '"'); - value = std::string_view(it, endIt); + value = ::cct::Market(std::string_view(it, endIt)); it = ++endIt; } }; diff --git a/src/basic-objects/test/market_test.cpp b/src/basic-objects/test/market_test.cpp index 36322450..2233091f 100644 --- a/src/basic-objects/test/market_test.cpp +++ b/src/basic-objects/test/market_test.cpp @@ -3,6 +3,7 @@ #include #include "cct_exception.hpp" +#include "cct_json-serialization.hpp" #include "currencycode.hpp" namespace cct { @@ -65,4 +66,46 @@ TEST(MarketTest, StrLen) { market = Market("1INCH", "EUR", Market::Type::kFiatConversionMarket); EXPECT_EQ(market.strLen(), 10); } + +struct Foo { + bool operator==(const Foo &) const noexcept = default; + + Market market; +}; + +TEST(MarketTest, JsonSerializationValue) { + Foo foo{Market{"DOGE", "BTC"}}; + + string buffer; + auto res = json::write(foo, buffer); // NOLINT(readability-implicit-bool-conversion) + + EXPECT_FALSE(res); + + EXPECT_EQ(buffer, R"({"market":"DOGE-BTC"})"); +} + +using MarketMap = std::map; + +TEST(MarketTest, JsonSerializationKey) { + MarketMap map{{Market{"DOGE", "BTC"}, true}, {Market{"BTC", "ETH"}, false}}; + + string buffer; + auto res = json::write(map, buffer); // NOLINT(readability-implicit-bool-conversion) + + EXPECT_FALSE(res); + + EXPECT_EQ(buffer, R"({"BTC-ETH":false,"DOGE-BTC":true})"); +} + +TEST(MarketTest, JsonDeserialization) { + Foo foo; + + // NOLINTNEXTLINE(readability-implicit-bool-conversion) + auto ec = json::read(foo, R"({"market":"DOGE-ETH"})"); + + ASSERT_FALSE(ec); + + EXPECT_EQ(foo, Foo{Market("DOGE", "ETH")}); +} + } // namespace cct diff --git a/src/engine/include/auto-trade-options.hpp b/src/engine/include/auto-trade-options.hpp new file mode 100644 index 00000000..18177497 --- /dev/null +++ b/src/engine/include/auto-trade-options.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include "auto-trade-config.hpp" +#include "cct_const.hpp" +#include "cct_fixedcapacityvector.hpp" +#include "cct_smallvector.hpp" +#include "cct_vector.hpp" +#include "exchange-names.hpp" +#include "exchangename.hpp" + +namespace cct { + +class AutoTradeOptions { + public: + using AccountAutoTradeOptionsPtrVector = + SmallVector; + + struct MarketExchanges { + Market market; + ExchangeNames privateExchangeNames; + const schema::AutoTradeMarketConfig *pMarketAutoTradeOptions{}; + }; + + using MarketStatusVector = vector; + + struct MarketExchangeOptions { + MarketStatusVector marketStatusVector; + }; + + struct PublicExchangeMarketOptions { + ExchangeName publicExchangeName; + MarketExchangeOptions marketExchangeOptions; + }; + + using PublicExchangeMarketOptionsVector = FixedCapacityVector; + + AutoTradeOptions() noexcept = default; + + explicit AutoTradeOptions(schema::AutoTradeConfig &&autoTradeConfig); + + auto begin() const { return _autoTradeConfig.begin(); } + auto end() const { return _autoTradeConfig.end(); } + + ExchangeNames exchangeNames() const; + + ExchangeNameEnumVector publicExchanges() const; + + AccountAutoTradeOptionsPtrVector accountAutoTradeOptionsPtr(std::string_view publicExchangeName) const; + + const schema::AutoTradeExchangeConfig &operator[](ExchangeNameEnum exchangeNameEnum) const; + + private: + schema::AutoTradeConfig _autoTradeConfig; +}; + +} // namespace cct \ No newline at end of file diff --git a/src/engine/include/auto-trade-processor.hpp b/src/engine/include/auto-trade-processor.hpp new file mode 100644 index 00000000..8582b7db --- /dev/null +++ b/src/engine/include/auto-trade-processor.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +#include "auto-trade-config.hpp" +#include "cct_smallvector.hpp" +#include "cct_vector.hpp" +#include "exchange-names.hpp" +#include "exchange.hpp" +#include "market.hpp" +#include "timedef.hpp" + +namespace cct { +class AutoTradeOptions; + +class AutoTradeProcessor { + public: + explicit AutoTradeProcessor(const AutoTradeOptions& autoTradeOptions); + + struct SelectedMarket { + ExchangeNames privateExchangeNames; + Market market; + }; + + using SelectedMarketVector = SmallVector; + + SelectedMarketVector computeSelectedMarkets(); + + private: + struct MarketStatus { + ExchangeNames privateExchangeNames; + Market market; + TimePoint lastQueryTime; + const schema::AutoTradeMarketConfig* pMarketAutoTradeOptions{}; + }; + + using MarketStatusVector = vector; + + struct ExchangeStatus { + MarketStatusVector marketStatusVector; + const schema::AutoTradeExchangeConfig* pPublicExchangeAutoTradeOptions{}; + }; + + using ExchangeStatusVector = SmallVector; + + ExchangeStatusVector _exchangeStatusVector; + TimePoint _startTs = Clock::now(); + TimePoint _ts{_startTs}; +}; +} // namespace cct \ No newline at end of file diff --git a/src/engine/include/coincenter.hpp b/src/engine/include/coincenter.hpp index 88f17ce8..37e8edcd 100644 --- a/src/engine/include/coincenter.hpp +++ b/src/engine/include/coincenter.hpp @@ -4,6 +4,7 @@ #include #include "apikeysprovider.hpp" +#include "auto-trade-options.hpp" #include "cct_const.hpp" #include "cct_fixedcapacityvector.hpp" #include "coincenterinfo.hpp" @@ -149,6 +150,9 @@ class Coincenter { ReplayResults replay(const AbstractMarketTraderFactory &marketTraderFactory, const ReplayOptions &replayOptions, Market market, ExchangeNameSpan exchangeNames); + /// Run auto trade. + void autoTrade(const AutoTradeOptions &autoTradeOptions); + /// Dumps the content of all file caches in data directory to save cURL queries. void updateFileCaches() const; diff --git a/src/engine/include/coincentercommand.hpp b/src/engine/include/coincentercommand.hpp index db0894d1..0d5c3750 100644 --- a/src/engine/include/coincentercommand.hpp +++ b/src/engine/include/coincentercommand.hpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -47,6 +48,8 @@ class CoincenterCommand { CoincenterCommand& setReplayOptions(ReplayOptions replayOptions); + CoincenterCommand& setJsonConfigFile(std::string_view jsonConfigFile); + CoincenterCommand& setPercentageAmount(bool value = true); CoincenterCommand& withBalanceInUse(bool value = true); @@ -79,6 +82,8 @@ class CoincenterCommand { const ReplayOptions& replayOptions() const { return std::get(_specialOptions); } + std::string_view getJsonConfigFile() const { return std::get(_specialOptions); } + bool operator==(const CoincenterCommand&) const noexcept = default; using trivially_relocatable = @@ -89,7 +94,7 @@ class CoincenterCommand { private: using SpecialOptions = std::variant; + WithdrawOptions, ReplayOptions, std::string_view>; ExchangeNames _exchangeNames; SpecialOptions _specialOptions; diff --git a/src/engine/include/coincenteroptions.hpp b/src/engine/include/coincenteroptions.hpp index 24b44af3..f64df1db 100644 --- a/src/engine/include/coincenteroptions.hpp +++ b/src/engine/include/coincenteroptions.hpp @@ -104,6 +104,8 @@ class CoincenterCmdLineOptions { std::string_view marketData; + std::string_view autoTrade; + std::optional replay; std::string_view algorithmNames; std::string_view market; diff --git a/src/engine/include/coincenteroptionsdef.hpp b/src/engine/include/coincenteroptionsdef.hpp index 1b846add..f7379d69 100644 --- a/src/engine/include/coincenteroptionsdef.hpp +++ b/src/engine/include/coincenteroptionsdef.hpp @@ -477,6 +477,22 @@ struct CoincenterAllowedOptions : private CoincenterCmdLineOptionsDefinitions { "\nNominal replay will not validate input data to optimize performance, use this option to validate data once " "and for all."}, &OptValueType::validateOnly}, + {{{"Automation", 8004}, + "auto-trade", + "", + "Automatic live trading mode. Once you have validated on historical market-data the performance of an " + "algorithm, it's time to try it for real!\n" + "This command has some particularities:\n" + "- next commands will never be executed\n" + "- repeat is ignored (the auto trade will continue until one of terminating signals defined in the " + "configuration file is reached)\n" + "Configuration will be loaded from given json file, with following options (check README to get full " + "configuration schema):\n" + "- 'algorithm' : algorithm name to use\n" + "- 'market' : the market to trade onto\n" + "- 'startAmount' : the starting amount in base currency (can be a percentage of available amount)\n" + "- 'exchange' : exchange with account key (not needed if not ambiguous)"}, + &OptValueType::autoTrade}, {{{"Monitoring", 9000}, "--monitoring", "", diff --git a/src/engine/include/exchangesorchestrator.hpp b/src/engine/include/exchangesorchestrator.hpp index b3a6256b..3bebf813 100644 --- a/src/engine/include/exchangesorchestrator.hpp +++ b/src/engine/include/exchangesorchestrator.hpp @@ -3,6 +3,8 @@ #include #include +#include "auto-trade-options.hpp" +#include "auto-trade-processor.hpp" #include "exchange-names.hpp" #include "exchangename.hpp" #include "exchangeretriever.hpp" diff --git a/src/engine/src/auto-trade-options.cpp b/src/engine/src/auto-trade-options.cpp new file mode 100644 index 00000000..41e2d71c --- /dev/null +++ b/src/engine/src/auto-trade-options.cpp @@ -0,0 +1,61 @@ +#include "auto-trade-options.hpp" + +#include + +#include "auto-trade-config.hpp" +#include "cct_invalid_argument_exception.hpp" + +namespace cct { + +AutoTradeOptions::AutoTradeOptions(schema::AutoTradeConfig &&autoTradeConfig) + : _autoTradeConfig(std::move(autoTradeConfig)) {} + +ExchangeNames AutoTradeOptions::exchangeNames() const { + ExchangeNames exchangeNames; + for (const auto &[exchangeNameEnum, publicExchangeAutoTradeOptions] : _autoTradeConfig) { + const int posPublicExchangeName = exchangeNames.size(); + for (const auto &[market, autoTradeMarketConfig] : publicExchangeAutoTradeOptions) { + const int posMarket = exchangeNames.size(); + for (std::string_view account : autoTradeMarketConfig.accounts) { + ExchangeName exchangeName(exchangeNameEnum, account); + const auto it = std::find(exchangeNames.begin() + posPublicExchangeName, exchangeNames.end(), exchangeName); + if (it == exchangeNames.end()) { + exchangeNames.push_back(std::move(exchangeName)); + } else if (it >= exchangeNames.begin() + posMarket) { + throw invalid_argument("Duplicated account {} for exchange {}", account, exchangeName.name()); + } + } + } + } + return exchangeNames; +} + +ExchangeNameEnumVector AutoTradeOptions::publicExchanges() const { + ExchangeNameEnumVector exchanges; + for (const auto &[publicExchangeName, _] : _autoTradeConfig) { + exchanges.emplace_back(publicExchangeName); + } + std::ranges::sort(exchanges); + return exchanges; +} + +AutoTradeOptions::AccountAutoTradeOptionsPtrVector AutoTradeOptions::accountAutoTradeOptionsPtr( + std::string_view publicExchangeName) const { + AccountAutoTradeOptionsPtrVector accountAutoTradeOptionsPtr; + for (const auto &[exchangeNameEnum, publicExchangeAutoTradeOptions] : _autoTradeConfig) { + if (kSupportedExchanges[static_cast(exchangeNameEnum)] == publicExchangeName) { + accountAutoTradeOptionsPtr.emplace_back(&publicExchangeAutoTradeOptions); + } + } + return accountAutoTradeOptionsPtr; +} + +const schema::AutoTradeExchangeConfig &AutoTradeOptions::operator[](ExchangeNameEnum exchangeNameEnum) const { + const auto it = _autoTradeConfig.find(exchangeNameEnum); + if (it == _autoTradeConfig.end()) { + throw exception("No auto trade options for exchange {}", kSupportedExchanges[static_cast(exchangeNameEnum)]); + } + return it->second; +} + +} // namespace cct \ No newline at end of file diff --git a/src/engine/src/auto-trade-processor.cpp b/src/engine/src/auto-trade-processor.cpp new file mode 100644 index 00000000..ba37ae4c --- /dev/null +++ b/src/engine/src/auto-trade-processor.cpp @@ -0,0 +1,100 @@ +#include "auto-trade-processor.hpp" + +#include + +#include "auto-trade-options.hpp" +#include "cct_exception.hpp" +#include "timestring.hpp" + +namespace cct { + +AutoTradeProcessor::AutoTradeProcessor(const AutoTradeOptions &autoTradeOptions) + : _exchangeStatusVector(autoTradeOptions.publicExchanges().size()) { + int publicExchangePos = 0; + for (const auto &[exchangeNameEnum, publicExchangeAutoTradeOptions] : autoTradeOptions) { + ExchangeStatus &selectedExchangesStatus = _exchangeStatusVector[publicExchangePos]; + selectedExchangesStatus.pPublicExchangeAutoTradeOptions = &publicExchangeAutoTradeOptions; + for (const auto &[market, marketAutoTradeOptions] : publicExchangeAutoTradeOptions) { + MarketStatus &marketStatus = selectedExchangesStatus.marketStatusVector.emplace_back(); + + marketStatus.market = market; + marketStatus.pMarketAutoTradeOptions = &marketAutoTradeOptions; + + for (std::string_view account : marketAutoTradeOptions.accounts) { + marketStatus.privateExchangeNames.emplace_back(exchangeNameEnum, account); + } + } + ++publicExchangePos; + } +} + +namespace { +const auto &GetAutoTradeMarketConfig(Market market, const auto &publicExchangeAutoTradeOptions) { + const auto it = publicExchangeAutoTradeOptions.find(market); + if (it == publicExchangeAutoTradeOptions.end()) { + throw exception("Should not happen - market not found in account auto trade options"); + } + return it->second; +} + +bool IsQueryTooEarly(TimePoint nowTs, const auto &marketStatus, const auto &publicExchangeAutoTradeOptions) { + const auto &marketAutoTradeOptions = GetAutoTradeMarketConfig(marketStatus.market, publicExchangeAutoTradeOptions); + return marketStatus.lastQueryTime + marketAutoTradeOptions.repeatTime.duration > nowTs; +} +} // namespace + +AutoTradeProcessor::SelectedMarketVector AutoTradeProcessor::computeSelectedMarkets() { + SelectedMarketVector selectedMarketVector; + + auto ts = Clock::now(); + + TimePoint earliestQueryTime = TimePoint::max(); + + for (ExchangeStatus &exchangeStatus : _exchangeStatusVector) { + const auto &publicExchangeAutoTradeOptions = *exchangeStatus.pPublicExchangeAutoTradeOptions; + + auto &marketStatusVector = exchangeStatus.marketStatusVector; + + if (marketStatusVector.empty()) { + continue; + } + + // Sort markets by ascending last query time, discarding those (placed at the end of the vector) which cannot be + // queried right now + std::ranges::sort(marketStatusVector, + [ts, &publicExchangeAutoTradeOptions](const MarketStatus &lhs, const MarketStatus &rhs) { + const bool lhsIsTooEarly = IsQueryTooEarly(ts, lhs, publicExchangeAutoTradeOptions); + const bool rhsIsTooEarly = IsQueryTooEarly(ts, rhs, publicExchangeAutoTradeOptions); + + if (lhsIsTooEarly != rhsIsTooEarly) { + return !lhsIsTooEarly; + } + + return lhs.lastQueryTime < rhs.lastQueryTime; + }); + + MarketStatus &selectedMarketStatus = marketStatusVector.front(); + if (IsQueryTooEarly(ts, selectedMarketStatus, publicExchangeAutoTradeOptions)) { + const auto repeatTime = + GetAutoTradeMarketConfig(selectedMarketStatus.market, publicExchangeAutoTradeOptions).repeatTime.duration; + earliestQueryTime = std::min(earliestQueryTime, selectedMarketStatus.lastQueryTime + repeatTime); + continue; + } + + selectedMarketStatus.lastQueryTime = ts; + selectedMarketVector.emplace_back(selectedMarketStatus.privateExchangeNames, selectedMarketStatus.market); + } + + if (selectedMarketVector.empty() && earliestQueryTime != TimePoint::max()) { + log::debug("Sleeping until {}", TimeToString(earliestQueryTime)); + std::this_thread::sleep_until(earliestQueryTime + std::chrono::milliseconds(1)); + selectedMarketVector = computeSelectedMarkets(); + if (selectedMarketVector.empty()) { + throw exception("Waiting sufficient time should return at least one market for the next turn"); + } + } + + return selectedMarketVector; +} + +} // namespace cct \ No newline at end of file diff --git a/src/engine/src/coincenter-commands-processor.cpp b/src/engine/src/coincenter-commands-processor.cpp index 401e6b60..c4ee623e 100644 --- a/src/engine/src/coincenter-commands-processor.cpp +++ b/src/engine/src/coincenter-commands-processor.cpp @@ -6,6 +6,7 @@ #include #include +#include "auto-trade-options.hpp" #include "balanceoptions.hpp" #include "cct_const.hpp" #include "cct_exception.hpp" @@ -22,11 +23,13 @@ #include "exchange-names.hpp" #include "exchangename.hpp" #include "exchangepublicapi.hpp" +#include "file.hpp" #include "market-trader-factory.hpp" #include "market.hpp" #include "monetaryamount.hpp" #include "queryresultprinter.hpp" #include "queryresulttypes.hpp" +#include "read-json.hpp" #include "replay-options.hpp" #include "signal-handler.hpp" #include "timedef.hpp" @@ -294,18 +297,18 @@ TransferableCommandResultVector CoincenterCommandsProcessor::processGroupedComma break; } case CoincenterCommandType::MarketData: { - std::array marketPerPublicExchange; + std::array marketPerPublicExchangePos; for (const auto &cmd : groupedCommands) { if (cmd.exchangeNames().empty()) { - std::ranges::fill(marketPerPublicExchange, cmd.market()); + std::ranges::fill(marketPerPublicExchangePos, cmd.market()); } else { for (const auto &exchangeName : cmd.exchangeNames()) { - marketPerPublicExchange[exchangeName.publicExchangePos()] = cmd.market(); + marketPerPublicExchangePos[exchangeName.publicExchangePos()] = cmd.market(); } } } - // No return value here, this command is made only for storing purposes. - _coincenter.queryMarketDataPerExchange(marketPerPublicExchange); + // No need to retrieve the returned value here, this command is made only for storing purposes. + _coincenter.queryMarketDataPerExchange(marketPerPublicExchangePos); break; } case CoincenterCommandType::Replay: { @@ -329,6 +332,15 @@ TransferableCommandResultVector CoincenterCommandsProcessor::processGroupedComma _queryResultPrinter.printMarketsForReplay(firstCmd.replayOptions().timeWindow(), marketTimestampSetsPerExchange); break; } + case CoincenterCommandType::AutoTrade: { + const File configFile(firstCmd.getJsonConfigFile(), File::IfError::kThrow); + schema::AutoTradeConfig autoTradeConfig; + ReadJsonOrThrow(configFile.readAll(), autoTradeConfig); + const AutoTradeOptions autoTradeOptions(std::move(autoTradeConfig)); + + _coincenter.autoTrade(autoTradeOptions); + break; + } default: throw exception("Unknown command type"); } diff --git a/src/engine/src/coincenter.cpp b/src/engine/src/coincenter.cpp index 806834aa..f1686b4f 100644 --- a/src/engine/src/coincenter.cpp +++ b/src/engine/src/coincenter.cpp @@ -1,6 +1,7 @@ #include "coincenter.hpp" #include +#include #include #include #include @@ -9,6 +10,7 @@ #include "abstract-market-trader-factory.hpp" #include "algorithm-name-iterator.hpp" +#include "auto-trade-processor.hpp" #include "balanceoptions.hpp" #include "cct_const.hpp" #include "cct_log.hpp" @@ -29,6 +31,7 @@ #include "query-result-type-helpers.hpp" #include "queryresulttypes.hpp" #include "replay-options.hpp" +#include "signal-handler.hpp" #include "time-window.hpp" #include "timedef.hpp" #include "withdrawsconstraints.hpp" @@ -338,6 +341,29 @@ ReplayResults Coincenter::replay(const AbstractMarketTraderFactory &marketTrader return replayResults; } +void Coincenter::autoTrade(const AutoTradeOptions &autoTradeOptions) { + AutoTradeProcessor autoTradeProcessor(autoTradeOptions); + + while (!IsStopRequested()) { + // 1: select exchanges positions for which we are allowed to send a request. + AutoTradeProcessor::SelectedMarketVector selectedMarkets = autoTradeProcessor.computeSelectedMarkets(); + if (selectedMarkets.empty()) { + break; + } + + // 2: Query order books for those exchanges + std::array selectedMarketsPerPublicExchangePos; + for (const AutoTradeProcessor::SelectedMarket &selectedMarket : selectedMarkets) { + selectedMarketsPerPublicExchangePos[selectedMarket.privateExchangeNames.front().publicExchangePos()] = + selectedMarket.market; + } + MarketDataPerExchange marketDataPerExchange = queryMarketDataPerExchange(selectedMarketsPerPublicExchangePos); + + // 3: call algorithms and retrieve their actions + // 4: perform actual actions (Trades, cancel, exit criteria) + } +} + MarketTradingGlobalResultPerExchange Coincenter::replayAlgorithm( const AbstractMarketTraderFactory &marketTraderFactory, std::string_view algorithmName, const ReplayOptions &replayOptions, std::span marketTraderEngines, diff --git a/src/engine/src/coincentercommand.cpp b/src/engine/src/coincentercommand.cpp index 0f1dfba2..4e59e3ff 100644 --- a/src/engine/src/coincentercommand.cpp +++ b/src/engine/src/coincentercommand.cpp @@ -131,4 +131,9 @@ CoincenterCommand& CoincenterCommand::setReplayOptions(ReplayOptions replayOptio return *this; } +CoincenterCommand& CoincenterCommand::setJsonConfigFile(std::string_view jsonConfigFile) { + _specialOptions = jsonConfigFile; + return *this; +} + } // namespace cct diff --git a/src/engine/src/coincentercommands.cpp b/src/engine/src/coincentercommands.cpp index aaa9934e..4c061b5b 100644 --- a/src/engine/src/coincentercommands.cpp +++ b/src/engine/src/coincentercommands.cpp @@ -242,6 +242,10 @@ void CoincenterCommands::addOption(const CoincenterCmdLineOptions &cmdLineOption .setExchangeNames(optionParser.parseExchanges()); } + if (!cmdLineOptions.autoTrade.empty()) { + _commands.emplace_back(CoincenterCommandType::AutoTrade).setJsonConfigFile(cmdLineOptions.autoTrade); + } + optionParser.checkEndParsing(); // No more option part should be remaining } diff --git a/src/engine/src/exchangesorchestrator.cpp b/src/engine/src/exchangesorchestrator.cpp index 80d7aa4f..6fb36eab 100644 --- a/src/engine/src/exchangesorchestrator.cpp +++ b/src/engine/src/exchangesorchestrator.cpp @@ -12,6 +12,7 @@ #include #include +#include "auto-trade-processor.hpp" #include "balanceoptions.hpp" #include "balanceportfolio.hpp" #include "cct_const.hpp" diff --git a/src/schema/include/auto-trade-config.hpp b/src/schema/include/auto-trade-config.hpp new file mode 100644 index 00000000..b04b9482 --- /dev/null +++ b/src/schema/include/auto-trade-config.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include +#include +#include + +#include "cct_const.hpp" +#include "cct_json-serialization.hpp" +#include "cct_smallvector.hpp" +#include "cct_string.hpp" +#include "cct_vector.hpp" +#include "duration-schema.hpp" +#include "generic-object-json.hpp" +#include "market.hpp" +#include "monetaryamount.hpp" +#include "timedef.hpp" + +namespace cct::schema { + +#define CCT_AUTO_TRADE_STOP_CRITERIA_TYPES duration, protectLoss, secureProfit + +enum class AutoTradeStopCriteriaType : int8_t { CCT_AUTO_TRADE_STOP_CRITERIA_TYPES }; + +class AutoTradeStopCriteriaValue { + public: + AutoTradeStopCriteriaValue() = default; + + explicit AutoTradeStopCriteriaValue(std::string_view valueStr); + + ::cct::Duration duration() const { return std::get<::cct::Duration>(_value); } + + int maxEvolutionPercentage() const { return std::get(_value); } + + std::size_t strLen() const; + + char *appendTo(char *buf) const; + + auto operator<=>(const AutoTradeStopCriteriaValue &) const = default; + + private: + using Value = std::variant; + + Value _value; +}; + +struct AutoTradeStopCriterion { + AutoTradeStopCriteriaType type; + AutoTradeStopCriteriaValue value; + + auto operator<=>(const AutoTradeStopCriterion &) const = default; +}; + +struct AutoTradeMarketConfig { + SmallVector accounts; + string algorithmName; + Duration repeatTime{std::chrono::seconds(5)}; + MonetaryAmount baseStartAmount; + MonetaryAmount quoteStartAmount; + + vector stopCriteria; + + using trivially_relocatable = is_trivially_relocatable>::type; +}; + +using AutoTradeExchangeConfig = std::map; + +using AutoTradeConfig = std::map; + +} // namespace cct::schema + +template <> +struct glz::meta<::cct::schema::AutoTradeStopCriteriaType> { + using enum ::cct::schema::AutoTradeStopCriteriaType; + static constexpr auto value = enumerate(CCT_AUTO_TRADE_STOP_CRITERIA_TYPES); +}; + +#undef CCT_AUTO_TRADE_STOP_CRITERIA_TYPES + +namespace glz::detail { +template <> +struct from { + template + static void op(auto &&value, is_context auto &&, It &&it, End &&end) noexcept { + // used as a value. As a key, the first quote will not be present. + auto endIt = std::find(*it == '"' ? ++it : it, end, '"'); + value = ::cct::schema::AutoTradeStopCriteriaValue(std::string_view(it, endIt)); + it = ++endIt; + } +}; + +template <> +struct to { + template + static void op(auto &&value, Ctx &&, B &&b, IX &&ix) { + ::cct::details::ToJson(value, b, ix); + } +}; +} // namespace glz::detail diff --git a/src/schema/src/auto-trade-config.cpp b/src/schema/src/auto-trade-config.cpp new file mode 100644 index 00000000..0a6d98b8 --- /dev/null +++ b/src/schema/src/auto-trade-config.cpp @@ -0,0 +1,60 @@ +#include "auto-trade-config.hpp" + +#include +#include + +#include "durationstring.hpp" +#include "ndigits.hpp" +#include "stringconv.hpp" + +namespace cct::schema { + +namespace { +constexpr int kNbSignificantUnitsDuration = 10; +} + +AutoTradeStopCriteriaValue::AutoTradeStopCriteriaValue(std::string_view valueStr) { + if (valueStr.empty()) { + throw invalid_argument("Unexpected str {} to parse AutoTradeStopCriteriaValue", valueStr); + } + if (valueStr.back() == '%') { + valueStr.remove_suffix(1); + _value = StringToIntegral(valueStr); + } else { + _value = ParseDuration(valueStr); + } +} + +char *AutoTradeStopCriteriaValue::appendTo(char *buf) const { + return std::visit( + [&buf](const auto &value) -> char * { + if constexpr (std::is_same_v, Duration>) { + auto str = DurationToString(value, kNbSignificantUnitsDuration); + std::memcpy(buf, str.data(), str.size()); + return buf + str.size(); + } else if constexpr (std::is_same_v, int>) { + auto str = IntegralToCharVector(value); + std::memcpy(buf, str.data(), str.size()); + return buf + str.size(); + } else { + return buf; + } + }, + _value); +} + +std::size_t AutoTradeStopCriteriaValue::strLen() const { + return std::visit( + [](const auto &value) -> std::size_t { + if constexpr (std::is_same_v, Duration>) { + return DurationToString(value, kNbSignificantUnitsDuration).size(); + } else if constexpr (std::is_same_v, int>) { + return ndigits(value); + } else { + return 0; + } + }, + _value); +} + +} // namespace cct::schema \ No newline at end of file