All checks were successful
deploy / deploy (push) Successful in 48s
Instead of fetching up to 50k inventory snapshots (~442MB), scope the query to only the time range of the current submission batch with a 15-minute buffer. For a typical 1-hour batch this drops from 50k rows to ~300 rows, well within the 1280Mi pod memory limit. The coalesced_at_idx on intent_inventory_snapshots covers the BETWEEN clause so this remains efficient. Proof: history-writer OOM kills from refreshQuoteOutcomes inventory fetch Assumptions: 15min buffer covers the attribution window for all submissions Still fake: heuristic gap outcomes may attribute trades imprecisely
982 lines
23 KiB
Markdown
982 lines
23 KiB
Markdown
# Current System Architecture
|
|
|
|
This document explains the current `unrip` runtime at a high level:
|
|
|
|
- which components exist
|
|
- what responsibilities each component owns
|
|
- which signals move through Kafka/Redpanda
|
|
- what gets persisted
|
|
- which attributes are available in the stored data
|
|
- what kinds of analysis and operator workflows the data supports today
|
|
|
|
The system is intentionally bus-first and data-first:
|
|
|
|
- components are separate services
|
|
- the core trading flow moves through Kafka-compatible topics
|
|
- PostgreSQL is the durable analytical and operator history store
|
|
- some safety-critical operational state is also persisted in local/PVC-backed JSON files
|
|
- the browser reads only the operator dashboard backend, not Kafka or PostgreSQL directly
|
|
|
|
## Design Rules
|
|
|
|
The current architecture follows these rules:
|
|
|
|
- venue inputs, strategy, execution, alerting, persistence, and the dashboard are separate services
|
|
- the core trading path is event-driven, not direct service-to-service RPC
|
|
- raw and normalized events are both preserved
|
|
- decisions and execution attempts are auditable after the fact
|
|
- execution safety depends on persisted idempotency state, not Kafka consumer groups alone
|
|
|
|
## System At A Glance
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
subgraph External Sources
|
|
NearWS["NEAR Intents solver relay websocket"]
|
|
RefData["Kraken + CoinGecko"]
|
|
Bridge["Bridge RPC"]
|
|
Verifier["Verifier / NEAR RPC"]
|
|
BtcObs["BTC address observer"]
|
|
end
|
|
|
|
subgraph Runtime Services
|
|
Ingest["near-intents-ingest"]
|
|
RefIngest["market-reference-ingest"]
|
|
Liquidity["liquidity-manager"]
|
|
Inventory["inventory-sync"]
|
|
Strategy["strategy-engine"]
|
|
Executor["trade-executor"]
|
|
Sentinel["ops-sentinel"]
|
|
History["history-writer"]
|
|
Dashboard["operator-dashboard"]
|
|
end
|
|
|
|
Bus[("Redpanda / Kafka")]
|
|
Postgres[("PostgreSQL")]
|
|
Browser["Operator browser"]
|
|
|
|
NearWS --> Ingest
|
|
RefData --> RefIngest
|
|
Bridge --> Liquidity
|
|
Verifier --> Liquidity
|
|
BtcObs --> Liquidity
|
|
Verifier --> Inventory
|
|
Bridge --> Inventory
|
|
|
|
Ingest -->|"raw.near_intents.quote"| Bus
|
|
Ingest -->|"norm.swap_demand"| Bus
|
|
RefIngest -->|"ref.market_price"| Bus
|
|
Liquidity -->|"ops.liquidity_action"| Bus
|
|
Liquidity -->|"ops.funding_observation"| Bus
|
|
Inventory -->|"state.intent_inventory"| Bus
|
|
Strategy -->|"decision.trade_decision"| Bus
|
|
Strategy -->|"cmd.execute_trade"| Bus
|
|
Executor -->|"exec.trade_result"| Bus
|
|
Sentinel -->|"ops.alert"| Bus
|
|
|
|
Bus --> Inventory
|
|
Bus --> Strategy
|
|
Bus --> Executor
|
|
Bus --> Sentinel
|
|
Bus --> History
|
|
Bus --> Dashboard
|
|
|
|
History --> Postgres
|
|
Postgres --> Dashboard
|
|
Dashboard --> Browser
|
|
Dashboard -. "/state + /healthz" .-> Ingest
|
|
Dashboard -. "/state + /healthz" .-> RefIngest
|
|
Dashboard -. "/state + /healthz" .-> Liquidity
|
|
Dashboard -. "/state + /healthz" .-> Inventory
|
|
Dashboard -. "/state + /healthz" .-> History
|
|
Dashboard -. "/state + /healthz" .-> Sentinel
|
|
Dashboard -. "/state + /healthz" .-> Strategy
|
|
Dashboard -. "/state + /healthz" .-> Executor
|
|
```
|
|
|
|
## Components And Responsibilities
|
|
|
|
### `near-intents-ingest`
|
|
|
|
Responsibility:
|
|
|
|
- connect to the NEAR Intents websocket
|
|
- subscribe to quote traffic
|
|
- preserve the raw upstream payload
|
|
- normalize quote requests into the canonical `swap_demand` shape
|
|
- apply pair filtering before publishing
|
|
|
|
Produces:
|
|
|
|
- `raw.near_intents.quote`
|
|
- `norm.swap_demand`
|
|
|
|
### `market-reference-ingest`
|
|
|
|
Responsibility:
|
|
|
|
- fetch BTC/EUR reference prices
|
|
- prefer Kraken and fall back to CoinGecko
|
|
- publish the reference price used for valuation and strategy checks
|
|
|
|
Produces:
|
|
|
|
- `ref.market_price`
|
|
|
|
### `liquidity-manager`
|
|
|
|
Responsibility:
|
|
|
|
- track bridge deposit handles
|
|
- observe deposits and withdrawals
|
|
- observe pre-credit funding activity
|
|
- estimate and optionally submit withdrawals
|
|
- publish treasury and funding operations signals
|
|
|
|
Produces:
|
|
|
|
- `ops.liquidity_action`
|
|
- `ops.funding_observation`
|
|
|
|
### `inventory-sync`
|
|
|
|
Responsibility:
|
|
|
|
- combine verifier balances with deposit and withdrawal side inputs
|
|
- publish the current inventory snapshot
|
|
- separate spendable balances from pending inbound and pending outbound funds
|
|
|
|
Consumes:
|
|
|
|
- `ops.liquidity_action`
|
|
- `ops.funding_observation`
|
|
|
|
Produces:
|
|
|
|
- `state.intent_inventory`
|
|
|
|
### `strategy-engine`
|
|
|
|
Responsibility:
|
|
|
|
- consume normalized demand
|
|
- read the latest reference price and inventory snapshot
|
|
- evaluate whether a quote is actionable
|
|
- record why it skipped or accepted a quote
|
|
- emit an execution command when the strategy is armed and the quote passes checks
|
|
|
|
Consumes:
|
|
|
|
- `norm.swap_demand`
|
|
- `ref.market_price`
|
|
- `state.intent_inventory`
|
|
|
|
Produces:
|
|
|
|
- `decision.trade_decision`
|
|
- `cmd.execute_trade`
|
|
|
|
### `trade-executor`
|
|
|
|
Responsibility:
|
|
|
|
- consume execution commands
|
|
- enforce arming state
|
|
- enforce command idempotency
|
|
- submit a quote response back to the venue
|
|
- publish the execution result
|
|
|
|
Consumes:
|
|
|
|
- `cmd.execute_trade`
|
|
|
|
Produces:
|
|
|
|
- `exec.trade_result`
|
|
|
|
### `ops-sentinel`
|
|
|
|
Responsibility:
|
|
|
|
- watch price freshness, inventory freshness, funding progression, liquidity events, and execution outcomes
|
|
- turn those signals into operator alert transitions
|
|
|
|
Consumes:
|
|
|
|
- `ref.market_price`
|
|
- `state.intent_inventory`
|
|
- `ops.liquidity_action`
|
|
- `ops.funding_observation`
|
|
- `exec.trade_result`
|
|
|
|
Produces:
|
|
|
|
- `ops.alert`
|
|
|
|
### `history-writer`
|
|
|
|
Responsibility:
|
|
|
|
- consume the major event topics
|
|
- route each topic into a PostgreSQL history table
|
|
- compute portfolio metrics from the durable event history
|
|
|
|
Consumes:
|
|
|
|
- `raw.near_intents.quote`
|
|
- `norm.swap_demand`
|
|
- `ref.market_price`
|
|
- `state.intent_inventory`
|
|
- `ops.liquidity_action`
|
|
- `ops.funding_observation`
|
|
- `ops.alert`
|
|
- `decision.trade_decision`
|
|
- `cmd.execute_trade`
|
|
- `exec.trade_result`
|
|
|
|
Produces:
|
|
|
|
- rows in PostgreSQL event tables
|
|
- rows in `portfolio_metrics_snapshots`
|
|
|
|
### `operator-dashboard`
|
|
|
|
Responsibility:
|
|
|
|
- expose one browser-facing API and WebSocket endpoint
|
|
- read durable history and metrics from PostgreSQL
|
|
- read live service state from `/state` and `/healthz`
|
|
- consume a small live Kafka topic set to avoid polling in the browser
|
|
- fan live updates to the frontend
|
|
|
|
Consumes:
|
|
|
|
- PostgreSQL
|
|
- service control APIs
|
|
- `norm.swap_demand`
|
|
- `ref.market_price`
|
|
- `state.intent_inventory`
|
|
- `ops.alert`
|
|
- `exec.trade_result`
|
|
|
|
Produces:
|
|
|
|
- `/api/session`
|
|
- `/api/bootstrap`
|
|
- `/api/trades`
|
|
- `/api/control/...`
|
|
- `/ws`
|
|
|
|
## Topic Classes And Signals
|
|
|
|
The topic naming scheme is meaningful:
|
|
|
|
- `raw.*` means source-native truth
|
|
- `norm.*` means canonical normalized inputs
|
|
- `ref.*` means reference market data
|
|
- `state.*` means derived state snapshots
|
|
- `ops.*` means operational or treasury state transitions
|
|
- `decision.*` means strategy output explaining what the system thought
|
|
- `cmd.*` means requested execution work
|
|
- `exec.*` means execution outcome
|
|
|
|
| Topic | Producer | Primary meaning | Main consumers |
|
|
| --- | --- | --- | --- |
|
|
| `raw.near_intents.quote` | `near-intents-ingest` | Raw venue quote frame | `history-writer` |
|
|
| `norm.swap_demand` | `near-intents-ingest` | Canonical demand signal from quote requests | `strategy-engine`, `history-writer`, `operator-dashboard` |
|
|
| `ref.market_price` | `market-reference-ingest` | Reference BTC/EUR pricing for valuation and strategy | `strategy-engine`, `ops-sentinel`, `history-writer`, `operator-dashboard` |
|
|
| `state.intent_inventory` | `inventory-sync` | Current spendable and pending inventory state | `strategy-engine`, `ops-sentinel`, `history-writer`, `operator-dashboard` |
|
|
| `ops.liquidity_action` | `liquidity-manager` | Deposit handle, deposit, withdrawal, and treasury actions | `inventory-sync`, `ops-sentinel`, `history-writer` |
|
|
| `ops.funding_observation` | `liquidity-manager` | Pre-credit inbound funding observations | `inventory-sync`, `ops-sentinel`, `history-writer` |
|
|
| `ops.alert` | `ops-sentinel` | Raised or cleared operator alerts | `history-writer`, `operator-dashboard` |
|
|
| `decision.trade_decision` | `strategy-engine` | Strategy explanation for skip or action | `history-writer` |
|
|
| `cmd.execute_trade` | `strategy-engine` | Instruction for the executor | `trade-executor`, `history-writer` |
|
|
| `exec.trade_result` | `trade-executor` | Outcome of the execution attempt | `ops-sentinel`, `history-writer`, `operator-dashboard` |
|
|
|
|
## Signal Flow
|
|
|
|
This is the high-level flow from upstream quote to operator visibility:
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A["Quote request arrives from NEAR Intents"] --> B["near-intents-ingest"]
|
|
B --> C["raw.near_intents.quote"]
|
|
B --> D["norm.swap_demand"]
|
|
|
|
E["Reference price refresh"] --> F["market-reference-ingest"]
|
|
F --> G["ref.market_price"]
|
|
|
|
H["Bridge / verifier / BTC observer state"] --> I["liquidity-manager"]
|
|
I --> J["ops.liquidity_action"]
|
|
I --> K["ops.funding_observation"]
|
|
J --> L["inventory-sync"]
|
|
K --> L
|
|
L --> M["state.intent_inventory"]
|
|
|
|
D --> N["strategy-engine"]
|
|
G --> N
|
|
M --> N
|
|
N --> O["decision.trade_decision"]
|
|
N --> P["cmd.execute_trade"]
|
|
|
|
P --> Q["trade-executor"]
|
|
Q --> R["exec.trade_result"]
|
|
|
|
G --> S["ops-sentinel"]
|
|
M --> S
|
|
J --> S
|
|
K --> S
|
|
R --> S
|
|
S --> T["ops.alert"]
|
|
|
|
C --> U["history-writer"]
|
|
D --> U
|
|
G --> U
|
|
M --> U
|
|
J --> U
|
|
K --> U
|
|
O --> U
|
|
P --> U
|
|
R --> U
|
|
T --> U
|
|
U --> V["PostgreSQL"]
|
|
|
|
V --> W["operator-dashboard bootstrap reads"]
|
|
D --> X["operator-dashboard live consumer"]
|
|
G --> X
|
|
M --> X
|
|
T --> X
|
|
R --> X
|
|
W --> Y["Browser REST bootstrap"]
|
|
X --> Z["Browser WebSocket live updates"]
|
|
```
|
|
|
|
## Sequence Diagrams
|
|
|
|
### Quote To Decision To Execution
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Venue as NEAR Intents WS
|
|
participant Ingest as near-intents-ingest
|
|
participant Bus as Redpanda/Kafka
|
|
participant Strategy as strategy-engine
|
|
participant Executor as trade-executor
|
|
participant Sentinel as ops-sentinel
|
|
participant History as history-writer
|
|
participant DB as PostgreSQL
|
|
|
|
Venue->>Ingest: quote event frame
|
|
Ingest->>Bus: raw.near_intents.quote
|
|
Ingest->>Bus: norm.swap_demand
|
|
|
|
Bus->>Strategy: norm.swap_demand
|
|
Bus->>History: raw + norm events
|
|
History->>DB: insert raw_near_intents_quotes / swap_demand_events
|
|
|
|
Note over Strategy: Strategy already tracks latest ref.market_price and state.intent_inventory
|
|
|
|
Strategy->>Bus: decision.trade_decision
|
|
alt actionable and armed
|
|
Strategy->>Bus: cmd.execute_trade
|
|
Bus->>Executor: cmd.execute_trade
|
|
Executor->>Venue: quote_response submission
|
|
Venue-->>Executor: ack / response
|
|
Executor->>Bus: exec.trade_result
|
|
else rejected
|
|
Note over Strategy: No command emitted
|
|
end
|
|
|
|
Bus->>Sentinel: exec.trade_result
|
|
Sentinel->>Bus: ops.alert (if alert state changes)
|
|
|
|
Bus->>History: decision / command / result / alert
|
|
History->>DB: insert trade_decisions / execute_trade_commands / trade_execution_results / ops_alerts
|
|
```
|
|
|
|
### Dashboard Bootstrap And Live Updates
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Browser
|
|
participant Dashboard as operator-dashboard
|
|
participant DB as PostgreSQL
|
|
participant Services as internal /state + /healthz
|
|
participant Bus as Redpanda/Kafka
|
|
|
|
Browser->>Dashboard: GET /api/bootstrap
|
|
Dashboard->>DB: load portfolio metric, price, inventory, quotes, trades, alerts
|
|
Dashboard->>Services: GET /state and /healthz across services
|
|
DB-->>Dashboard: durable history + metrics
|
|
Services-->>Dashboard: live service state
|
|
Dashboard-->>Browser: bootstrap JSON
|
|
|
|
Browser->>Dashboard: WebSocket /ws
|
|
Dashboard-->>Browser: session.ready + initial live state
|
|
|
|
Bus->>Dashboard: norm.swap_demand
|
|
Dashboard-->>Browser: quotes.recent
|
|
|
|
Bus->>Dashboard: ref.market_price / state.intent_inventory / ops.alert / exec.trade_result
|
|
Dashboard-->>Browser: status_bar.updated / alerts.updated
|
|
```
|
|
|
|
## Event Envelope
|
|
|
|
Every bus event shares the same outer shape:
|
|
|
|
```json
|
|
{
|
|
"event_id": "string",
|
|
"event_type": "string",
|
|
"venue": "string",
|
|
"source": "string|null",
|
|
"schema_version": 1,
|
|
"observed_at": "ISO-8601|null",
|
|
"ingested_at": "ISO-8601",
|
|
"payload": {},
|
|
"raw": {}
|
|
}
|
|
```
|
|
|
|
This matters because it gives every downstream consumer a stable minimum contract:
|
|
|
|
- unique event identity
|
|
- event type
|
|
- venue/source attribution
|
|
- two timestamps
|
|
- the domain payload
|
|
- optional raw payload preservation
|
|
|
|
## What Is Persisted
|
|
|
|
There are three different persistence layers in the current system.
|
|
|
|
### 1. Kafka / Redpanda Retention
|
|
|
|
Kafka is the operational event backbone:
|
|
|
|
- it retains the event stream for replay and decoupling
|
|
- it is not the main analytical store
|
|
- it lets multiple consumers read the same event flow independently
|
|
|
|
What this layer is good for:
|
|
|
|
- fan-out
|
|
- short-horizon replay
|
|
- decoupled service design
|
|
- live dashboard updates without browser polling
|
|
|
|
### 2. PostgreSQL Durable History
|
|
|
|
PostgreSQL is the main durable queryable history layer for operator views and analysis.
|
|
|
|
All event tables share the same durable columns:
|
|
|
|
| Column | Meaning |
|
|
| --- | --- |
|
|
| `event_id` | Unique event id from the envelope |
|
|
| `topic` | Kafka topic name |
|
|
| `venue` | Venue or source family |
|
|
| `source` | Producing service or adapter |
|
|
| `event_type` | Domain event type |
|
|
| `observed_at` | When the upstream thing happened, if known |
|
|
| `ingested_at` | When this service emitted the event |
|
|
| `quote_id` | Quote correlation key when available |
|
|
| `pair` | Pair string when available |
|
|
| `decision_key` | Topic-specific correlation key such as decision id or command id |
|
|
| `payload` | The structured event payload |
|
|
| `raw` | The raw upstream payload when preserved |
|
|
|
|
#### `raw_near_intents_quotes`
|
|
|
|
Source topic:
|
|
|
|
- `raw.near_intents.quote`
|
|
|
|
Key payload attributes:
|
|
|
|
- `message`: original upstream frame
|
|
|
|
Useful for:
|
|
|
|
- schema drift inspection
|
|
- replay or re-normalization
|
|
- debugging unexpected parser behavior
|
|
- checking what fields the venue actually sent
|
|
|
|
#### `swap_demand_events`
|
|
|
|
Source topic:
|
|
|
|
- `norm.swap_demand`
|
|
|
|
Key payload attributes:
|
|
|
|
- `quote_id`
|
|
- `asset_in`
|
|
- `asset_out`
|
|
- `pair`
|
|
- `request_kind`
|
|
- `amount_in`
|
|
- `amount_out`
|
|
- `min_deadline_ms`
|
|
|
|
Useful for:
|
|
|
|
- quote frequency by time
|
|
- pair mix and direction analysis
|
|
- size distributions
|
|
- exact-in vs exact-out behavior
|
|
- quote freshness analysis
|
|
|
|
#### `market_price_events`
|
|
|
|
Source topic:
|
|
|
|
- `ref.market_price`
|
|
|
|
Key payload attributes:
|
|
|
|
- `price_id`
|
|
- `pair`
|
|
- `eur_per_btc`
|
|
- `eure_per_btc`
|
|
- `btc_per_eur`
|
|
- `btc_per_eure`
|
|
- `source_used`
|
|
- `fallback_in_use`
|
|
- `divergence_pct`
|
|
- `eure_per_eur_assumption`
|
|
- `kraken.price`
|
|
- `kraken.observed_at`
|
|
- `kraken.age_ms`
|
|
- `kraken.healthy`
|
|
- `kraken.error`
|
|
- `coingecko.price`
|
|
- `coingecko.observed_at`
|
|
- `coingecko.age_ms`
|
|
- `coingecko.healthy`
|
|
- `coingecko.error`
|
|
|
|
Useful for:
|
|
|
|
- valuation at a point in time
|
|
- price freshness tracking
|
|
- fallback source usage
|
|
- disagreement between Kraken and CoinGecko
|
|
- linking strategy decisions to the exact price snapshot they saw
|
|
|
|
#### `intent_inventory_snapshots`
|
|
|
|
Source topic:
|
|
|
|
- `state.intent_inventory`
|
|
|
|
Key payload attributes:
|
|
|
|
- `inventory_id`
|
|
- `account_id`
|
|
- `synced_at`
|
|
- `spendable`
|
|
- `pending_inbound`
|
|
- `pending_outbound`
|
|
- `deposits`
|
|
- `withdrawals`
|
|
- `reconciliation_status`
|
|
|
|
Deposit record attributes:
|
|
|
|
- `tx_hash`
|
|
- `chain`
|
|
- `asset_id`
|
|
- `amount`
|
|
- `address`
|
|
- `status`
|
|
- `decimals`
|
|
|
|
Withdrawal record attributes:
|
|
|
|
- `withdrawal_hash`
|
|
- `asset_id`
|
|
- `chain`
|
|
- `amount`
|
|
- `status`
|
|
- `address`
|
|
|
|
Useful for:
|
|
|
|
- spendable balance history
|
|
- pending transfer tracking
|
|
- treasury state over time
|
|
- deposit and withdrawal visibility
|
|
- linking a decision to the inventory snapshot it used
|
|
|
|
#### `liquidity_actions`
|
|
|
|
Source topic:
|
|
|
|
- `ops.liquidity_action`
|
|
|
|
Key payload attributes:
|
|
|
|
- `liquidity_action_id`
|
|
- `account_id`
|
|
- `action_type`
|
|
- `status`
|
|
- `chain`
|
|
- `asset_id`
|
|
- `details`
|
|
|
|
The `details` object varies by action type. Common examples include:
|
|
|
|
- refreshed deposit addresses
|
|
- observed deposit records
|
|
- tracked withdrawal objects
|
|
- changed withdrawal status objects
|
|
- submitted withdrawal details
|
|
|
|
Useful for:
|
|
|
|
- treasury workflow timelines
|
|
- deposit handle rotation history
|
|
- withdrawal lifecycle tracking
|
|
- identifying when operator actions changed state
|
|
|
|
#### `funding_observations`
|
|
|
|
Source topic:
|
|
|
|
- `ops.funding_observation`
|
|
|
|
Key payload attributes:
|
|
|
|
- `funding_observation_id`
|
|
- `account_id`
|
|
- `asset_id`
|
|
- `chain`
|
|
- `funding_handle`
|
|
- `source`
|
|
- `tx_hash`
|
|
- `status`
|
|
- `amount`
|
|
- `confirmations`
|
|
- `first_seen_at`
|
|
- `last_seen_at`
|
|
- `credited_at`
|
|
- `bridge_deposit_tx_hash`
|
|
- `bridge_status`
|
|
- `credit_correlation`
|
|
|
|
Useful for:
|
|
|
|
- pre-credit deposit detection
|
|
- funding aging and stuck funding analysis
|
|
- credit lag measurement
|
|
- correlation between observed chain transactions and bridge crediting
|
|
|
|
#### `ops_alerts`
|
|
|
|
Source topic:
|
|
|
|
- `ops.alert`
|
|
|
|
Key payload attributes:
|
|
|
|
- `alert_event_id`
|
|
- `alert_code`
|
|
- `status`
|
|
- `severity`
|
|
- `reason`
|
|
- `service_scope`
|
|
- `pair`
|
|
- `asset_id`
|
|
- `raised_at`
|
|
- `cleared_at`
|
|
- `details`
|
|
|
|
Useful for:
|
|
|
|
- alert timelines
|
|
- mean time to recovery
|
|
- stale data and treasury issue analysis
|
|
- operator incident review
|
|
|
|
#### `trade_decisions`
|
|
|
|
Source topic:
|
|
|
|
- `decision.trade_decision`
|
|
|
|
Key payload attributes:
|
|
|
|
- `decision_id`
|
|
- `quote_id`
|
|
- `pair`
|
|
- `direction`
|
|
- `request_kind`
|
|
- `decision`
|
|
- `decision_reason`
|
|
- `threshold_pct`
|
|
- `max_notional_eure`
|
|
- `strategy_armed`
|
|
- `assumptions.eure_per_eur`
|
|
- `price_freshness_ms`
|
|
- `inventory_freshness_ms`
|
|
- `reference_rate`
|
|
- `implied_rate`
|
|
- `gross_edge_pct`
|
|
- `inventory_asset`
|
|
- `inventory_required`
|
|
- `inventory_available`
|
|
- `inventory_id`
|
|
- `price_id`
|
|
- `eure_notional`
|
|
- `proposed_amount_in`
|
|
- `proposed_amount_out`
|
|
- `pending_inbound`
|
|
|
|
Useful for:
|
|
|
|
- understanding why quotes were skipped
|
|
- measuring how many opportunities reached the actionable state
|
|
- analyzing insufficient inventory vs stale data vs threshold rejects
|
|
- reconstructing the exact decision context used by the strategy
|
|
|
|
#### `execute_trade_commands`
|
|
|
|
Source topic:
|
|
|
|
- `cmd.execute_trade`
|
|
|
|
Key payload attributes:
|
|
|
|
- `command_id`
|
|
- `decision_id`
|
|
- `idempotency_key`
|
|
- `execution_key`
|
|
- `quote_id`
|
|
- `pair`
|
|
- `asset_in`
|
|
- `asset_out`
|
|
- `amount_in`
|
|
- `amount_out`
|
|
- `request_kind`
|
|
- `min_deadline_ms`
|
|
- `quote_output.amount_in`
|
|
- `quote_output.amount_out`
|
|
- `proposed_amount_in`
|
|
- `proposed_amount_out`
|
|
|
|
Useful for:
|
|
|
|
- seeing exactly what execution was asked to do
|
|
- duplicate detection analysis
|
|
- comparing command intent to result outcome
|
|
- joining strategy decisions to actual execution attempts
|
|
|
|
#### `trade_execution_results`
|
|
|
|
Source topic:
|
|
|
|
- `exec.trade_result`
|
|
|
|
Key payload attributes:
|
|
|
|
- `command_id`
|
|
- `decision_id`
|
|
- `idempotency_key`
|
|
- `execution_key`
|
|
- `quote_id`
|
|
- `pair`
|
|
- `status`
|
|
- `result_code`
|
|
- `note`
|
|
- `venue_response`
|
|
- `error`
|
|
|
|
Useful for:
|
|
|
|
- execution success and failure rates
|
|
- venue response analysis
|
|
- distinguishing disarmed rejects from submission failures
|
|
- command-to-result conversion tracking
|
|
|
|
#### `portfolio_metrics_snapshots`
|
|
|
|
This table is derived from durable history rather than copied directly from a Kafka event.
|
|
|
|
Top-level columns:
|
|
|
|
- `metric_id`
|
|
- `computed_at`
|
|
- `baseline_anchor_at`
|
|
- `baseline_status`
|
|
- `payload`
|
|
|
|
Key payload attributes:
|
|
|
|
- `metric_version`
|
|
- `baseline_status`
|
|
- `command_count`
|
|
- `result_count`
|
|
- `current_price.price_id`
|
|
- `current_price.observed_at`
|
|
- `current_price.eure_per_btc`
|
|
- `current_inventory.inventory_id`
|
|
- `current_inventory.synced_at`
|
|
- `current_inventory.btc_units`
|
|
- `current_inventory.btc`
|
|
- `current_inventory.eure_units`
|
|
- `current_inventory.eure`
|
|
- `current_portfolio_value_eure`
|
|
- `current_btc_mark_value_eure`
|
|
- `current_eure_cash_value_eure`
|
|
- `trade_pnl_eure`
|
|
- `mark_to_market_pnl_eure`
|
|
- `price_move_pnl_eure`
|
|
- `baseline_portfolio_value_eure_at_baseline_price`
|
|
- `baseline_portfolio_value_eure_at_current_price`
|
|
- `current_portfolio_value_eure_at_baseline_price`
|
|
- `inventory_delta.btc_units`
|
|
- `inventory_delta.btc`
|
|
- `inventory_delta.eure_units`
|
|
- `inventory_delta.eure`
|
|
- `baseline.anchor`
|
|
- `baseline.command_at`
|
|
- `baseline.price`
|
|
- `baseline.inventory`
|
|
|
|
Useful for:
|
|
|
|
- current operator PnL display
|
|
- separating trade contribution from price movement
|
|
- baseline vs current comparison
|
|
- current portfolio valuation using the current price snapshot
|
|
|
|
### 3. JSON / PVC-Backed Operational State
|
|
|
|
Some important state is persisted outside PostgreSQL because it is operational control state, not analytical history.
|
|
|
|
#### Strategy armed state
|
|
|
|
Stored in:
|
|
|
|
- `strategyStateDir/strategy-engine-control.json`
|
|
|
|
Attributes:
|
|
|
|
- `armed`
|
|
- `updated_at`
|
|
|
|
Purpose:
|
|
|
|
- preserve strategy arming/disarming across restarts
|
|
|
|
#### Executor armed state
|
|
|
|
Stored in:
|
|
|
|
- `executorStateDir/trade-executor-control.json`
|
|
|
|
Attributes:
|
|
|
|
- `armed`
|
|
- `updated_at`
|
|
|
|
Purpose:
|
|
|
|
- preserve executor arming/disarming across restarts
|
|
|
|
#### Executor command idempotency state
|
|
|
|
Stored in:
|
|
|
|
- `executorStateDir/trade-executor-commands.json`
|
|
|
|
Attributes per command id:
|
|
|
|
- `quote_id`
|
|
- `idempotency_key`
|
|
- `execution_key`
|
|
- `status`
|
|
- `updated_at`
|
|
- result or error metadata when available
|
|
|
|
Purpose:
|
|
|
|
- prevent duplicate execution across restart or replay scenarios
|
|
|
|
#### Liquidity manager state
|
|
|
|
Stored in:
|
|
|
|
- `liquidityStateDir/liquidity.json`
|
|
|
|
Key attributes:
|
|
|
|
- `paused`
|
|
- `funding_observer_paused`
|
|
- `withdrawals_frozen`
|
|
- `deposit_addresses`
|
|
- `deposits`
|
|
- `tracked_withdrawals`
|
|
- `supported_tokens`
|
|
- `funding_observations`
|
|
- `funding_observations_by_handle`
|
|
- `funding_visibility_by_asset`
|
|
- `uncredited_funding_total_by_asset`
|
|
- `credit_correlation`
|
|
- `observer_health`
|
|
- `last_refresh_at`
|
|
- `last_funding_observation_at`
|
|
- `funding_observer_last_refresh_at`
|
|
- `funding_observer_last_error`
|
|
- `last_error`
|
|
- `last_withdrawal_request`
|
|
- `last_withdrawal_result`
|
|
- `publish_count`
|
|
- `funding_publish_count`
|
|
|
|
Purpose:
|
|
|
|
- preserve treasury and observer operational state across restarts
|
|
- back service `/state` responses
|
|
|
|
## What The Data Lets You Do
|
|
|
|
With the currently persisted data, you can already answer questions like:
|
|
|
|
- How many quotes arrived, at what sizes, and in which direction?
|
|
- Which quotes were rejected because of stale price, stale inventory, insufficient inventory, pending deposits, unsupported pair, or max notional?
|
|
- Which decisions became actual commands?
|
|
- Which commands became successful submitted results?
|
|
- What was the reference price and inventory snapshot seen by the strategy when it made a decision?
|
|
- How often did the pricing source fall back from Kraken to CoinGecko?
|
|
- How much pre-credit funding was visible before bridge crediting happened?
|
|
- How long did funding remain in a pending or stuck state?
|
|
- What alert transitions happened and how long did they remain raised?
|
|
- What is the current portfolio value, and how much of the change looks like price move vs trade contribution?
|
|
|
|
## Current Limits
|
|
|
|
A few important limits are worth naming plainly:
|
|
|
|
- Kafka retention is not the long-term historical warehouse; PostgreSQL is the main durable query surface
|
|
- raw venue schemas can drift, so raw quote storage matters for parser changes
|
|
- execution results currently represent submission outcomes, not full realized settlement economics
|
|
- fee coverage is not yet complete enough to treat all PnL numbers as full realized net profit
|
|
- some safety-critical state lives in JSON/PVC-backed operational stores, not only in PostgreSQL
|
|
|
|
## Recommended Reading Order
|
|
|
|
If you want to trace the system from code:
|
|
|
|
1. `src/lib/config.mjs`
|
|
2. `src/venues/near-intents/ws.mjs`
|
|
3. `src/apps/strategy-engine.mjs`
|
|
4. `src/apps/trade-executor.mjs`
|
|
5. `src/apps/history-writer.mjs`
|
|
6. `src/core/history-records.mjs`
|
|
7. `src/lib/postgres.mjs`
|
|
8. `src/apps/operator-dashboard.mjs`
|