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
8 changes: 8 additions & 0 deletions .github/workflows/ubuntu-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ jobs:
- name: Configure
run: cmake -S . -B build-linux -DOPTIONX_BUILD_DEPS=ON -DOPTIONX_BUILD_TESTS=ON -DOPTIONX_BUILD_EXAMPLES=ON

- name: Build lifecycle stack test and example
run: cmake --build build-linux --target lifecycle_stack_test lifecycle_stack_example -j

- name: Run lifecycle stack test and example
run: |
./build-linux/lifecycle_stack_test --gtest_brief=1
./build-linux/lifecycle_stack_example

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

Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/windows-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ jobs:
-DOPTIONX_BUILD_EXAMPLES=ON
-DOPTIONX_LIGHTWEIGHT_BRIDGE_SMOKE_TESTS=ON

- name: Build lifecycle stack test and example
run: >
cmake --build build-windows --config Debug --target
lifecycle_stack_test lifecycle_stack_example

- name: Run lifecycle stack test and example
shell: pwsh
run: |
$env:PATH = "$PWD\build-windows\bin;$PWD\build-windows\Debug;$env:PATH"
.\build-windows\Debug\lifecycle_stack_test.exe --gtest_brief=1
.\build-windows\Debug\lifecycle_stack_example.exe

- name: Build MetaTrader path discovery test
run: cmake --build build-windows --config Debug --target metatrader_paths_test

Expand Down
8 changes: 8 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
контракт Router, provider registry, replay, ownership и bot-thread dispatch.
- [Market data router guide RU](guides/market-data-router.ru.md) - русский
перевод, обновляемый вместе с каноническим руководством.
- [Lifecycle stack guide](guides/lifecycle-stack.md) - канонический контракт
общего process/shutdown API и staged reverse-order shutdown.
- [Lifecycle stack guide RU](guides/lifecycle-stack.ru.md) - русский перевод,
обновляемый вместе с каноническим руководством.
- [Codebase orientation](guides/codebase-orientation.md) - карта проекта,
DDD-слои, зависимости, расширение и безопасные точки входа.
- [Build and test](guides/build-and-test.md) - CMake options, зависимости,
Expand Down Expand Up @@ -91,3 +95,7 @@
`guides/market-data-router.md`. Любое смысловое изменение синхронизируй с
`guides/market-data-router.ru.md` в том же PR; русский перевод не является
источником обратных изменений английского контракта.
- Для общего lifecycle stack каноническая версия - английская
`guides/lifecycle-stack.md`. Любое смысловое изменение синхронизируй с
`guides/lifecycle-stack.ru.md` в том же PR; русский перевод не является
источником обратных изменений английского контракта.
36 changes: 36 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,41 @@ if(OPTIONX_BUILD_EXAMPLES)
)
endif()

add_executable(lifecycle_stack_example examples/lifecycle_stack_example.cpp)

target_include_directories(lifecycle_stack_example PRIVATE
${EXAMPLE_INCLUDE_DIRS}
${EXAMPLE_DEPS_INCLUDE_DIRS}
)

target_link_directories(lifecycle_stack_example PRIVATE ${EXAMPLE_LIBRARY_DIRS})
target_compile_definitions(
lifecycle_stack_example PRIVATE
${EXAMPLE_DEFINES}
LOGIT_BASE_PATH="${LOGIT_BASE_PATH_FWD}"
)
target_link_libraries(lifecycle_stack_example PRIVATE ${EXAMPLE_LIBS} optionx_cpp)

if(OPTIONX_BUILD_DEPS)
add_dependencies(lifecycle_stack_example mdbx-static AES)
endif()

foreach(dll ${EXAMPLE_DLL_FILES})
add_custom_command(TARGET lifecycle_stack_example POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${dll}" "$<TARGET_FILE_DIR:lifecycle_stack_example>"
)
endforeach()

if(WIN32)
add_custom_command(TARGET lifecycle_stack_example POST_BUILD
COMMAND ${CMAKE_COMMAND}
-DOPTIONX_RUNTIME_DLL_DIR="${EXAMPLE_BUILD_LIBS_DIR}/bin"
-DOPTIONX_RUNTIME_TARGET_DIR="$<TARGET_FILE_DIR:lifecycle_stack_example>"
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/copy_runtime_dlls.cmake"
)
endif()

add_executable(intrade_market_data_example examples/intrade_market_data_example.cpp)

target_include_directories(intrade_market_data_example PRIVATE
Expand Down Expand Up @@ -1032,6 +1067,7 @@ if(OPTIONX_BUILD_TESTS)

set(OPTIONX_LIGHTWEIGHT_TESTS
bridge_host_test
lifecycle_stack_test
metatrader_paths_test
telegram_dto_test
telegram_signal_parser_test
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ OptionX is a C++ library for working with APIs of various brokers and trading pl

- Market-data routing, provider selection, replay, and bot threads:
[English](guides/market-data-router.md) | [Русский](guides/market-data-router.ru.md)
- Optional common module processing and staged shutdown:
[English](guides/lifecycle-stack.md) | [Русский](guides/lifecycle-stack.ru.md)
135 changes: 135 additions & 0 deletions examples/lifecycle_stack_example.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#include <optionx_cpp/lifecycle.hpp>
#include <optionx_cpp/market_data.hpp>

#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <utility>
#include <vector>

namespace {

namespace lifecycle = optionx::lifecycle;
namespace market_data = optionx::market_data;

class OwnerLoop final : public lifecycle::ILifecycleModule {
public:
bool post(std::function<void()> task) {
if (!task) return false;
std::lock_guard<std::mutex> lock(m_mutex);
if (m_shutdown_requested) return false;
m_tasks.push_back(std::move(task));
return true;
}

void process() override {
std::vector<std::function<void()>> tasks;
{
std::lock_guard<std::mutex> lock(m_mutex);
tasks.swap(m_tasks);
}
for (auto& task : tasks) task();

std::lock_guard<std::mutex> lock(m_mutex);
if (m_shutdown_requested && m_tasks.empty()) m_stopped = true;
}

void shutdown() noexcept override {
std::lock_guard<std::mutex> lock(m_mutex);
m_shutdown_requested = true;
if (m_tasks.empty()) m_stopped = true;
}

[[nodiscard]] bool is_stopped() const noexcept override {
std::lock_guard<std::mutex> lock(m_mutex);
return m_stopped;
}

private:
mutable std::mutex m_mutex;
std::vector<std::function<void()>> m_tasks;
bool m_shutdown_requested = false;
bool m_stopped = false;
};

class DeferredProvider final : public market_data::BaseMarketDataProvider {
public:
explicit DeferredProvider(OwnerLoop& owner_loop)
: m_owner_loop(owner_loop) {}

bool subscribe_ticks(
market_data::TickSubscriptionRequest request,
subscription_callback_t callback) override {
auto subscription = market_data::MarketDataSubscriptionHandle::from_tick_request(
provider_id(),
m_next_subscription_id++,
request);
if (callback) {
callback(market_data::MarketDataSubscriptionResult::subscribed(
std::move(subscription)));
}
return true;
}

bool unsubscribe(
market_data::MarketDataSubscriptionHandle subscription,
subscription_callback_t callback) override {
return m_owner_loop.post(
[subscription = std::move(subscription),
callback = std::move(callback)]() mutable {
if (callback) {
callback(market_data::MarketDataSubscriptionResult::unsubscribed(
std::move(subscription)));
}
});
}

private:
OwnerLoop& m_owner_loop;
market_data::SubscriptionId m_next_subscription_id = 1;
};

class QuoteSink final : public market_data::IMarketDataSubscriber {};

} // namespace

int main() {
OwnerLoop owner_loop;
DeferredProvider provider(owner_loop);
market_data::MarketDataRouter router(
[&owner_loop](market_data::MarketDataRouter::owner_task_t task) {
return owner_loop.post(std::move(task));
});
auto subscriber = std::make_shared<QuoteSink>();

lifecycle::LifecycleStack application;
// Registration order declares dependencies: executor first, Router next.
if (!application.add_module(owner_loop) ||
!application.add_module(router)) {
std::cerr << "Lifecycle registration failed\n";
return 1;
}

auto route = router.subscribe_ticks(
provider,
subscriber,
market_data::TickSubscriptionRequest("EURUSD"));
application.process();
if (!route.active()) {
std::cerr << "Subscription did not become active\n";
return 1;
}

application.shutdown();
while (!application.is_stopped()) {
application.process();
}
if (route.valid()) {
std::cerr << "Routed subscription cleanup did not finish\n";
return 1;
}

std::cout << "Lifecycle stopped after routed subscription cleanup\n";
return 0;
}
22 changes: 22 additions & 0 deletions guides/api-and-header-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,28 @@ etc.) являются implementation detail конкретной платфор
метод сначала должен появиться на facade/base contract, а затем делегироваться
в manager.

## Common Lifecycle Contract

`optionx_cpp/lifecycle.hpp` exposes the optional
`lifecycle::ILifecycleModule` and `lifecycle::LifecycleStack` API.

- A module implements `process()`, idempotent `shutdown() noexcept`, and the
terminal predicate `is_stopped()`.
- The stack stores non-owning references. Registered modules must outlive the
stack and its complete shutdown.
- Registration order is dependency order. Processing runs forward; shutdown is
staged in reverse, one module at a time.
- Dependencies remain processable until the current dependent module stops.
- Stack calls are owner-loop confined. The stack does not create threads.
- Initialization and `run()` remain explicit because existing module startup
contracts differ.
- `MarketDataRouter` and `BaseTradingPlatform` implement the common interface.
Direct lifecycle calls remain supported.

See [lifecycle-stack.md](lifecycle-stack.md) for the complete contract and
[lifecycle-stack.ru.md](lifecycle-stack.ru.md) for the synchronized Russian
version.

## Account Info Subscriber Contract

`components::AccountInfoHub` is an optional fan-out adapter for the single
Expand Down
1 change: 1 addition & 0 deletions guides/build-and-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ targets.

- `examples/event_mediator_test.cpp`
- `examples/intrade_bar_api_example.cpp`
- `examples/lifecycle_stack_example.cpp`
- `examples/market_data_router_example.cpp`
- `examples/market_data_subscriber_base_example.cpp`
- `examples/task_manager_example.cpp`
Expand Down
4 changes: 2 additions & 2 deletions guides/codebase-orientation.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
## Public Include Points

Главная include-точка - `include/optionx_cpp/optionx.hpp`. Она включает
`utils.hpp`, `data.hpp`, `storages.hpp`, `components.hpp`, `platforms.hpp`,
`bridges.hpp`.
`utils.hpp`, `lifecycle.hpp`, `data.hpp`, `storages.hpp`, `components.hpp`,
`platforms.hpp`, `bridges.hpp`.

Aggregate headers в корне `include/optionx_cpp` - часть публичной поверхности.
Если добавляешь новый публичный DTO/component/platform, проверь соответствующий
Expand Down
13 changes: 13 additions & 0 deletions guides/implementation-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,19 @@ Lifecycle:
- Registered components хранятся как raw pointers; concrete platform должна
владеть ими как fields и гарантировать lifetime.

### Optional Application Lifecycle Stack

`lifecycle::LifecycleStack` composes top-level modules without replacing their
direct APIs. Register dependencies first. Normal processing runs forward;
shutdown is staged in reverse and does not stop a lower-level executor until
the current dependent module reports `is_stopped()`.

The stack is non-owning and owner-loop confined. It intentionally does not call
`initialize()` or `run()` because existing platform, component, Router, and bot
startup contracts are not equivalent. Keep startup explicit and use the stack
only when common processing and shutdown are useful. The canonical contract is
in [lifecycle-stack.md](lifecycle-stack.md).

### IntradeBar Delayed Retry Lifecycle Note

Do not report a use-after-free risk for the current IntradeBar settings-switch
Expand Down
Loading
Loading