KIS + KRX Pre-Trade Validation Matrix (Executable Rules Playbook)
Date: 2026-02-25 (KST)
TL;DR
If Vellab goes live on Korean equities, the main risk is not alpha quality but invalid order intents reaching the broker.
This playbook converts KRX market micro-rules + KIS API constraints into an executable pre-trade matrix:
- Validate session/order-type/time windows before any API call
- Normalize price to KRX tick-size ladder before submit
- Enforce throttle + websocket budget + reconnect discipline as hard gates
- Add a market-abuse risk filter (pattern-based, machine-enforced)
1) Source-Backed Constraints to Encode as Code
1.1 KRX Trading Time Windows (KOSPI/KOSDAQ baseline)
From 찾기쉬운 생활법령정보 summary of KRX business rules:
- Regular session: 09:00–15:30
- Pre-open after-hours: 08:00–09:00
- Closing-price match block: 08:30–08:40
- Post-close after-hours: 15:40–18:00
- Closing-price match block: 15:40–16:00
- Single-price after-hours block: 16:00–18:00
Implementation principle: strategy should declare allowed session blocks explicitly (no implicit “always-on” trading).
1.2 KRX Tick-Size Ladder (Must Round Before Send)
Price tick units (생활법령정보 summary):
- < 2,000 KRW: 1 KRW
- 2,000–4,999: 5 KRW
- 5,000–19,999: 10 KRW
- 20,000–49,999: 50 KRW
- 50,000–199,999: 100 KRW
- 200,000–499,999: 500 KRW
= 500,000: 1,000 KRW
If your engine computes fair value at 73,742 KRW, you must map to a legal tick (73,700 or 73,800 depending on side policy).
1.3 Market-Abuse Legal Boundary (Automation Still Liable)
Capital Markets Act Article 176 prohibits deceptive/manipulative patterns (e.g., collusive matched behavior, non-beneficial wash-like trades, misleading market activity).
Engine implication: implement anti-pattern gates in automation instead of relying on operator intuition.
1.4 KIS Connectivity + Auth Shape (from official sample repo)
From open-trading-api (kis_auth.py, kis_devlp.yaml, README):
- REST token:
POST /oauth2/tokenP - WebSocket approval key:
POST /oauth2/Approval - Prod REST:
https://openapi.koreainvestment.com:9443 - Paper REST:
https://openapivts.koreainvestment.com:29443 - Prod WS:
ws://ops.koreainvestment.com:21000 - Paper WS:
ws://ops.koreainvestment.com:31000
Notable operational note in sample README: token reissue guidance mentions “1 minute per issue” behavior.
2) Executable Pre-Trade Validation Matrix
| Gate | Input | Rule | Action on Fail | Telemetry Key |
|---|---|---|---|---|
| Session gate | nowKST, targetSession | Order allowed only in declared session block | reject intent (no broker call) | reject.session_window |
| Market-day gate | nowKST | block Saturdays + known exchange holidays/closed days | reject intent | reject.market_closed |
| Order-type gate | session, orderType | allowed order-type subset per session | reject intent | reject.order_type_session |
| Tick-size gate | px, side | price must align to legal KRX tick | auto-normalize or reject (policy) | adjust.tick_round / reject.tick |
| Qty gate | qty | min unit (1 share baseline, strategy-specific min lot optional) | reject intent | reject.qty_unit |
| Notional cap | qty, px | per-order and per-symbol cap | reject intent | reject.notional_cap |
| Position cap | currentPos, projectedPos | projected exposure <= configured max | reject intent | reject.position_cap |
| Throttle gate | account/appkey budget | REST/WS/token budget not exceeded | queue/defer/reject | throttle.hit |
| WS budget gate | wsSubs | active realtime regs <= allowed budget | deny new sub / rotate LRU | ws.sub_budget_hit |
| Abuse-pattern gate | order stream features | block suspicious repetitive self-reinforcing patterns | freeze strategy + alert | risk.abuse_pattern |
3) Session × Order Policy Table (Starter)
| Session block | Time (KST) | Allowed strategy intent (example) | Notes |
|---|---|---|---|
| PRE_CROSS | 08:30–08:40 | low-risk carryover only | no aggressive catch-up |
| REGULAR | 09:00–15:30 | full strategy set | strict loss + participation caps |
| POST_CLOSE_CROSS | 15:40–16:00 | controlled residual unwind | no new directional entries |
| POST_SINGLE | 16:00–18:00 | optional residual management only | symbol whitelist recommended |
Keep this table config-driven (session_policy.yaml), not hardcoded constants.
4) Tick Normalization Policy (Side-Aware)
Use deterministic rounding policy to avoid random reject patterns:
- Buy limit: round down to nearest legal tick (price-improvement conservative)
- Sell limit: round up to nearest legal tick
- If normalized price deviates >
maxTickDriftfrom model price, reject instead of silently mutating intent
This prevents hidden strategy drift and makes TCA attribution cleaner.
5) KIS Control-Plane Hardening Rules
5.1 Separate limiter buckets
Implement independent token buckets:
rest_realrest_papertoken_issuews_subscribe_ops
Do not share one global limiter; each has different failure costs.
5.2 Conservative token issuance policy
Because documentation/operational notes can differ over time, enforce conservative defaults:
token_issue: at most 1 per minute unless verified tighter/looser with latest notice- singleflight token refresh lock (prevent thundering refresh)
5.3 WebSocket anti-thrash
- exponential backoff + jitter
- reconnect attempts budget (e.g., <= N per rolling minute)
- breaker state after repeated failures
- no infinite reconnect loops
6) Abuse-Pattern Safety Layer (Article 176 aware)
Minimum machine checks before order send:
- Cancel/replace burst detector per symbol/window
- Same-price repetitive pinging detector without inventory objective
- Participation spike detector vs ADV/intraday curve
- Cross-account correlation alarm (if multi-account infra exists)
On trigger:
- strategy state ->
RISK_HOLD - auto-notify operator
- require manual resume + incident note
7) Suggested Data Contract for Validator
interface PreTradeIntent {
account: string
symbol: string
side: 'BUY' | 'SELL'
orderType: 'LIMIT' | 'MARKET' | '...'
qty: number
price?: number
strategyId: string
sessionTag: 'PRE_CROSS' | 'REGULAR' | 'POST_CLOSE_CROSS' | 'POST_SINGLE'
tsKst: string
}
interface ValidationResult {
ok: boolean
normalized?: { price?: number; qty?: number }
rejectCode?: string
rejectReason?: string
metrics: string[]
}
All rejected intents should be persisted to an audit table for postmortem and policy tuning.
8) Implementation Sequence (Fast + Safe)
- Build pure function validator (no network calls)
- Add unit tests for 50+ boundary scenarios (time edges, tick edges, cap edges)
- Integrate into execution gateway as first gate
- Enforce “no validation, no order” invariant at compile/runtime boundaries
- Add dashboard for reject/adjust rates by rule
9) References
- KIS Developers portal: https://apiportal.koreainvestment.com/intro
- KIS Open API sample repo: https://github.com/koreainvestment/open-trading-api
- Raw sample config (
kis_devlp.yaml): https://raw.githubusercontent.com/koreainvestment/open-trading-api/main/kis_devlp.yaml - Raw auth helper (
examples_llm/kis_auth.py): https://raw.githubusercontent.com/koreainvestment/open-trading-api/main/examples_llm/kis_auth.py - KRX trading-time/tick-unit legal summary (생활법령정보): https://easylaw.go.kr/CSP/CnpClsMain.laf?popMenu=ov&csmSeq=1701&ccfNo=2&cciNo=1&cnpClsNo=2
- Capital Markets Act Article 176 (국가법령정보센터): https://law.go.kr/lsLawLinkInfo.do?lsJoLnkSeq=1001155941&chrClsCd=010202
One-line takeaway
For KR live execution, treat pre-trade validation as a deterministic compiler pass: if intent doesn’t compile against market, broker, and legal rules, it never reaches the exchange.