package services import ( "encoding/json" "fmt" ) // OrderService 주식 주문 서비스 (매수/매도/정정/취소) type OrderService struct { client *KiwoomClient } var orderService *OrderService // GetOrderService 주문 서비스 싱글턴 반환 func GetOrderService() *OrderService { if orderService == nil { orderService = &OrderService{client: GetKiwoomClient()} } return orderService } // OrderRequest 주문 요청 파라미터 type OrderRequest struct { Exchange string // KRX, NXT, SOR Code string // 종목코드 Qty string // 주문수량 Price string // 주문단가 (시장가 시 빈 문자열) TradeTP string // 0:보통, 3:시장가, 5:조건부지정가, 6:최유리, 7:최우선 } // OrderResult 주문 응답 type OrderResult struct { OrderNo string `json:"orderNo"` ReturnCode int `json:"returnCode"` ReturnMsg string `json:"returnMsg"` } // Buy 매수주문 (kt10000) func (s *OrderService) Buy(req OrderRequest) (*OrderResult, error) { body := map[string]string{ "dmst_stex_tp": req.Exchange, "stk_cd": req.Code, "ord_qty": req.Qty, "ord_uv": req.Price, "trde_tp": req.TradeTP, "cond_uv": "", } respBody, err := s.client.post("kt10000", "/api/dostk/ordr", body) if err != nil { return nil, fmt.Errorf("매수주문 실패: %w", err) } return parseOrderResult(respBody) } // Sell 매도주문 (kt10001) func (s *OrderService) Sell(req OrderRequest) (*OrderResult, error) { body := map[string]string{ "dmst_stex_tp": req.Exchange, "stk_cd": req.Code, "ord_qty": req.Qty, "ord_uv": req.Price, "trde_tp": req.TradeTP, "cond_uv": "", } respBody, err := s.client.post("kt10001", "/api/dostk/ordr", body) if err != nil { return nil, fmt.Errorf("매도주문 실패: %w", err) } return parseOrderResult(respBody) } // Modify 정정주문 (kt10002) func (s *OrderService) Modify(exchange, origOrdNo, code, qty, price string) (*OrderResult, error) { body := map[string]string{ "dmst_stex_tp": exchange, "orig_ord_no": origOrdNo, "stk_cd": code, "mdfy_qty": qty, "mdfy_uv": price, "mdfy_cond_uv": "", } respBody, err := s.client.post("kt10002", "/api/dostk/ordr", body) if err != nil { return nil, fmt.Errorf("정정주문 실패: %w", err) } return parseOrderResult(respBody) } // Cancel 취소주문 (kt10003) // qty: "0" = 전량취소 func (s *OrderService) Cancel(exchange, origOrdNo, code, qty string) (*OrderResult, error) { body := map[string]string{ "dmst_stex_tp": exchange, "orig_ord_no": origOrdNo, "stk_cd": code, "cncl_qty": qty, } respBody, err := s.client.post("kt10003", "/api/dostk/ordr", body) if err != nil { return nil, fmt.Errorf("취소주문 실패: %w", err) } return parseOrderResult(respBody) } // parseOrderResult 주문 응답 JSON 파싱 공통 함수 func parseOrderResult(body []byte) (*OrderResult, error) { var result struct { OrdNo string `json:"ord_no"` ReturnCode int `json:"return_code"` ReturnMsg string `json:"return_msg"` } if err := json.Unmarshal(body, &result); err != nil { return nil, fmt.Errorf("주문 응답 파싱 실패: %w", err) } if result.ReturnCode != 0 { return nil, fmt.Errorf("주문 오류: %s", result.ReturnMsg) } return &OrderResult{ OrderNo: result.OrdNo, ReturnCode: result.ReturnCode, ReturnMsg: result.ReturnMsg, }, nil }