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
27 changes: 27 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,33 @@
- [Commit conventions](guides/commit-conventions.md) - формат коммитов, если
пользователь просит создать commit.

## Header Ownership And Include Context

Before editing a header under `include/optionx_cpp`, classify it as a
supported public entry point or an internal leaf:

- Supported public entry points are the aggregate/facade headers listed in
`guides/api-and-header-contracts.md`. Headers under paths such as
`platforms/<Platform>/`, `market_data/`, and `data/*` are internal leaves
unless the documentation explicitly promotes them.
- An internal leaf is not a standalone include target. Do not add a project
cross-domain include, a `../` path, or a broad aggregate merely to make the
leaf compile in isolation. A leaf may use standard-library, third-party,
and same-family dependencies supplied by its owning domain.
- The nearest owning aggregate/facade owns the complete cross-domain include
closure and its order. Add prerequisites there, before including the leaf.
For example, `platforms.hpp` prepares `utils.hpp`, `data.hpp`,
`components.hpp`, and the platform contracts before including
`platforms/IntradeBarPlatform.hpp`; that context transitively supplies
`platforms/IntradeBarPlatform/ObservedTickHistory.hpp`.
- Tests and examples that verify the include contract must include the same
supported aggregate used by consumers. Do not use a direct leaf include as
an aggregate/include-policy test. A direct leaf test is valid only when the
leaf is intentionally documented and tested as self-contained.
- When the ownership is unclear, inspect the owning aggregate and its include
order first, then verify the chosen public path with an aggregate consumer
compile before changing a leaf include.

## Critical Defaults

- Перед правками проверь `git status --short` и не перетирай чужие изменения.
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,7 @@ if(OPTIONX_BUILD_TESTS)
telegram_worker_source_test
trading_view_bridge_test
market_data_tick_history_contract_test
intrade_observed_tick_history_test
)
if(OPTIONX_LIGHTWEIGHT_BRIDGE_SMOKE_TESTS)
list(APPEND OPTIONX_LIGHTWEIGHT_TESTS
Expand Down
48 changes: 48 additions & 0 deletions guides/api-and-header-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,28 @@
`include`. Он совпадает с consumer contract `<optionx_cpp/...>` и не маскирует
неверные cross-domain quoted includes во вложенных headers.

### Internal Leaf Include Context

Classify a header before adding a project include. A supported public
aggregate/facade owns the include closure for its internal leaves; a leaf is
not automatically a standalone include target.

- Headers under `platforms/<Platform>/`, `market_data/`, and `data/*` are
internal leaves unless this document explicitly lists them as public entry
points.
- Do not add a cross-domain project include, `../` path, or broad aggregate to
an internal leaf solely to make a direct include compile. Put the prerequisite
in the owning aggregate, in the intended order, using the installed
`<optionx_cpp/...>` spelling for cross-domain edges.
- For example, `platforms.hpp` prepares shared `utils`, `data`, `components`,
and platform prerequisites before including `platforms/IntradeBarPlatform.hpp`.
The latter then includes `platforms/IntradeBarPlatform/ObservedTickHistory.hpp`
through the prepared context.
- Include-contract tests and examples must include the supported aggregate
(`<optionx_cpp/platforms.hpp>` in this case), not the internal leaf. A direct
leaf test is appropriate only when standalone leaf compilation is an
intentional documented contract.

## Header-Only Ownership

`optionx_cpp` - header-only C++17 библиотека. Большая часть публичной
Expand Down Expand Up @@ -268,6 +290,32 @@ Contract rules:
- `MarketDataContinuityService` is the thin helper for routing recovered history
into the same bar batch pipeline. It marks payload bars as
`HISTORICAL` and, for gap recovery, `BACKFILL`.
- Historical ticks use `TickHistoryRequest` and `TickHistoryResult` with an
inclusive millisecond range. `range_complete` is an explicit completeness
assertion; successful observations with `false` must not be used as proof of
continuity. Equal timestamps are valid, and exact duplicate removal remains
provider/consumer policy.

### Intrade observed tick history

`platforms::IntradeBarPlatform::fetch_tick_history()` reads a bounded in-memory
archive populated by the platform's `/price_now` polling path. These are
observed quote snapshots, not a historical tick endpoint supplied by the
broker:

- `Tick::time_ms` keeps the broker timestamp, whose effective granularity is
one second.
- `Tick::received_ms` keeps local receipt time when the polling response was
parsed.
- The archive is per symbol, session-scoped, non-persistent, and bounded by
item count and lookback duration.
- Identical market observations are deduplicated; distinct observations with
the same second remain separate.
- `range_complete=true` is returned only for aligned requests where every
expected one-second sample is retained. Missing, unaligned, or evicted
samples leave it false while the returned observations remain usable.
- `trade_check2.php` is a trade settlement/result check keyed by deal ID. It
is not the implementation of `fetch_tick_history()`.
- `BarSubscriptionRequest::continuity` enables Router-owned bar prefill and
optional timestamp-gap recovery. Router buffers live batches until the
corresponding history operation completes and reports route-scoped progress
Expand Down
3 changes: 1 addition & 2 deletions guides/build-and-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,7 @@ destination `OptionX` folders.
обновляй тест, который подключает intended public entry point:

```cpp
#include <optionx_cpp/data.hpp>
#include <optionx_cpp/platforms/IntradeBarPlatform.hpp>
#include <optionx_cpp/platforms.hpp>
```

Direct leaf includes допустимы для white-box tests only when that domain
Expand Down
4 changes: 4 additions & 0 deletions guides/coding-style.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
prefix, for example `<optionx_cpp/data/trading.hpp>` or
`<optionx_cpp/utils/tasks.hpp>`. Do not use quoted `"optionx_cpp/..."` paths
and do not rely on `include/optionx_cpp` as an additional include root.
- This cross-domain rule applies to supported aggregates and facades. Internal
leaves receive that context from their owner and must not add a project
cross-domain include, `../` path, or broad aggregate solely to compile in
isolation.
- Prefer the nearest aggregate header for public-domain and cross-domain
dependencies; do not rebuild aggregate include order inside leaf DTO headers.
- Do not use `../` in `#include` directives.
Expand Down
22 changes: 17 additions & 5 deletions guides/market-data-router.md
Original file line number Diff line number Diff line change
Expand Up @@ -634,11 +634,23 @@ service.request_tick_history_batch(
true); // require_complete_range
```

`MarketDataRouter` still integrates continuity only for bars. No current
provider in this repository exposes authoritative tick history, so the base
operation returns `false` until a provider-specific endpoint and semantics are
implemented. This is intentional: a current-price snapshot cannot be reused as
historical tick data.
`MarketDataRouter` still has its mature continuity state machine on bars. The
Intrade Bar provider now also implements `fetch_tick_history()` as a bounded,
session-scoped archive of observed `/price_now` snapshots. This is useful for
short reconnect windows, but it is not an authoritative broker tick archive:

- broker timestamps have one-second granularity;
- the archive starts empty for a new authenticated session and evicts old data;
- `range_complete=true` means every expected observed second is retained, not
that every broker micro-event was captured;
- distinct snapshots sharing one second are preserved, while exact duplicate
snapshots are removed;
- `trade_check2.php` remains a settlement/trade-result API and is not used for
range history.

Router tick continuity is the next layer. Until that integration is enabled,
callers can use the provider operation directly and must treat
`range_complete=false` as an observation result rather than continuity proof.

## Owner Loop And Bot Threads

Expand Down
23 changes: 18 additions & 5 deletions guides/market-data-router.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -791,8 +791,21 @@ service.request_tick_history_batch(
true); // require_complete_range
```

`MarketDataRouter` пока интегрирует continuity только для bars. Ни один
текущий provider в этом репозитории не предоставляет authoritative tick
history, поэтому базовая операция возвращает `false`, пока не появятся
конкретный endpoint и его семантика. Это намеренно: текущий price snapshot
нельзя выдавать за исторические ticks.
`MarketDataRouter` уже имеет полноценную state machine continuity для bars.
Кроме того, Intrade Bar теперь реализует `fetch_tick_history(...)` через
ограниченный архив наблюдаемых snapshots из `/price_now`. Это полезно для
короткого восстановления после reconnect, но не является authoritative
broker tick archive:

- broker timestamps имеют гранулярность в одну секунду;
- архив пуст для новой authenticated session и вытесняет старые данные;
- `range_complete=true` означает наличие каждого ожидаемого наблюдаемого
second, а не получение каждого micro-event брокера;
- разные snapshots с одним timestamp сохраняются, а полностью одинаковые
snapshots удаляются;
- `trade_check2.php` остаётся settlement/trade-result API и не используется
для range history.

Интеграция tick continuity в Router выполняется следующим слоем. До неё
вызывающий код может использовать provider operation напрямую и обязан
считать `range_complete=false` observations, а не доказательством continuity.
16 changes: 11 additions & 5 deletions guides/platform-api-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ long-lived condition hub does not retain a stale tradable snapshot.
| `apply_subscriptions(batch, callback)` | Атомарно применить набор subscribe/unsubscribe изменений | Одиночные helpers являются wrappers над batch |
| `unsubscribe(handle, callback)` | Остановить live stream | Handle должен принадлежать этому provider instance |
| `fetch_bar_history(request, callback)` | Запросить исторические бары | Возвращает `BarHistoryResult`, а не пустой массив при ошибке |
| `fetch_tick_history(request, callback)` | Получить наблюдаемые или исторические ticks | Inclusive millisecond range; `range_complete` отделяет доказанную полноту от observations |

Subscription rules:

Expand Down Expand Up @@ -128,6 +129,9 @@ Subscription rules:
timer-based final snapshot to be delivered.
- `MarketDataContinuityService` routes recovered historical bars into the same
`BarDataBatch` pipeline and marks them as `HISTORICAL`/`BACKFILL`.
- `TickHistoryRequest` and `TickHistoryResult` use inclusive millisecond ranges.
A successful result may be useful even when `range_complete=false`; that
flag is the provider's proof that the requested range is fully accounted for.
- `BarSubscriptionRequest::continuity` lets `MarketDataRouter` request initial
bar history before live delivery and optionally recover timestamp gaps. Live
batches are buffered while history is in flight. Route-scoped progress is
Expand All @@ -146,11 +150,13 @@ Subscription rules:
`READY`, while a plain or completed `PREFILL` route does not gain outage
recovery. Tick routes do not have this guarantee because the provider contract
still lacks generic tick-history.
- Router continuity is currently bar-only. Providers have a separate
`fetch_tick_history()` contract with inclusive millisecond ranges and an
explicit `range_complete` result, but no current provider implements
authoritative tick history yet. See the complete EN/RU Router guides and
`market_data_continuity_example.cpp`.
- Router continuity is currently bar-first. Intrade additionally exposes a
bounded, session-scoped observed-tick archive populated by `/price_now`.
Its timestamps have one-second broker granularity, and `range_complete=true`
means every expected observed second is present in the retained archive, not
that every broker micro-event was captured. The archive is non-persistent and
starts empty for a new authenticated session. `trade_check2.php` remains a
settlement/trade-result endpoint and is not used for tick history.
- `BaseMarketDataProvider` is non-copyable and non-movable so provider identity
cannot be duplicated after handles were issued.
- Public subscriptions describe consumer routing. Internal platform polling or
Expand Down
17 changes: 9 additions & 8 deletions guides/refactor-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,18 @@ series. Keep it short and remove items once they are handled.
monotonic stale/degraded durations.
- The generic tick-history foundation now has typed timestamp-range request and
result DTOs, a provider hook, ordering/range validation, explicit
completeness semantics, and a batch adapter. No provider-specific endpoint
or Router tick continuity is implied yet.
completeness semantics, and a batch adapter.
- Intrade Bar now exposes a bounded, session-scoped observed-tick archive fed by
`/price_now`. It preserves distinct same-second snapshots and reports
`range_complete` only for proven one-second coverage; it is not a persistent
or authoritative broker tick archive.

## Next PR Candidates

- Add a provider-specific tick-history implementation only where the provider
can supply authoritative historical ticks, then integrate it with Router
continuity in a separate change.
- Integrate tick continuity into Router only after a provider-specific history
endpoint defines sequence/timestamp deduplication, retry, and history-to-live
boundary semantics.
- Integrate Intrade's observed-tick archive with Router continuity while keeping
incomplete ranges fail-closed and documenting the session/retention limit.
- Add an authoritative provider tick-history implementation only if a broker
later exposes one; do not treat `trade_check2.php` as a range-history API.
- Replace the dense-bar assumption with an explicit provider completeness
capability or `range_complete` history result for session-based markets.
- Allow reconnect candle boundaries to use a broker-aligned clock instead of
Expand Down
20 changes: 18 additions & 2 deletions include/optionx_cpp/platforms/IntradeBarPlatform.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "IntradeBarPlatform/AuthData.hpp"
#include "IntradeBarPlatform/AccountInfoData.hpp"
#include "IntradeBarPlatform/ApiResponses.hpp"
#include "IntradeBarPlatform/ObservedTickHistory.hpp"
#include "IntradeBarPlatform/http_utils.hpp"
#include "IntradeBarPlatform/http_parsers.hpp"
#include "IntradeBarPlatform/HttpClientComponent.hpp"
Expand Down Expand Up @@ -46,7 +47,8 @@ namespace optionx::platforms {
///
/// Initializes all required components, including HTTP communication, authentication,
/// balance tracking, trade execution, and price updates.
IntradeBarPlatform()
explicit IntradeBarPlatform(
intrade_bar::IntradeObservedTickHistoryOptions tick_history_options = {})
: BaseTradingPlatform(std::make_shared<intrade_bar::AccountInfoData>()),
m_http_client(*this),
m_request_manager(*this, m_http_client),
Expand All @@ -55,7 +57,8 @@ namespace optionx::platforms {
m_balance_manager(*this, m_request_manager, m_account_info),
m_active_trades_sync_manager(*this, m_request_manager, m_account_info),
m_trading_condition_manager(*this, m_account_info),
m_price_manager(*this, m_request_manager),
m_tick_history(std::move(tick_history_options)),
m_price_manager(*this, m_request_manager, m_tick_history),
m_btc_price_manager(*this),
m_fx_price_websocket_manager(*this),
m_market_data_subscriptions(
Expand Down Expand Up @@ -153,6 +156,18 @@ namespace optionx::platforms {
return true;
}

/// \brief Returns observed Intrade polling ticks from the in-memory archive.
/// \param request Inclusive broker-time range in milliseconds.
/// \param callback Callback receiving observations or a typed failure.
/// \return True when the request was accepted for local processing.
bool fetch_tick_history(
const TickHistoryRequest& request,
market_data::BaseMarketDataProvider::tick_history_callback_t callback) override {
if (!callback || !request.valid()) return false;
callback(m_tick_history.fetch(request));
return true;
}

/// \brief Returns the live bar data callback.
market_data::BaseMarketDataProvider::bars_callback_t& on_bar_data() override {
return m_bar_data_callback;
Expand Down Expand Up @@ -236,6 +251,7 @@ namespace optionx::platforms {
intrade_bar::BalanceManager m_balance_manager; ///< Tracks account balance.
intrade_bar::ActiveTradesSyncManager m_active_trades_sync_manager; ///< Syncs broker active trade snapshots.
intrade_bar::TradingConditionManager m_trading_condition_manager; ///< Publishes current trading conditions.
intrade_bar::IntradeObservedTickHistory m_tick_history; ///< Bounded polling tick archive.
intrade_bar::PriceManager m_price_manager; ///< Retrieves and updates price data.
intrade_bar::BtcPriceManager m_btc_price_manager;///< Retrieves BTC/USDT quotes from the websocket stream.
intrade_bar::FxPriceWebSocketManager m_fx_price_websocket_manager; ///< Retrieves FX quotes from websocket streams.
Expand Down
Loading
Loading