Sprint 2 (accounting + observability parity, PINK scope):
- Verified pink_clickhouse.py writes the 8 BLUE-legacy row families at
matching schema and that capital authority in pink_direct.step() is
solely kernel.account (no balance-poll overwrite in the hot loop).
- Report: prod/clean_arch/dita_v2/SPRINT2_ACCOUNTING_PARITY.md.
Sprint 3 offline groundwork (no exchange contact):
- Add _write_trade_exit_leg to pink_clickhouse.py: one BLUE-schema-faithful
trade_exit_legs row per exit leg, with isolated (non-cumulative) per-leg
deltas tracked via _leg_state (reset on ENTER). Closes the docstring gap.
- New offline suite test_pink_multi_exit_groundwork.py (3 passed):
* Flaw 4 — two-leg exit closes once, realized accrues per leg, closed
slot rejects further EXIT (no double-close).
* Overshoot invariant — a final EXIT requesting more than the remaining
size CLAMPS (size to 0, no oversell), retiring the Sprint 0 cumulative-
ratio risk empirically.
* trade_exit_legs delta + full BLUE column-set assertions.
- Persistence regression after edits: 10 passed.
BLUE untouched: no changes to dolphin.* / DOLPHIN_*_BLUE / nautilus_event_trader.py.
Live VST multi-leg run remains deferred pending explicit authorization.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
15 KiB
PINK BingX Simplification Spec
Status: Draft for implementation review Date: 2026-05-22 Owner: Runtime / Trading Systems Scope: PINK only, with BLUE parity preserved for algorithm comparison
1. Purpose
This spec defines a simplified live-trading architecture for PINK that:
- Preserves the BLUE algorithm exactly.
- Makes every engine action observable.
- Uses the exchange as the authoritative source of live position truth.
- Reuses the existing data structures needed for BLUE/PINK comparison.
- Reduces hidden state and duplicate decision paths.
- Keeps PINK mechanically comparable to BLUE wherever the exchange model allows it.
This document does not change the signal math, thresholds, or TP/exit logic. It only simplifies how those decisions move through the system and how they are recorded. Where BingX semantics differ from BLUE's historical execution surface, the difference must be isolated behind the execution boundary rather than pushed into the engine.
2. Design Goals
The architecture must satisfy all of the following:
- Faithfulness to BLUE's original algorithm.
- Full observability of actions as:
- fired
- requested
- sent
- acknowledged
- executed
- reflected on BingX
- Minimal complexity.
- Maximum reuse of existing tables, maps, and record shapes.
- Clean comparability between BLUE and PINK.
- No second domain-level truth source.
3. Non-Goals
This spec does not:
- Change the trading signal formula.
- Change the TP value or exit semantics.
- Add a second live source of truth.
- Replace supervisor with a new process manager.
- Introduce a new order ledger when existing tables can be reused.
4. Core Principle
PINK must be exchange-led.
That means:
- BingX position state is authoritative for whether the slot is open.
- BingX open-order state is authoritative for whether an exit is pending.
- Account state is a projection of confirmed exchange events.
- Local engine state is a projection of exchange state plus decision metadata.
- ClickHouse is the durable audit trail.
- Hazelcast is the live control/state bus.
- The TUI is a derived view only.
If local state and BingX state disagree, the system must reconcile toward BingX.
BLUE comparability rule:
- The engine-side lifecycle, state names, and record shapes should remain BLUE-compatible unless BingX makes that impossible.
- Any unavoidable exchange-specific deviation must be isolated in the execution adapter and event normalization path.
- The engine itself should remain oblivious to BingX quirks except for the minimal authority rules needed to stay safe.
5. Minimal State Model
The system should keep only these live state categories:
- Decision state
- what the engine decided
- Order state
- what was requested and acknowledged
- Position state
- what BingX currently holds
- Account state
- capital, leverage, open notional
- Terminal trade state
- completed trades only
Everything else should be derived from those categories.
The simplification target is not "remove layers entirely". It is "make the layers explicit and narrow":
engine intent
-> execution facade
-> exchange adapter
-> exchange
-> event normalization
-> durable ledger
The execution facade is where BLUE-compatible semantics are preserved.
The exchange adapter is where BingX-specific request/response shapes live.
event normalization is a thin technical return channel inside the execution boundary:
- dedupe exchange callbacks
- normalize terminal states
- map exchange facts into canonical trade/account events
- update projections and durable rows
It is not a separate policy or trading layer.
This spec uses the following DITA split:
Decision- pure signal evaluation
Intent- candidate selection and sizing proposal
Trade- single-slot lifecycle state machine
Account- projection of confirmed execution facts
6. Existing Data Structures to Reuse
This spec reuses the current structures instead of introducing parallel ones.
6.1 ClickHouse tables
dolphin_pink.position_state- lifecycle source for open and closed trade status
dolphin_pink.trade_events- terminal ledger for completed trades
dolphin_pink.account_events- capital and exposure snapshots
dolphin_pink.v7_decision_events- decision trail
dolphin_pink.adaptive_exit_shadow- shadow-only exit analysis
6.2 Hazelcast maps
DOLPHIN_STATE_PINKDOLPHIN_PNL_PINKDOLPHIN_FEATURESDOLPHIN_SAFETYDOLPHIN_HEARTBEAT
6.3 Exchange-side sources
user/positionstrade/openOrderstrade/allOrderstrade/allFillOrders
7. Authoritative Precedence
Live truth must be resolved in this order:
BingX user/positions
↓
BingX trade/openOrders
↓
BingX journal snapshot
↓
ClickHouse account_events / position_state
↓
Hazelcast engine snapshot
↓
Supervisor log fallback
Rules:
- The first matching live BingX signal wins.
- Local snapshots may lag and must not override BingX.
- Log parsing is a last resort only.
For BLUE comparability:
- The adapter must emit the same semantic milestones BLUE would expose, even if the physical exchange response is different.
- If BingX cannot express a BLUE milestone exactly, preserve the closest semantic equivalent and annotate the deviation in the event payload.
8. High-Level Data Flow
+------------------+
| Binance data |
| / HZ features |
+---------+--------+
|
v
+------------------+
| DolphinActor |
| (BLUE logic) |
+---------+--------+
|
v
+------------------+
| NDAlphaEngine |
| single slot only |
+---------+--------+
|
decision / request
|
v
+------------------+
| Execution facade |
| BLUE-compatible |
+---------+--------+
|
exchange-specific request
|
v
+------------------+
| BingXExecClient |
+---------+--------+
|
v
+------------------+
| BingX VST |
| positions/orders |
+---------+--------+
|
poll / ack / fill / close
|
v
+------------------+
| journal snapshot |
+---------+--------+
|
v
+----------------+----------------+
| ClickHouse + Hazelcast + TUI |
+---------------------------------+
9. Order Lifecycle
The system should treat every trade as a simple state machine.
EMPTY
|
v
DECISION_CREATED
|
v
ORDER_REQUESTED
|
v
ORDER_SENT
|
v
ORDER_ACKNOWLEDGED
|
v
POSITION_OPENED
|
v
POSITION_UPDATED
|
v
EXIT_REQUESTED
|
v
EXIT_SENT
|
v
EXIT_ACKNOWLEDGED
|
v
POSITION_CLOSED
|
v
TRADE_TERMINAL_WRITTEN
|
v
EMPTY
Rules:
- A trade is not "closed" until BingX no longer reports the position.
- A terminal close row is not optional.
- The close row must be written after exchange-event normalization confirms terminality, not before.
10. Open / Update / Close Mechanics
10.1 Open
- Engine produces a decision.
- Actor converts it into an intent.
- Execution facade normalizes the request into a BLUE-compatible action record.
- Execution client submits the request to BingX.
- BingX acknowledges or rejects.
- BingX position becomes authoritative once open.
- Event normalization updates
position_stateand account projections.
10.2 Update
- Execution client polls
openOrders. - Execution facade records the requested action.
- Execution client polls
user/positions. - Execution client refreshes account state.
- Journal snapshot is persisted.
- ClickHouse rows are appended.
- Hazelcast state is refreshed.
- TUI renders the derived result.
10.3 Close
- Engine or exit manager requests exit.
- Execution facade normalizes the exit into the same lifecycle that BLUE would represent.
- Exit order is submitted reduce-only.
- BingX confirms fill or terminal state.
- Exchange position disappears.
- Event normalization emits the terminal close fact.
trade_eventsclose row is written.position_stateis updated to closed.
11. Reconciliation Model
In this spec, "reconciliation" is not a first-class domain layer. It is the thin adapter-side return path that converts BingX facts into canonical events and projections.
The simplified model is:
engine intent
-> exchange submission
-> exchange state
-> event normalization
-> durable ledger
Not:
engine intent
-> local inferred close
-> maybe exchange close later
The second pattern is what creates ghost closes and confusing TUI state.
The return path must remain thin and mostly transparent:
- confirm what BingX actually did
- translate exchange reality into canonical engine state and durable ledger rows
- backfill only the minimum terminal bookkeeping needed to keep the audit trail complete
It must not:
- make trading decisions
- invent or reinterpret strategy state
- act as a second policy layer
- override engine intent except where required to reflect BingX authority
In other words:
policy lives in the engine
translation lives in the execution boundary
truth lives on BingX
If the return path starts shaping strategy behavior, the architecture has drifted.
12. ClickHouse Accounting Contract
12.1 account_events
This table must represent the latest authoritative snapshot of:
- capital
- open positions
- open notional
- leverage
- fills metadata
It is not the source of truth for execution. It is the projection of confirmed execution facts and the best table for capital-path replay.
12.2 position_state
This table must represent per-trade lifecycle state.
Required lifecycle states:
OPENEXIT_REQUESTEDEXIT_ACKEDCLOSEDRECONCILED
This table is the canonical lifecycle projection, not a second engine.
12.3 trade_events
This table must represent terminal closed trades only.
Rules:
- one terminal row per completed trade
- dedupe by
trade_id - never infer a close row from a fill snapshot alone
12.4 status_snapshots
When capital replay is needed, status_snapshots remains the preferred capital-path source because it captures:
- capital
- posture
trades_executedrmvol_ok- related snapshot state
trade_events alone is not enough for capital replay.
13. PINK and BLUE Comparison Rules
PINK must remain structurally comparable to BLUE.
That means:
- same trade identity model
- same key fields for open/close events
- same exit reason vocabulary
- same capital accounting semantics
- same bar and hold semantics
Namespace differences are allowed. Semantic differences are not.
The DITA split must stay semantically compatible with BLUE:
- decision semantics preserved
- intent selection preserved
- trade lifecycle compatible
- account projection comparable
- return-channel normalization exchange-specific only
14. Simplification Rules
To reduce bugs, do the following:
14.1 Keep one authoritative open-slot view
Do not maintain competing local definitions of "open trade".
14.2 Stop inventing closed trades in the TUI
The TUI may display:
- open positions
- terminal trades
- fills
It must not convert fills into fake closes.
14.3 Remove recovery ambiguity
At startup:
- BingX positions are imported
- stale local slots are cleared
- journal state is restored only when it does not contradict BingX
- account projection is rebuilt from confirmed exchange facts, not from intent history
14.4 Keep the event trail append-only
If a state needs correction, emit a new event. Do not rewrite history.
15. ASCII Failure Modes
15.1 Ghost close
EXIT_REQUESTED
|
v
EXIT_SENT
|
+--> local snapshot says CLOSED
|
+--> BingX still shows position OPEN
|
v
BUG: local UI looks flat, exchange is not flat
15.2 Missing terminal row
EXIT_ACKNOWLEDGED
|
v
POSITION_CLOSED on BingX
|
v
trade_events row missing
|
v
BUG: replay/debug cannot prove the close
15.3 Duplicate ledger row
trade_events insert
|
+--> duplicate insert for same trade_id
|
v
BUG: replay capital is overstated unless deduped
16. Acceptance Criteria
The simplification is acceptable only if all of the following hold:
- BLUE algorithm behavior is preserved exactly.
- PINK trades can be compared to BLUE trades using the same structures.
- Every order action is visible in the trail.
- Every close can be traced to BingX terminal state.
- TUI never invents a close.
- Capital replay can be reconstructed from
status_snapshotsplus deduped trade rows. - BingX remains the authoritative open-position source.
17. Implementation Boundaries
The following are the expected boundaries for any implementation work:
- Launcher layer
- namespace wiring only
- Actor layer
- engine-slot projection and adapter ingress
- Execution facade layer
- BLUE-compatible action normalization
- order lifecycle event emission
- BingX execution layer
- order submit / poll / reconcile / snapshot
- Journal layer
- durable bridge into ClickHouse
- Observability layer
- derived display only
The return path should be treated as a translation boundary, not a policy boundary. Its ideal steady state is nearly invisible.
Any new BingX-specific behavior should go in the execution or adapter-ingress path, not in the engine decision logic.
18. Recommended Simplified Architecture
[decision]
|
v
[intent]
|
v
[trade FSM]
|
v
[execution adapter]
|
v
[BingX order/position]
|
v
[event normalization]
|
v
[ClickHouse account + trade ledger]
|
v
[TUI / replay]
This is the simplest version that still preserves BLUE faithfulness and auditability.
19. Open Questions
These are implementation questions, not design blockers:
- Should PINK
trade_eventsremain fully separate from BLUE-compatible schema, or only namespace-tagged? - Should the TUI use
account_eventsorposition_stateas the primary open-trade panel source? - Should
position_statebecome the canonical lifecycle table for all live strategies, or only PINK first? - Should any exchange callback normalization be shared with BLUE, or remain PINK-only until parity is proven?
20. Final Decision
The target simplification is:
- one engine
- one exchange authority
- one append-only audit trail
- one derived TUI
- one replay path
Anything that introduces a second truth source should be removed or demoted.
Reconciliation, if the term is retained at all, should mean only the thin adapter-side normalization of BingX facts into canonical events and account projection. It should not exist as a policy layer.