Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ubuntu-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,12 @@ jobs:
- name: Run protocol_v1_named_pipe_bridge_smoke
run: ./build-linux/protocol_v1_named_pipe_bridge_smoke --self-test

- name: Build telegram_archive_parser_smoke
run: cmake --build build-linux --target telegram_archive_parser_smoke -j

- name: Run telegram_archive_parser_smoke
run: ./build-linux/telegram_archive_parser_smoke

- name: Build market_data_subscription_contract_test
run: cmake --build build-linux --target market_data_subscription_contract_test -j

Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/windows-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ jobs:
- name: Build Bridge Protocol v1 named-pipe smoke example
run: cmake --build build-windows --config Debug --target protocol_v1_named_pipe_bridge_smoke

- name: Build Telegram archive parser smoke example
run: cmake --build build-windows --config Debug --target telegram_archive_parser_smoke

- name: Run MetaTrader file tests
shell: pwsh
run: |
Expand All @@ -92,6 +95,7 @@ jobs:
.\build-windows\Debug\named_pipe_bridge_smoke.exe --self-test
.\build-windows\Debug\protocol_v1_bridge_smoke.exe --self-test
.\build-windows\Debug\protocol_v1_named_pipe_bridge_smoke.exe --self-test
.\build-windows\Debug\telegram_archive_parser_smoke.exe

- name: Test MetaEditor compile smoke script
shell: pwsh
Expand Down
41 changes: 41 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -738,6 +738,44 @@ 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(telegram_archive_parser_smoke examples/telegram_archive_parser_smoke.cpp)
target_compile_features(telegram_archive_parser_smoke PRIVATE cxx_std_17)
target_include_directories(telegram_archive_parser_smoke PRIVATE
${EXAMPLE_INCLUDE_DIRS}
${EXAMPLE_DEPS_INCLUDE_DIRS}
)
target_link_directories(telegram_archive_parser_smoke PRIVATE ${EXAMPLE_LIBRARY_DIRS})
target_compile_definitions(
telegram_archive_parser_smoke PRIVATE
${EXAMPLE_DEFINES}
LOGIT_BASE_PATH="${LOGIT_BASE_PATH_FWD}"
)
if(MINGW)
target_compile_options(telegram_archive_parser_smoke PRIVATE -Wa,-mbig-obj)
elseif(MSVC)
target_compile_options(telegram_archive_parser_smoke PRIVATE /bigobj)
endif()
target_link_libraries(telegram_archive_parser_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)

Expand Down Expand Up @@ -888,6 +926,9 @@ if(OPTIONX_BUILD_TESTS)
bridge_host_test
metatrader_paths_test
telegram_dto_test
telegram_signal_parser_test
telegram_signal_bridge_test
telegram_worker_source_test
)
if(OPTIONX_LIGHTWEIGHT_BRIDGE_SMOKE_TESTS)
list(APPEND OPTIONX_LIGHTWEIGHT_TESTS
Expand Down
8 changes: 8 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ 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.
- `telegram_archive_parser_smoke.cpp` replays exported Telegram raw-message
records through the parser and keeps executable signals, outcomes and
diagnostics separate. It uses deterministic fixtures and 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
Expand Down
81 changes: 81 additions & 0 deletions examples/telegram_archive_parser_smoke.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/// \file telegram_archive_parser_smoke.cpp
/// \brief Demonstrates replaying exported Telegram messages through the parser.

#include <optionx_cpp/bridges/telegram.hpp>

#include <cstdint>
#include <iostream>
#include <string>
#include <utility>
#include <vector>

namespace {

optionx::bridges::telegram::TelegramRawMessage make_message(
std::int64_t message_id,
std::string text,
std::int64_t reply_to_message_id = 0);

std::vector<optionx::bridges::telegram::TelegramRawMessage> demo_archive();

void print_parsed(
const optionx::bridges::telegram::TelegramParsedMessage& parsed);

} // namespace

int main() {
// In production these records arrive one at a time from
// tg-client-stdio::WorkerClient::stream_messages(). Keeping the replay
// loop source-independent makes parser tests and backtests deterministic.
optionx::bridges::telegram::TelegramSignalParser parser;
for (const auto& raw : demo_archive()) {
print_parsed(parser.parse(raw));
}
return 0;
}

namespace {

optionx::bridges::telegram::TelegramRawMessage make_message(
const std::int64_t message_id,
std::string text,
const std::int64_t reply_to_message_id) {
optionx::bridges::telegram::TelegramRawMessage message;
message.chat_id = "-1001234567890";
message.chat_title = "Demo archive";
message.message_id = message_id;
message.date_ms = 1800000000000 + message_id * 1000;
message.reply_to_message_id = reply_to_message_id;
message.text = std::move(text);
return message;
}

std::vector<optionx::bridges::telegram::TelegramRawMessage> demo_archive() {
return {
make_message(100, "EURUSD BUY 5m"),
make_message(101, "EURUSD WIN", 100),
make_message(102, "USDJPY BUY 2 weeks"),
};
}

void print_parsed(
const optionx::bridges::telegram::TelegramParsedMessage& parsed) {
std::cout << "message=" << parsed.raw.message_identity()
<< " signals=" << parsed.signals.size()
<< " outcomes=" << parsed.outcomes.size()
<< " diagnostics=" << parsed.diagnostics.size() << '\n';
for (const auto& signal : parsed.signals) {
std::cout << " signal=" << signal.symbol
<< " direction=" << optionx::to_str(signal.order_type)
<< " duration=" << signal.duration << '\n';
}
for (const auto& outcome : parsed.outcomes) {
std::cout << " outcome=" << outcome.symbol
<< " result=" << static_cast<int>(outcome.result) << '\n';
}
for (const auto& diagnostic : parsed.diagnostics) {
std::cout << " diagnostic=" << diagnostic.code << '\n';
}
}

} // namespace
113 changes: 113 additions & 0 deletions examples/telegram_signal_bridge_smoke.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/// \file telegram_signal_bridge_smoke.cpp
/// \brief Demonstrates Telegram parsing and bridge dispatch with a fake source.

#include <optionx_cpp/bridges/telegram.hpp>

#include <iostream>
#include <memory>
#include <string>
#include <utility>

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<optionx::bridges::telegram::TelegramSignalBridgeConfig>
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<DemoMessageSource>();
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<optionx::TradeSignal> 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<optionx::bridges::telegram::TelegramSignalBridgeConfig>
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
5 changes: 5 additions & 0 deletions guides/bridge-examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ families are grouped by external protocol or adapter contract, not by transport.
| BotBinary/BinaryBot | `optionx_cpp/bridges/bot_binary.hpp` | `BotBinaryBridgeConfig` | `examples/bot_binary_bridge_smoke.cpp` | Compatibility intake for BotBinary `request=...` HTTP URLs and file-signal filenames. |
| BotBinary command helper | `optionx_cpp/bridges/bot_binary.hpp` | none | `examples/bot_binary_command_builder_smoke.cpp` | Formatter/parser helper for legacy BotBinary command strings. |
| Legacy trading pipe | `optionx_cpp/bridges/legacy_trading.hpp` | `LegacyTradingBridgeConfig` | `examples/named_pipe_bridge_smoke.cpp` | Compatibility bridge for the older named-pipe JSON trading protocol. |
| Telegram signal bridge | `optionx_cpp/bridges/telegram.hpp` | `TelegramSignalBridgeConfig` | `examples/telegram_signal_bridge_smoke.cpp` | Deterministic parser, source boundary, signal callback, duplicate report, and shutdown lifecycle without Telegram credentials. |

## Choosing A Bridge

Expand All @@ -29,6 +30,10 @@ families are grouped by external protocol or adapter contract, not by transport.
BotBinary command string or file-signal filename.
- Use the legacy trading pipe only for old clients that already speak that
named-pipe JSON format.
- Use the Telegram signal bridge when a user-client source must turn channel
messages into normalized signals. The production source is expected to be a
`tg-client-stdio` adapter; the parser and bridge can be tested independently
with a fake source.

Compatibility bridges should convert their external payload into `TradeSignal`
callbacks and reports. They do not need to expose Bridge Protocol v1 endpoints
Expand Down
7 changes: 7 additions & 0 deletions guides/bridge-taxonomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,18 @@ OptionX protocol together.
| TradingView extension | `optionx_cpp/bridges/trading_view.hpp` | Adapter for payloads emitted by `browser_extensions/tradingview-alert-extension`. | HTTP. |
| BinaryBot/BotBinary | `optionx_cpp/bridges/bot_binary.hpp` | Compatibility bridge and formatter/parser helpers for observed BinaryBot-compatible command strings. | HTTP `request=...`, file-signal name. |
| Legacy trading pipe | `optionx_cpp/bridges/legacy_trading.hpp` | Compatibility bridge for the older named-pipe JSON trading protocol. | Named pipe. |
| Telegram signal bridge | `optionx_cpp/bridges/telegram.hpp` | User-client message parser and live signal adapter. The Telegram worker/session remains an external source boundary. | stdio worker source, with source adapters kept outside the parser. |

All families converge internally on OptionX DTOs such as `TradeSignal`,
`TradeRequest`, account snapshots and bridge callbacks. The public wire format
does not need to be the same for every family.

Telegram is a source adapter family rather than a transport-only family. The
public bridge consumes `TelegramMessageSource` callbacks, while authorization,
proxy handling, dialog discovery and historical export belong to the
`tg-client-stdio` worker/supervisor layer. Historical export remains a separate
archive capability and is not added to `BaseBridge`.

For practical embedding of the native HTTP/WebSocket server bridge, see
`guides/protocol-v1-bridge-runtime.md`. For runnable bridge entry points, see
`examples/README.md`.
Expand Down
Loading
Loading