Files
trex-core/trex_core/domain/pricing.py
T
genichinandClaude Opus 4.8 d8d9021260 feat(execution,obs): 호가단위 스냅(dcy-030/#28) + 운영 관측성(dcy-029/#29) + REL-007 환류
dcy-030 (issue #28, P0 체결 복구):
- REQ-083 KRX 호가단위(tick) 순수 유틸 (domain/pricing.py)
- REQ-070 CHG: 슬리피지 실효가 호가단위 스냅 — 상한 준수(매수 floor/매도 ceil)
  + band<1tick 하한/상한 보장 + 부동소수 epsilon 가드. 147700→148400/54500→54700.
- BAT-044 release-candidate

dcy-029 (issue #29, 운영 관측성):
- REQ-084 구조화 로깅 기반 (runtime/logging_setup.py): ISO8601 KST·레벨·Loki JSON
  ·correlation id(slot_id)
- REQ-085 주문 결과 로깅 (event=order_submit|fill|reject)
- REQ-087 거부 ERROR 가시성 (조용한 롤백 제거)
- REQ-086 청산 reason+actor 동반 노출 (안전망/전략 구분, 신규 필드 없음)
- REQ-088 운영 조회 CLI report (application/operator_report.py, 보유·오늘 체결
  ·실현 PnL net·대기, direct-DB)
- REQ-089 운영자 런북 docs/operations.md
- REQ-090 baseline 정합 (runtime-flows·project-structure·interface-contract)
- BAT-045(P0)·BAT-046(P1) release-candidate

REL-007: confirmed → feedback-captured (운영 인스턴스 라이브 검증 + #28/#29 환류
정규화). BAT-037~041 released.

검증: 537 passed, 47 skipped, 회귀 0, ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 13:19:31 +09:00

64 lines
2.2 KiB
Python

"""KRX 호가단위(tick-size) 산출 — 순수 도메인 함수 (REQ-083, dcy-030).
slippage 보호 지정가(REQ-070)가 KIS에 제출되기 전에 유효한 KRX 호가단위
배수로 스냅되도록 가격→tick 산출과 배수 내림/올림 헬퍼를 제공한다.
주식 전용이다. ETF/ETN 등 비주식 호가체계는 범위 밖이며(dcy-030 OQ-1),
2,000원 미만 비주식은 미지원(known limitation). 종목 유형 분기는 두지 않는다.
호가단위 표(KRX 주식, 2023-01-25~). 구간은 하한 포함/상한 미만 `[lo, hi)`:
가격(원) tick(원)
< 2,000 1
2,000 ~ 5,000 5
5,000 ~ 20,000 10
20,000 ~ 50,000 50
50,000 ~ 200,000 100
200,000 ~ 500,000 500
>= 500,000 1,000
"""
from __future__ import annotations
import math
# (lower_bound_inclusive, tick). 내림차순으로 평가한다. 단일 변경 지점(dcy-030 R-3).
_KRX_STOCK_TICKS: tuple[tuple[int, int], ...] = (
(500_000, 1_000),
(200_000, 500),
(50_000, 100),
(20_000, 50),
(5_000, 10),
(2_000, 5),
(0, 1),
)
def krx_stock_tick(price: float) -> int:
"""`price`(원)가 속한 KRX 주식 호가단위(원)를 반환한다.
구간 경계는 하한 포함(예: 정확히 50,000원 → 100원 tick).
"""
p = float(price)
for lower, tick in _KRX_STOCK_TICKS:
if p >= lower:
return tick
return 1 # p < 0 방어 (도달하지 않음)
# 부동소수 잡음 흡수용 epsilon. slippage raw = base*(1+pct/100)는 수학적으로
# 정수여도 float로는 1004.9999999999999처럼 나올 수 있다(잡음 ~ q*1e-16).
# q(=price/tick)는 유계라 1e-9면 잡음을 안전하게 흡수하면서 실제 값은 안 넘긴다.
_EPS = 1e-9
def floor_to_tick(price: float) -> int:
"""`price`를 해당 가격대 호가단위의 **내림 배수**로 정규화한다 (≤ price)."""
tick = krx_stock_tick(price)
return int(math.floor(float(price) / tick + _EPS) * tick)
def ceil_to_tick(price: float) -> int:
"""`price`를 해당 가격대 호가단위의 **올림 배수**로 정규화한다 (≥ price)."""
tick = krx_stock_tick(price)
return int(math.ceil(float(price) / tick - _EPS) * tick)