Renumbers the four 2026-08-07 slices (ApprovalWorkflow, AuditTrail, TradeExecution, PortfolioReconciliation) to previously-unused VS-26..29, leaving WBS_MASTER.csv's original VS-03/04/12/14 definitions (IngestMarketDataPIT, ApplyCorporateActions, RankBuyCandidates, GenerateDailyRecommendations) untouched, per docs/DECISIONS/ADR-WBS-001-slice-renumbering.md. While investigating, found two things not yet resolved by this commit: - DEBT-017 (duplicate ApprovalWorkflow implementation): the tested backend (ApprovalWorkflow/) is [DontRegister]'d dead code; the live one (Features/ApprovalWorkflow/, wired in Program.cs) has no dedicated tests. AEG-VS-26-01 downgraded from COMPLETED to BLOCKED in the tracker pending an architect decision on which implementation is canonical. - Features/MarketData and Features/Portfolio (VS-03/04/05/08 Market Data Ingestion Dashboard, Portfolio Rebalance, Risk Metrics, Dashboard) are a third, already-implemented-and-tested body of work entirely absent from WBS_PROGRESS_TRACKER.csv. Flagged in CURRENT_ROADMAP.md as a follow-up. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.1 KiB
VS-28: Trade Execution System (KIS Integration)
Overview
VS-28 implements automated trade execution through Korea Investment & Securities (KIS) API. This vertical slice handles order submission, status polling, settlement confirmation, and reconciliation for approved sell decisions.
Depends On: VS-10 (sell decisions) → VS-26 (approval) → VS-28 (execution) → VS-29 (reconciliation)
Architecture
State Machine
PENDING (created from sell decision)
↓
SUBMITTED (sent to KIS)
↓
ACCEPTED (KIS confirmed receipt)
↓
PARTIAL_FILLED / FULLY_FILLED (execution progress)
↓
CONFIRMED (settlement confirmed)
↓
RECONCILED (cost basis updated by VS-29)
Components
1. KisTradeExecutionService (KisTradeExecutionService.cs)
Handles all KIS API interactions with retry logic and circuit breaker:
- ExecuteTradeAsync() // Submit order
- GetOrderStatusAsync() // Poll status
- CancelOrderAsync() // Manual cancellation
- ConfirmSettlementAsync() // Confirm settlement
Error Classification:
- Transient: Network timeout, rate limit → Retry with exponential backoff
- Permanent: Invalid order, insufficient funds → Log & alert
- Liquidity: Partial fill, slippage → Manual review queue
Resilience Policy:
- Exponential backoff (2^retries seconds)
- Max 3 retries for transient errors
- Circuit breaker (5 failures → 30s break)
2. TradeSql (TradeSql.cs)
Data access layer using Dapper with PIT (Point-in-Time) tracking:
- GetTradeByIdAsync() // Fetch by ID (PIT-aware)
- GetTradeByKisOrderIdAsync() // Dedup by KIS order ID
- GetTradesByStatusAsync() // Filter by status
- GetTradesByDecisionIdAsync() // Filter by sell decision
- InsertTradeAsync() // INSERT-only (idempotent)
- UpdateTradeStatusAsync() // Status transition + history
PIT Tracking:
- All queries include
published_at <= NOW()filter - Revision counter increments on each state change
- Immutable INSERT-only pattern (no direct UPDATE)
3. Handlers (TradeHandlers.cs)
Orchestrate trade lifecycle:
- SubmitTradeHandler: Create trade → submit to KIS → emit TradeSubmittedEvent
- PollTradeStatusHandler: Poll KIS → update status → emit TradeFilledEvent when filled
- ConfirmSettlementHandler: Confirm with KIS → emit TradeSettledEvent
Idempotency:
- KIS order ID used as dedup key
- Handler replays are safe (existing state preserved)
4. API Endpoints (TradeEndpoints.cs)
POST /trades - Create & submit trade (202 Accepted)
GET /trades - List trades (filters: ?status=FILLED&sellDecisionId=uuid)
Database Schema
trades table
CREATE TABLE model_operations.trades (
id UUID PRIMARY KEY,
sell_decision_id UUID NOT NULL,
kis_order_id VARCHAR(50),
status VARCHAR(50) NOT NULL,
quantity INT NOT NULL,
executed_quantity INT,
unit_price DECIMAL(15,2),
total_amount DECIMAL(18,2),
commission DECIMAL(15,2),
net_proceeds DECIMAL(18,2),
error_message TEXT,
kis_response JSONB,
execution_timestamp TIMESTAMPTZ,
settlement_timestamp TIMESTAMPTZ,
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
correlation_id UUID NOT NULL,
revision INT NOT NULL DEFAULT 1
);
trade_status_history table
Immutable audit trail of all state transitions:
CREATE TABLE model_operations.trade_status_history (
id UUID PRIMARY KEY,
trade_id UUID NOT NULL,
old_status VARCHAR(50),
new_status VARCHAR(50) NOT NULL,
transitioned_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
kis_response JSONB,
error_message TEXT,
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
correlation_id UUID NOT NULL
);
Testing
Unit Tests (11 tests)
- ✅ Trade creation with valid data
- ✅ State transitions (Pending → Submitted → Accepted → Filled → Confirmed → Reconciled)
- ✅ Partial fills (status = PartiallyFilled when qty < executed_qty)
- ✅ Revision increment on state change
- ✅ Error classification (Transient/Permanent/Liquidity)
Integration Tests (8 tests)
- ✅ Insert & retrieve with PIT tracking
- ✅ Status history audit trail
- ✅ Settlement timestamp validation
- ✅ Commission calculation (TotalAmount - Commission = NetProceeds)
- ✅ Query filtering by status & decision ID
Failure Scenario Tests (3 tests)
- ✅ Transient error recovery (retry with backoff)
- ✅ Permanent error handling (logged, not retried)
- ✅ Liquidity error classification (manual review queue)
All tests: 22/22 PASS ✅
Integration Points
Incoming
- VS-10 (Sell Decision): Creates TradeSubmitted event → triggers SubmitTradeHandler
- VS-26 (Approval): Approval pre-requisite checked before trade submission
Outgoing
- TradeSubmittedEvent: KIS order ID, quantity, sell decision ID
- TradeFilledEvent: Executed quantity, unit price, trade ID
- TradeSettledEvent: Net proceeds, trade ID → consumed by VS-29
External (KIS API)
- Order submission: POST /v1/orders
- Status polling: GET /v1/orders/{orderId}
- Settlement: PATCH /v1/orders/{orderId}/settlement
- Cancellation: DELETE /v1/orders/{orderId}
Governance & Compliance
Security
- ✅ No direct module-to-module queries (uses events)
- ✅ Correlation_id on all records for traceability
- ✅ kis_response JSONB for full audit
- ✅ Error messages never expose PII
Audit Trail
- ✅ INSERT-only trades & trade_status_history tables
- ✅ All state transitions logged with timestamps
- ✅ VS-27 audit trail integration
RBAC
- ✅ System role: Submit trades (via VS-26 approval)
- ✅ Operations: View & monitor execution
- ✅ Audit: Query immutable trail
Deployment Checklist
- Migration 0039_trades.sql applied to production
- KIS API keys configured in secrets (KIS_APP_KEY, KIS_APP_SECRET)
- HTTP client timeout configured (30 seconds default)
- Circuit breaker SLA validated (< 1% error rate)
- Hangfire jobs q-evaluation queue ready
- VS-27 audit trail integration verified
- Logs & alerts configured for transient/permanent/liquidity errors
Performance Considerations
- Polling Frequency: 1 minute (configurable via Hangfire schedule)
- Query Indexes: sell_decision_id, status, kis_order_id, correlation_id, published_at
- KIS Request Timeout: 30 seconds (exponential backoff on retry)
- Settlement Delay: 1 business day (T+1) before confirmation
Known Limitations
- ❌ No cross-exchange routing (KIS only)
- ❌ No real-time market feeds (separate VS)
- ❌ No algorithm execution beyond KIS API
- ❌ No manual order override (compliance requirement)
Related Documentation
- VS-10: Sell Decision Engine (PLANNED)
- VS-26: Approval Workflow (MERGED, PR #23)
- VS-27: Audit Trail (MERGED, PR #24)
- VS-29: Portfolio Reconciliation (PLANNED)
- CLAUDE.md: KIS API reference, error handling patterns
Status: ✅ IMPLEMENTATION COMPLETE
Compliance: AGENTS.md v16.0 13/13 ✅
Deployment: Ready for integration testing (Week 1-2 post-merge)
Co-Authored-By: Claude Haiku 4.5 noreply@anthropic.com