Skip to content
Closed
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 AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
задач с изменением файлов.
- [Project overview](guides/project-overview.md) - назначение библиотеки,
публичная поверхность, основные домены и поток выполнения.
- [Trading system architecture](guides/TRADING_SYSTEM_ARCHITECTURE.md) -
границы нового домена торговых систем, identity model и adapter boundary.
- [Trading system reuse audit](guides/TRADING_SYSTEM_REUSE_AUDIT.md) -
инвентаризация существующих DTO, платформ, Router и storage перед runtime.
- [Trading system contracts](guides/TRADING_SYSTEM_CONTRACTS.md) - предложенные
минимальные lifecycle, context, branching и risk contracts.
- [Trading system implementation plan](guides/TRADING_SYSTEM_IMPLEMENTATION_PLAN.md) -
последовательность небольших PR и первый synthetic vertical slice.
- [Platform API guide](guides/platform-api-guide.md) - практический справочник
по платформам, модулям, событиям, DTO, storage и bridge API.
- [API and header contracts](guides/api-and-header-contracts.md) - публичный
Expand Down
142 changes: 142 additions & 0 deletions guides/TRADING_SYSTEM_ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# OptionX Trading System Architecture

## Status

This is the foundation decision for a future `optionx::trading_system` domain.
It is intentionally independent of ADELIA, MegaConnector, GUI code and broker
implementations. This document defines boundaries and invariants; it does not
authorize a production runtime implementation by itself.

## Responsibility Boundary

OptionX has two related but separate responsibilities:

| Area | Responsibility |
| --- | --- |
| Broker and data APIs | Authentication, account data, market data, trade requests, broker results and persistence primitives. |
| Trading system | Signals, decision context, filters, sizing, risk admission, intent lifecycle, series progression and recovery contracts. |

MegaConnector composes these primitives into an application. ADELIA may expose
the same primitives as graph nodes and typed edges. Neither dependency is
visible from OptionX headers. A normal C++ bot must be able to use the trading
system without either project.

The intended dependency direction is:

```text
broker/data APIs -> trading-system contracts -> host composition
^
ADELIA/MegaConnector adapter
```

The arrow is a boundary, not a required call sequence. A signal can originate
from a bridge, a market-data event, a schedule, or an active module.

## Core Identity Model

The system keeps these identities distinct:

| Identity | Meaning | Lifetime |
| --- | --- | --- |
| `signal_id` | Immutable source event and correlation identity. | Persistent source record. |
| `intent_id` | One candidate logical operation after policy decisions. | Persistent until terminal outcome. |
| `series_id` | Stateful progression such as martingale or anti-martingale. | Persistent across operations and restarts. |
| `execution_group_id` | One logical operation represented by one or more physical broker trades. | Persistent until all members settle or become unknown. |
| `trade_id` | OptionX persistent identity for one physical trade record. | Existing `TradeRecordDB` identity. |
| `broker_trade_id` | Broker-side numeric or string identity. | Available only after broker acceptance, or unresolved. |

`TradeSignal` remains the source event. It must not be mutated to represent a
later branch. A `TradeIntent` is the mutable, policy-approved candidate. A
`TradeRequest` is the broker-facing physical request. A `TradeResult` describes
the observed execution/outcome, and a `TradeRecord` is its durable snapshot.

## Lifecycle Invariants

The proposed logical intent lifecycle is:

```text
PROPOSED -> REJECTED
-> ADMITTED -> SUBMITTING -> EXECUTED -> SETTLED
\-> UNKNOWN
-> CANCELLED
```

`UNKNOWN` is not `CANCELLED`. It covers a request that may have reached the
broker but whose acceptance is not proven. It requires reconciliation before a
retry or release of reserved risk. Terminal delivery is idempotent by
`intent_id`, `execution_group_id`, `trade_id` and a settlement/event identity;
duplicate callbacks must not advance a series twice.

The state source of truth is durable operation state. Event delivery is a
notification mechanism and cannot be required to reach every upstream module
for correctness. RAII manages subscriptions and runtime resources; it does
not replace persistence or recovery after a process crash.

## Branching and Execution Cardinality

Branching is allowed when the intent declares its cardinality:

| Policy | Meaning |
| --- | --- |
| `ZERO_OR_ONE` | One physical execution or an explicit rejection. |
| `EXACTLY_ONE` | Exactly one eligible alternative must be selected. |
| `MULTIPLE` | Several independent physical executions are allowed. |
| `SPLIT_GROUP` | Several physical trades form one logical execution group. |

Series progression consumes the aggregate logical outcome, not an arbitrary
branch callback. A split group remains non-terminal while any member is
unknown. For a settled group, aggregation is explicit, normally by total net
profit rather than by the count of wins.

## Contexts

Trading decisions read a single immutable decision snapshot composed of small
contexts:

- signal context;
- account context, including freshness and availability;
- market context, including ticks/bars and their provenance;
- calendar/news context;
- portfolio context;
- execution context, including reservations and active intents.

These contexts are not one universal datastore. Account values must distinguish
`SUPPORTED`, `UNAVAILABLE`, `STALE` and `UNKNOWN`; a zero default is not a safe
substitute. News is a calendar context, not a market tick. Historical/replay
consumers must not observe facts before their publication time.

For market data, prefer an immutable provider-owned snapshot held through a
shared lifetime when asynchronous modules need to retain it. A borrowed view is
valid only while that snapshot is alive. Do not expose a raw `span` whose owner
can disappear after a callback. Existing `TickSequence` and routed batches
remain the source of truth until a dedicated snapshot contract is justified.

## Module Model

The first design uses small capability interfaces rather than one mandatory
`ITradingModule` with every possible method:

- signal/event consumers;
- stateless filters;
- position sizing policies;
- series progression policies;
- risk admission;
- execution adapters;
- settlement/recovery participants.

Modules normally run on the existing owner loop and task system. A separate
worker is reserved for genuinely long or latency-sensitive computation. The
final risk admission and reservation still happen through the common owner of
account/risk state.

## ADELIA and MegaConnector Adapter Boundary

The host may map a graph node to one or more small OptionX capabilities and map
typed graph edges to `TradeIntent` or lifecycle events. The host owns graph
topology, configuration, UI and node scheduling. OptionX owns trading-domain
contracts and broker adapters. No OptionX type should mention a graph node,
graph editor, ADELIA, ImNodeFlow or MegaConnector.

The adapter must preserve identities, cardinality, cancellation semantics and
unknown execution state. It must not convert a rejected intent into a loss or
turn a missing broker response into a cancellation.
188 changes: 188 additions & 0 deletions guides/TRADING_SYSTEM_CONTRACTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Trading System Contracts

## Status

These are proposed C++17 contracts for review. They are intentionally small
and header-only in spirit. Names and field types may change in the first
implementation PR; the invariants are the important part.

## Stable IDs and States

```cpp
using IntentId = std::uint64_t;
using SeriesId = std::uint64_t;
using ExecutionGroupId = std::uint64_t;
using SettlementId = std::uint64_t;

enum class IntentStatus {
PROPOSED,
REJECTED,
ADMITTED,
SUBMITTING,
EXECUTION_UNKNOWN,
EXECUTED,
SETTLED,
CANCELLED
};

enum class ExecutionCardinality {
ZERO_OR_ONE,
EXACTLY_ONE,
MULTIPLE,
SPLIT_GROUP
};
```

State transitions must be validated against the current revision and an
idempotency key. A repeated terminal event is a no-op. An out-of-date revision
cannot silently overwrite a newer series state.

## Signal, Intent and Execution

```cpp
struct TradeIntent {
IntentId intent_id = 0;
SignalId origin_signal_id = 0;
SeriesId series_id = 0;
std::int32_t series_step = -1;
ExecutionGroupId execution_group_id = 0;
ExecutionCardinality cardinality = ExecutionCardinality::ZERO_OR_ONE;
IntentStatus status = IntentStatus::PROPOSED;
TradeRequest request;
std::uint64_t state_revision = 0;
};

struct TradeOutcome {
IntentId intent_id = 0;
ExecutionGroupId execution_group_id = 0;
SettlementId settlement_id = 0;
TradeState state = TradeState::UNKNOWN;
double net_profit = 0.0;
bool final = false;
};
```

This is a contract sketch, not a request to add these exact structs before the
first vertical slice is tested. `TradeSignal` remains immutable. A physical
`TradeRequest` still flows through the existing platform API and keeps the
existing persistent `trade_id`.

## Decision Context

```cpp
enum class DataAvailability { SUPPORTED, UNAVAILABLE, STALE, UNKNOWN };

struct AccountContext {
DataAvailability availability = DataAvailability::UNKNOWN;
std::uint64_t captured_at_ms = 0;
std::uint64_t revision = 0;
// Immutable account/balance/payout values.
};

struct TradingDecisionContext {
const TradeSignal* signal = nullptr;
const AccountContext* account = nullptr;
const MarketSnapshot* market = nullptr;
const CalendarSnapshot* calendar = nullptr;
const PortfolioSnapshot* portfolio = nullptr;
const ExecutionSnapshot* execution = nullptr;
};
```

The final implementation may replace raw pointers with owned immutable
snapshot handles. A pointer in this sketch is never valid beyond the lifetime
of the context call. Asynchronous modules must retain a shared snapshot, not a
borrowed pointer or a `span` over mutable storage.

## Small Module Interfaces

The intended capabilities are separate:

```cpp
struct IntentDecision {
bool accepted = false;
std::string reason;
std::optional<TradeIntent> intent;
};

class IIntentFilter {
public:
virtual ~IIntentFilter() = default;
virtual IntentDecision evaluate(const TradingDecisionContext&) = 0;
};

class IPositionSizer {
public:
virtual ~IPositionSizer() = default;
virtual std::optional<double> size(const TradingDecisionContext&) = 0;
};

class ISeriesProgression {
public:
virtual ~ISeriesProgression() = default;
virtual void apply_outcome(const TradeOutcome&, std::uint64_t expected_revision) = 0;
};
```

Active/event-driven modules may consume ticks, bars, calendar events or
settlement events and emit intents. They do not need a private thread by
default. The existing task manager and lifecycle stack remain the execution
mechanism.

## Risk Admission

Risk admission is the final gate immediately before submission:

```cpp
struct RiskReservation {
std::uint64_t reservation_id = 0;
bool valid = false;
};

class IRiskAdmission {
public:
virtual ~IRiskAdmission() = default;
virtual std::optional<RiskReservation> reserve(
const TradeIntent&, const TradingDecisionContext&) = 0;
virtual bool commit(const RiskReservation&, const TradeRequest&) = 0;
virtual void release(const RiskReservation&) = 0;
virtual void mark_unknown(const RiskReservation&) = 0;
};
```

Two strategies must not both reserve against the same stale balance snapshot.
`mark_unknown` retains the reservation until reconciliation; it is not an
alias for `release`.

## Execution Groups

For `SPLIT_GROUP`, all physical members share an `execution_group_id`. The
group is final only when every required member is settled or the group policy
explicitly records a terminal failure. Aggregate outcome is defined by policy,
normally total confirmed net P&L. One unknown member keeps the group pending.

## Persistence and Recovery

The persistence adapter must support:

- create/update intent with monotonic revision;
- create/update series state;
- create execution group and physical trade links;
- append or idempotently apply settlement identity;
- find unresolved `SUBMITTING`/`EXECUTION_UNKNOWN` operations;
- reconcile broker identity before retry;
- restore state before accepting new series progression.

`SignalRecordDB` and `TradeRecordDB` remain adapters and sources of existing
signal/trade facts. They should not be silently treated as a complete journal.
The first implementation can use a dedicated table or a carefully scoped
extension, but it must not hide logical intent state in free-form comments.

## Cancellation Rules

- Before submission: transition to `CANCELLED` or `REJECTED`, release the
reservation, and emit one terminal event.
- During submission: record the operation as `SUBMITTING`; cancellation cannot
claim that the broker did not receive it.
- After transport ambiguity: transition to `EXECUTION_UNKNOWN`, retain risk,
reconcile by broker identity/history, and only then settle or cancel.
Loading
Loading