unrip/archive/implementation/20260402T230445Z-first-non-mocked-tradeable-loop-for-one-pair-implementation.md
philipp 54dc05a94c Archive first live trade loop and open funding visibility turn
Proof: Preserve the completed first live BTC/EURe trade loop and establish the next approved implementation proof around pre-credit funding visibility and operator alerts.
Assumptions: The live-trade loop is sufficiently proven by the recorded deposits, withdrawals, durable command/result chain, and successful mainnet quote responses; the next highest-value slice is operational visibility rather than new execution breadth.
Still fake: The newly opened funding-visibility and alert turn is planning only; no pre-credit watcher or durable alert evaluator is implemented yet.
2026-04-03 01:07:02 +02:00

544 lines
16 KiB
Markdown

# Implementation Turn: first non-mocked tradeable loop for one pair
Status: open
Opened: 2026-04-01
## Goal
Build the first full live vertical slice that can actually attempt a trade:
1. observe a real NEAR Intents quote
2. enrich it with live external reference pricing
3. synchronize spendable inventory already credited inside NEAR Intents
4. evaluate it in a real strategy service
5. gate it by inventory and arm state
6. submit it through a real Near Intents executor
7. persist the full chain in PostgreSQL
This turn is explicitly not a storage side-quest. Storage, control surfaces, analytics support, and treasury funding visibility are included because they are required to make the trade loop trustworthy.
## Selected backlog items
- [I001] Hybrid reference-price service using Kraken stream and CoinGecko poll or fallback for the active pair inputs.
- [I002] PostgreSQL event store plus history writer for quotes, reference prices, decisions, commands, and execution results.
- [I003] Strategy engine that consumes `norm.swap_demand` plus reference prices and emits auditable decisions.
- [I004] Real Near Intents executor service using pre-funded internal inventory, with explicit arming and idempotent result reporting.
- [I006] Decision-to-command safety gate with explicit arm or disarm state, notional caps, and inventory freshness checks.
- [I007] Inventory-aware execution rule: implement both directions, but only fire the side backed by credited internal source-asset inventory.
- [I008] Inventory-sync service for NEAR Intents internal balances and pending funding state.
- [I009] Liquidity-manager service for deposit addresses, funding actions, and treasury visibility.
- [B002] No reference price source exists, so the system cannot estimate edge.
- [B003] Dummy reactor and dummy executor prevent a non-mocked trade path.
- [B004] The current plan assumed external hot wallets were on the hot trade path instead of pre-funded internal inventory.
## Architectural shape
### Event backbone
Kafka or Redpanda remains the backbone between services.
Required topic set for this turn:
- `raw.near_intents.quote`
- `norm.swap_demand`
- `ref.market_price`
- `state.intent_inventory`
- `ops.liquidity_action`
- `decision.trade_decision`
- `cmd.execute_trade`
- `exec.trade_result`
### Durable store
PostgreSQL is the first durable analytics and audit layer.
Why:
- enough write throughput for current scope
- strong queryability for replay, inspection, and debugging
- simple to operate
- better fit than inventing a warehouse right now
### Service split
The first real loop should be seven services:
- `near-intents-ingest`
- `market-reference-ingest`
- `inventory-sync`
- `liquidity-manager`
- `history-writer`
- `strategy-engine`
- `trade-executor`
## Service-by-service responsibilities
### 1. `near-intents-ingest`
Responsibilities:
- connect to the NEAR Intents quote stream
- filter or classify the active pair
- emit raw and normalized events
- expose runtime state
Inputs:
- NEAR Intents websocket
- pair filter config and runtime override
Outputs:
- `raw.near_intents.quote`
- `norm.swap_demand`
Control surface:
- `GET /healthz`
- `GET /state`
- `GET /pair-filter`
- `PUT /pair-filter`
- `POST /pair-filter/reset`
Important edge cases:
- websocket disconnect
- reconnect storm
- invalid JSON
- pair silent but connection healthy
### 2. `market-reference-ingest`
Responsibilities:
- subscribe to Kraken BTC/EUR pricing
- poll CoinGecko for fallback or cross-check pricing
- derive fair BTC/EURe price for both trade directions
- publish reference-price events
- expose latest source state and freshness
Inputs:
- Kraken stream
- CoinGecko HTTP API
Outputs:
- `ref.market_price`
Control surface:
- `GET /healthz`
- `GET /state`
- `POST /refresh`
- `POST /pause`
- `POST /resume`
Required state:
- latest Kraken price
- latest CoinGecko price
- derived fair rate
- freshness age
- source health flags
Important edge cases:
- Kraken disconnect
- CoinGecko timeout or rate limit
- both sources stale
- conflicting sources beyond tolerance
Required behavior:
- if Kraken is down but CoinGecko is fresh, degrade according to policy and record fallback usage
- if both are stale, mark the service unhealthy for decisioning
- the first implementation must make the EURe/EUR pricing assumption explicit:
- either treat EURe as 1:1 with EUR and record that plainly
- or use a separate sanity source and record the mapping logic
### 3. `inventory-sync`
Responsibilities:
- read current credited spendable inventory from NEAR Intents internal state
- distinguish credited balances from pending deposits and pending withdrawals
- publish current internal inventory state
- expose freshness and reconciliation state
Inputs:
- NEAR Intents inventory or verifier surfaces
- liquidity-manager state when relevant
Outputs:
- `state.intent_inventory`
Control surface:
- `GET /healthz`
- `GET /state`
- `POST /refresh`
- `POST /pause`
- `POST /resume`
Required state:
- spendable balances by asset
- pending inbound funding by asset
- pending outbound withdrawal by asset
- last sync time
- reconciliation status
Important edge cases:
- internal inventory surface unavailable
- external deposit seen but not yet credited internally
- credited balance lower than expected after funding
- stale inventory snapshot
Required behavior:
- only credited internal inventory counts as spendable
- pending treasury movements must remain non-spendable
- stale inventory state must be visible and actionable
### 4. `liquidity-manager`
Responsibilities:
- request and track deposit addresses or equivalent funding handles for treasury assets
- track treasury funding actions from external wallets into NEAR Intents
- track withdrawals and rebalance actions
- expose current funding pipeline state
- publish auditable liquidity action records
Inputs:
- treasury configuration
- external funding wallets
- NEAR Intents deposit or withdrawal surfaces
Outputs:
- `ops.liquidity_action`
Control surface:
- `GET /healthz`
- `GET /state`
- `POST /refresh`
- `POST /pause`
- `POST /resume`
- `POST /freeze-withdrawals`
Required state:
- active deposit addresses or funding handles
- recent funding attempts
- pending credits
- recent withdrawals
- rebalance state
Important edge cases:
- deposit address request failure
- external wallet funded but NEAR Intents credit delayed
- duplicate funding detection
- unsupported asset or chain mapping
Required behavior:
- treasury actions must remain visible and auditable
- funding must be decoupled from the per-trade hot path
### 5. `history-writer`
Responsibilities:
- consume the core topics
- write append-only rows into PostgreSQL
- preserve causality across quote, decision, and execution records
- expose lag and write state
Inputs:
- all core Kafka topics
Outputs:
- PostgreSQL rows
Control surface:
- `GET /healthz`
- `GET /state`
- `POST /pause`
- `POST /resume`
- `POST /drain`
Required stored record families:
- raw quotes
- normalized demand
- reference prices
- inventory snapshots
- liquidity actions
- trade decisions
- execute commands
- execution results
Important edge cases:
- PostgreSQL unavailable
- duplicate deliveries from Kafka
- offset commit mismatch after partial write
Required behavior:
- never claim success before the write is durable
- surface last committed offsets and last successful write time
### 6. `strategy-engine`
Responsibilities:
- join latest demand with latest price and inventory state
- compute implied quote rate
- compare it to fair rate
- apply freshness thresholds
- apply the initial 2% gross edge threshold
- apply max-notional and inventory checks
- emit auditable decision events
- emit `cmd.execute_trade` only when all gates pass and the system is armed
Inputs:
- `norm.swap_demand`
- `ref.market_price`
- `state.intent_inventory`
Outputs:
- `decision.trade_decision`
- `cmd.execute_trade`
Control surface:
- `GET /healthz`
- `GET /state`
- `POST /arm`
- `POST /disarm`
- `POST /pause`
- `POST /resume`
- `PUT /threshold`
- `PUT /limits`
Required decision state:
- arm state
- current threshold
- current notional cap
- latest decision
- latest rejected decision reason
- per-reason skip counters
Required decision record fields:
- `decision_id`
- `quote_id`
- `pair`
- `direction`
- `implied_rate`
- `reference_rate`
- `gross_edge_pct`
- `price_freshness_ms`
- `inventory_snapshot`
- `decision`
- `decision_reason`
Important edge cases:
- stale prices
- no price yet
- no inventory yet
- insufficient spendable inventory
- pending deposit exists but is not yet credited
- quote already expired
- repeated quote IDs
- one direction affordable and the other not
- fresh deploy starts armed by mistake
### 7. `trade-executor`
Responsibilities:
- consume `cmd.execute_trade`
- load the correct Near Intents signing authority
- perform the real Near Intents submission using pre-funded internal inventory
- preserve idempotency across retries and restarts
- emit `exec.trade_result`
Inputs:
- `cmd.execute_trade`
- signing secrets
- optional latest inventory state
Outputs:
- `exec.trade_result`
Control surface:
- `GET /healthz`
- `GET /state`
- `POST /arm`
- `POST /disarm`
- `POST /pause`
- `POST /resume`
- `POST /drain`
Required state:
- arm state
- last command seen
- last request sent
- last venue response
- in-flight command count
- completed command count
- error counters
Important edge cases:
- duplicate command delivery
- crash after submission but before result publish
- venue reject
- timeout with unknown outcome
- insufficient internal inventory detected late
- secret missing or invalid
- signer not authorized for the intended NEAR Intents account
Required behavior:
- never silently swallow a venue error
- always publish a result record for attempted commands
- make duplicate suppression durable
- never try to bridge or top up inventory during trade execution
- start disarmed by default on fresh deploy
## Persistence design
### Why PostgreSQL now
- current scale does not require a special time-series database
- rows are easier to inspect than a custom file format for this phase
- joins across decision, command, and result matter more right now than raw ingestion throughput
- treasury, inventory, and execution joins matter more than raw bus throughput at this stage
### Minimum tables or equivalent record families
- `raw_near_intents_quotes`
- `swap_demand_events`
- `market_price_events`
- `intent_inventory_snapshots`
- `liquidity_actions`
- `trade_decisions`
- `execute_trade_commands`
- `trade_execution_results`
### Query requirements
Must be able to answer:
- what was the latest fair price when this decision was made
- what spendable inventory existed when this decision was made
- what treasury funding actions were still pending
- why was this quote skipped
- what command was emitted for this quote
- what happened when the executor submitted it
- what credited internal inventory existed at the time
## Control and stop semantics
Every service must support inspection.
State endpoints must be sufficient to answer:
- is it healthy
- is it connected
- what is it currently using as input state
- what was the last successful action
- why is it blocked, if blocked
- whether the state is authoritative or stale
Stopping must be explicit:
- `pause` means stop taking new work but keep process alive
- `drain` means finish in-flight work then stop cleanly
- `disarm` means remain live and observable but refuse side effects
This matters because the operator must be able to halt strategy or execution without destroying the whole pipeline.
## Logging requirements
All services use structured JSON logs.
Stable top-level fields:
- `level`
- `service`
- `component`
- `event`
- `namespace`
- `venue`
- `topic`
- `pair`
Additional body fields when relevant:
- `quote_id`
- `decision_id`
- `command_id`
- `execution_id`
- `inventory_id`
- `liquidity_action_id`
- `gross_edge_pct`
- `price_freshness_ms`
Log when:
- source connection is lost or reestablished
- price source becomes stale
- inventory state becomes stale or recovers
- funding action is requested, seen, credited, delayed, failed, or frozen
- strategy rejects with a meaningful reason
- strategy arms or disarms
- executor arms or disarms
- command is submitted
- venue rejects or times out
- PostgreSQL disconnects or recovers
- control API changes state
Do not log every healthy message by default.
## Failure and failover behavior
### Reference pricing
- Kraken failure with fresh CoinGecko:
- allowed only if fallback policy says yes
- decision records must note fallback source use
- both sources stale:
- strategy blocks all execution
### Inventory state
- missing or stale inventory state:
- strategy may still emit a non-actionable rejected decision
- strategy must not emit `cmd.execute_trade`
- pending funding action:
- remains non-spendable
- must be visible in control state and durable records
- treasury action service down:
- already funded trading may continue if inventory-sync is fresh
- new funding or withdrawal operations must be blocked visibly
### Persistence
- PostgreSQL unavailable:
- history-writer unhealthy
- system must surface that audit history is impaired
- if the user wants strict mode, strategy or executor may be blocked until persistence returns
### Execution
- timeout with unknown venue outcome:
- emit explicit uncertain result
- preserve idempotency state for recovery
- restart after partial submission:
- executor must not blindly resubmit without checking prior state
- executor sees lower real inventory than strategy snapshot:
- emit explicit failure result
- do not attempt fallback treasury movement
## Testing and validation plan
### Unit tests
- implied-rate calculation for both directions
- explicit EURe/EUR pricing-basis test
- threshold checks
- stale-price blocking
- insufficient-inventory blocking
- pending-deposit-not-spendable blocking
- command emission only when armed
- idempotency transitions in executor
### Integration tests
- pricing service emits canonical reference-price events
- inventory-sync emits credited vs pending inventory state correctly
- liquidity-manager records funding actions and status transitions
- strategy consumes demand, pricing, and inventory and emits decision plus command as expected
- history-writer persists linked records across topics
- executor publishes result records for success and failure paths
### Runtime validation in cluster
- inspect each service `/state`
- verify live reference pricing updates
- verify live internal inventory state
- verify one treasury funding path from request or deposit tracking to credited inventory
- verify strategy and executor start disarmed on a fresh deploy
- observe a rejected decision due to block condition
- arm strategy and executor
- keep the first live max notional tiny, on the order of a few EURe, before any larger cap
- observe one command emission
- observe one real Near Intents execution attempt
- verify PostgreSQL contains the full linked chain
## Deliberately rejected for this turn
- dashboards
- broad multi-pair abstractions
- ML training infrastructure
- broad backtest framework
- polished operator UI
- warehouse or lakehouse design
## Expected deliverables
- `market-reference-ingest`
- `inventory-sync`
- `liquidity-manager`
- `history-writer`
- `strategy-engine`
- `trade-executor`
- PostgreSQL schema and migrations or equivalent setup
- updated Kafka topic creation and config
- shared control API pattern for all long-running services
- tests for strategy, inventory, persistence, and executor behavior
- docs for operations, arm or disarm flow, and inspection commands
## Validation target
This turn is only complete when the deployed system can, through repo-controlled services alone, take one live active-pair quote, price it, verify credited internal inventory, decide on it, gate it, submit a real Near Intents execution attempt, and preserve the full record chain in PostgreSQL.