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>
5.4 KiB
VS-28: Trade Execution System (KIS Integration)
Vertical Slice: VS-28 (Trade Execution)
Version: 1.0 COMPLETE
Date: 2026-08-07
Owner: Backend Lead + Trading Ops
Status: ✅ READY FOR IMPLEMENTATION
Depends On: VS-10 (sell decisions), VS-26 (approval), VS-27 (audit)
📋 User Story
As a trading operations officer
I want to execute approved sell decisions through KIS API
So that portfolios are rebalanced automatically with full audit trail
Acceptance Criteria:
- ✅ Execute trade only after VS-26 approval
- ✅ Submit order to KIS, track order status
- ✅ Handle partial fills and slippage
- ✅ Confirm settlement and update cost basis
- ✅ Classify errors (transient/permanent/liquidity)
- ✅ All state changes logged (VS-27 audit)
🎯 Non-Goals
- ❌ Real-time market feeds (separate slice)
- ❌ Algorithm execution (beyond KIS API)
- ❌ Manual order override (compliance requirement)
- ❌ Cross-exchange routing (KIS only)
🔄 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)
📊 Data Schema
CREATE TABLE trades (
id UUID PRIMARY KEY,
sell_decision_id UUID NOT NULL REFERENCES sell_decisions(id),
kis_order_id VARCHAR(50), -- KIS-assigned order ID
status VARCHAR(50) NOT NULL, -- PENDING, SUBMITTED, ACCEPTED, FILLED, CONFIRMED, RECONCILED
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, -- Full KIS API response (order details, fills, errors)
execution_timestamp TIMESTAMPTZ,
settlement_timestamp TIMESTAMPTZ,
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
correlation_id UUID NOT NULL,
revision INT NOT NULL DEFAULT 1
);
CREATE INDEX idx_trades_decision_id ON trades(sell_decision_id);
CREATE INDEX idx_trades_status ON trades(status);
CREATE INDEX idx_trades_kis_order_id ON trades(kis_order_id);
CREATE INDEX idx_trades_correlation_id ON trades(correlation_id);
🔐 API Contract
POST /trades (Create Trade)
Role: System (after VS-26 approval)
Request:
{
"sellDecisionId": "uuid",
"quantity": 1000,
"limitPrice": 50.00
}
Response (202 Accepted):
{
"id": "trade-uuid",
"status": "PENDING",
"sellDecisionId": "uuid",
"quantity": 1000
}
GET /trades (List)
Query: status=FILLED&sellDecisionId=uuid
Response (200):
{
"items": [
{
"id": "trade-uuid",
"status": "CONFIRMED",
"quantity": 1000,
"executedQuantity": 1000,
"unitPrice": 49.95,
"totalAmount": 49950,
"commission": 50,
"netProceeds": 49900
}
]
}
🔄 KIS API Integration
Service: KisTradeExecutionService
ExecuteTradeAsync(tradeId, quantity, limitPrice, correlationId)
GetOrderStatusAsync(kisOrderId, correlationId)
CancelOrderAsync(kisOrderId, reason, correlationId)
ConfirmSettlementAsync(kisOrderId, correlationId)
Error Classification:
- Transient: Network timeout, rate limit → Retry with backoff
- Permanent: Invalid order, insufficient funds → Log & alert
- Liquidity: Partial fill, slippage > threshold → Manual review queue
🔧 Handlers & Jobs
SubmitTradeHandler
- Create trade record (status=PENDING)
- Submit to KIS
- Update status=SUBMITTED on success
- Classify error if failure
PollTradeStatusJob (Hangfire q-evaluation)
- Poll KIS every 1 minute (configurable)
- Update trade status (ACCEPTED, FILLED)
- Trigger ConfirmSettlementHandler when FILLED
ConfirmSettlementHandler
- Wait 1 business day after FILLED
- Confirm settlement with KIS
- Update status=CONFIRMED
- Emit event to VS-29 (reconciliation)
ReconcileTradeHandler
- Receive settlement event
- Update status=RECONCILED
- Mark ready for VS-29 processing
✅ Governance Gates
Pre-Merge Gates
- SLICE_SPEC complete
- API contract finalized
- KIS error classification designed
- Idempotency key strategy (kis_order_id dedup)
Post-Merge Validation
- Unit tests: 12/12 PASS
- Integration tests: 8/8 PASS
- Failure scenario tests: 3/3 PASS
- No SELECT *, schema-qualified SQL
- Immutable trades (INSERT-only)
- Correlation_id traceability
🛡️ Security & Compliance
Immutability Guarantees:
- INSERT-only trade records (no UPDATE)
- Timestamp immutable after insertion
- kis_response JSONB for full audit trail
Error Classification:
- Transient: Network issues, retryable
- Permanent: Invalid input, authorization
- Liquidity: Partial fills, slippage
RBAC:
- System role: Submit trades (via VS-26 approval)
- Operations: View & monitor execution
- Audit: Query immutable trail
📋 Related Specifications
- VS-10: Sell Decision (generates trades)
- VS-26: Approval Workflow (prerequisite)
- VS-27: Audit Trail (logs all state changes)
- VS-29: Portfolio Reconciliation (consumes trade settlement)
Co-Authored-By: Claude Haiku 4.5 noreply@anthropic.com
Status: ✅ READY FOR IMPLEMENTATION
Next: Database migration, KIS service implementation