diff --git a/CMakeLists.txt b/CMakeLists.txt index 8247af7..2fa939f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -738,6 +738,25 @@ if(OPTIONX_BUILD_EXAMPLES) ) endif() + add_executable(telegram_signal_bridge_smoke examples/telegram_signal_bridge_smoke.cpp) + target_compile_features(telegram_signal_bridge_smoke PRIVATE cxx_std_17) + target_include_directories(telegram_signal_bridge_smoke PRIVATE + ${EXAMPLE_INCLUDE_DIRS} + ${EXAMPLE_DEPS_INCLUDE_DIRS} + ) + target_link_directories(telegram_signal_bridge_smoke PRIVATE ${EXAMPLE_LIBRARY_DIRS}) + target_compile_definitions( + telegram_signal_bridge_smoke PRIVATE + ${EXAMPLE_DEFINES} + LOGIT_BASE_PATH="${LOGIT_BASE_PATH_FWD}" + ) + if(MINGW) + target_compile_options(telegram_signal_bridge_smoke PRIVATE -Wa,-mbig-obj) + elseif(MSVC) + target_compile_options(telegram_signal_bridge_smoke PRIVATE /bigobj) + endif() + target_link_libraries(telegram_signal_bridge_smoke PRIVATE ${EXAMPLE_LIBS} optionx_cpp) + add_executable(protocol_v1_bridge_smoke examples/protocol_v1_bridge_smoke.cpp) target_compile_features(protocol_v1_bridge_smoke PRIVATE cxx_std_17) diff --git a/examples/README.md b/examples/README.md index 46c10c0..d4cd657 100644 --- a/examples/README.md +++ b/examples/README.md @@ -55,6 +55,10 @@ Currently maintained examples: - `protocol_v1_named_pipe_bridge_smoke.cpp` starts Bridge Protocol v1 over a local named pipe and, on Windows, can run `--self-test` with a local pipe client. +- `telegram_signal_bridge_smoke.cpp` demonstrates the Telegram parser and live + bridge lifecycle with a deterministic in-memory source. The production + source boundary can be backed by `tg-client-stdio`; this example needs no + Telegram credentials. - `metatrader_file_bridge_smoke.cpp` runs the C++ side of the MetaTrader Common\Files bridge against a temporary command/event layout. - `metatrader_file_command_writer_smoke.cpp` demonstrates the C++ command-writer diff --git a/examples/telegram_signal_bridge_smoke.cpp b/examples/telegram_signal_bridge_smoke.cpp new file mode 100644 index 0000000..86b8a4f --- /dev/null +++ b/examples/telegram_signal_bridge_smoke.cpp @@ -0,0 +1,113 @@ +/// \file telegram_signal_bridge_smoke.cpp +/// \brief Demonstrates Telegram parsing and bridge dispatch with a fake source. + +#include + +#include +#include +#include +#include + +namespace { + +class DemoMessageSource final + : public optionx::bridges::telegram::TelegramMessageSource { +public: + bool start(message_callback_t on_message, error_callback_t on_error) override; + void stop() noexcept override; + void emit(const optionx::bridges::telegram::TelegramRawMessage& message); + +private: + message_callback_t m_on_message; +}; + +std::unique_ptr +make_config(); + +optionx::bridges::telegram::TelegramRawMessage make_message( + std::string text, + std::int64_t message_id); + +} // namespace + +int main() { + // The production source can be an adapter around tg-client-stdio. A fake + // source keeps this example deterministic and runnable without Telegram + // credentials or an authorized session. + auto source = std::make_shared(); + optionx::bridges::telegram::TelegramSignalBridge bridge(source); + if (!bridge.configure(make_config())) { + std::cerr << "failed to configure Telegram bridge\n"; + return 1; + } + + std::int64_t next_signal_id = 0; + bridge.on_signal_id() = [&next_signal_id]() { + return ++next_signal_id; + }; + bridge.on_status_update() = [](const optionx::BridgeStatusUpdate& update) { + std::cout << "status=" << optionx::to_str(update.status); + if (!update.message.empty()) { + std::cout << " message=" << update.message; + } + std::cout << '\n'; + }; + bridge.on_signal_report() = [](const optionx::BridgeSignalReport& report) { + std::cout << "report=" << report.reason_code + << " status=" << optionx::to_str(report.status) << '\n'; + }; + bridge.on_trade_signal() = [](std::unique_ptr signal) { + std::cout << "signal=" << signal->symbol + << " direction=" << optionx::to_str(signal->order_type) + << " duration=" << signal->duration + << " id=" << signal->signal_id << '\n'; + }; + + bridge.run(); + source->emit(make_message("EURUSD BUY 5m", 1)); + source->emit(make_message("EURUSD BUY 5m", 1)); + bridge.shutdown(); + return 0; +} + +namespace { + +bool DemoMessageSource::start(message_callback_t on_message, error_callback_t on_error) { + (void)on_error; + m_on_message = std::move(on_message); + return true; +} + +void DemoMessageSource::stop() noexcept { + m_on_message = {}; +} + +void DemoMessageSource::emit( + const optionx::bridges::telegram::TelegramRawMessage& message) { + if (m_on_message) { + m_on_message(message); + } +} + +std::unique_ptr +make_config() { + auto config = std::make_unique< + optionx::bridges::telegram::TelegramSignalBridgeConfig>(); + config->bridge_id = 9001; + config->fixed_amount = 1.0; + return config; +} + +optionx::bridges::telegram::TelegramRawMessage make_message( + std::string text, + const std::int64_t message_id) { + optionx::bridges::telegram::TelegramRawMessage message; + message.chat_id = "demo-signals"; + message.chat_title = "Demo Telegram Signals"; + message.message_id = message_id; + message.date_ms = 1800000000000; + message.text = std::move(text); + return message; +} + +} // namespace