Files
auto-stock/backend/app/trading/order_manager.py

195 lines
6.8 KiB
Python

"""
Order Manager
주문 실행·취소·조회 전담 모듈
"""
from typing import Dict, Any, Optional
import logging
from app.trading.kis_client import kis_client
logger = logging.getLogger(__name__)
class OrderManager:
"""주문 실행 관리자"""
def __init__(self, client=None):
self.client = client or kis_client
async def place_order(
self,
stock_code: str,
order_type: str, # "BUY" or "SELL"
quantity: int,
price: Optional[float] = None # None = 시장가
) -> Dict[str, Any]:
"""주문 실행"""
try:
account_number_prefix = self.client.account_number[:8]
account_number_suffix = self.client.account_number[8:]
is_virtual = "vts" in self.client.base_url.lower()
if order_type == "BUY":
tr_id = "VTTC0802U" if is_virtual else "TTTC0802U"
else:
tr_id = "VTTC0801U" if is_virtual else "TTTC0801U"
order_dvsn = "01" if price is None else "00"
order_price = "0" if price is None else str(int(price))
logger.info(f"📤 주문: {order_type} {stock_code} {quantity}주 @{order_price}")
result, _ = await self.client.request(
"POST",
"/uapi/domestic-stock/v1/trading/order-cash",
tr_id,
json={
"CANO": account_number_prefix,
"ACNT_PRDT_CD": account_number_suffix,
"PDNO": stock_code,
"ORD_DVSN": order_dvsn,
"ORD_QTY": str(quantity),
"ORD_UNPR": order_price
}
)
logger.info(f"📥 주문 응답: rt_cd={result.get('rt_cd')}, odno={result.get('odno')}")
return result
except Exception as e:
logger.error(f"❌ 주문 실패: {e}")
return {"rt_cd": "-1", "msg1": str(e), "odno": None}
async def cancel_order(self, order_id: str, quantity: int) -> Dict[str, Any]:
"""주문 취소"""
try:
account_number_prefix = self.client.account_number[:8]
account_number_suffix = self.client.account_number[8:]
is_virtual = "vts" in self.client.base_url.lower()
tr_id = "VTTC0803U" if is_virtual else "TTTC0803U"
result, _ = await self.client.request(
"POST",
"/uapi/domestic-stock/v1/trading/order-rvsecncl",
tr_id,
json={
"CANO": account_number_prefix,
"ACNT_PRDT_CD": account_number_suffix,
"KRX_FWDG_ORD_ORGNO": "",
"ORGN_ODNO": order_id,
"ORD_DVSN": "00",
"RVSE_CNCL_DVSN_CD": "02",
"ORD_QTY": "0",
"ORD_UNPR": "0",
"QTY_ALL_ORD_YN": "Y"
}
)
return result
except Exception as e:
logger.error(f"❌ 취소 실패: {e}")
raise
async def get_balance(self) -> Dict[str, Any]:
"""계좌 잔고 조회"""
try:
account_number_prefix = self.client.account_number[:8]
account_number_suffix = self.client.account_number[8:]
is_virtual = "vts" in self.client.base_url.lower()
tr_id = "VTTC8434R" if is_virtual else "TTTC8434R"
result, _ = await self.client.request(
"GET",
"/uapi/domestic-stock/v1/trading/inquire-balance",
tr_id,
params={
"CANO": account_number_prefix,
"ACNT_PRDT_CD": account_number_suffix,
"AFHR_FLPR_YN": "N",
"OFL_YN": "",
"INQR_DVSN": "02",
"UNPR_DVSN": "01",
"FUND_STTL_ICLD_YN": "N",
"FNCG_AMT_AUTO_RDPT_YN": "N",
"PRCS_DVSN": "01",
"CTX_AREA_FK100": "",
"CTX_AREA_NK100": ""
}
)
return result
except Exception as e:
logger.error(f"❌ 잔고 조회 실패: {e}")
raise
async def place_overseas_order(
self,
symbol: str,
order_type: str,
quantity: int,
price: float,
exchange_code: str = "NASD"
) -> Dict[str, Any]:
"""해외주식 주문"""
try:
account_number_prefix = self.client.account_number[:8]
account_number_suffix = self.client.account_number[8:]
is_virtual = "vts" in self.client.base_url.lower()
if order_type == "BUY":
tr_id = "VTTT1002U" if is_virtual else "TTTT1002U"
else:
tr_id = "VTTT1006U" if is_virtual else "TTTT1006U"
result, _ = await self.client.request(
"POST",
"/uapi/overseas-stock/v1/trading/order",
tr_id,
json={
"CANO": account_number_prefix,
"ACNT_PRDT_CD": account_number_suffix,
"OVRS_EXCG_CD": exchange_code.upper(),
"PDNO": symbol.upper(),
"ORD_QTY": str(quantity),
"OVRS_ORD_UNPR": str(price),
"ORD_SVR_DVSN_CD": "0",
"ORD_DVSN": "00"
}
)
return result
except Exception as e:
logger.error(f"❌ 해외주식 주문 실패: {e}")
raise
async def get_overseas_balance(self) -> Dict[str, Any]:
"""해외주식 잔고 조회"""
try:
account_number_prefix = self.client.account_number[:8]
account_number_suffix = self.client.account_number[8:]
is_virtual = "vts" in self.client.base_url.lower()
tr_id = "VTTS3012R" if is_virtual else "TTTS3012R"
result, _ = await self.client.request(
"GET",
"/uapi/overseas-stock/v1/trading/inquire-balance",
tr_id,
params={
"CANO": account_number_prefix,
"ACNT_PRDT_CD": account_number_suffix,
"OVRS_EXCG_CD": "NASD",
"TR_CRCY_CD": "USD",
"CTX_AREA_FK200": "",
"CTX_AREA_NK200": "",
}
)
return result
except Exception as e:
logger.error(f"❌ 해외주식 잔고 조회 실패: {e}")
return {}
# 싱글톤
order_manager = OrderManager()