package model import AutoTradeItem import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable data class StockBalanceResponse( val rt_cd: String = "", val msg1: String = "", val output1: List = emptyList(), val output2: List = emptyList() ) @Serializable data class StockHolding( val pdno: String = "", // 상품번호 val prdt_name: String = "", // 상품명 val hldg_qty: String = "0", // 보유수량 val pchs_avg_pric: String = "0", // 매입평균가 val prpr: String = "0", // 현재가 val evlu_pfls_rt: String = "0.0", // 평가손익률 val evlu_amt: String = "0" // 평가금액 ) @Serializable data class BalanceSummary( val tot_evlu_amt: String = "0", // 총 평가금액 val evlu_pfls_rt: String = "0.0", // 총 수익률 (에러 발생 지점: 기본값 추가로 해결) val asst_icrt: String = "0.0", // 일부 환경에서 수익률 필드명 val nass_amt: String = "0" , // 순자산 금액 val dnca_tot_amt: String = "0" ) @Serializable data class RankingResponse( var rt_cd : String, var msg1 : String, var msg_cd : String, val output1: List = emptyList(), val output: List = emptyList() ) { val list = output + output1 + emptyList() } //@Serializable enum class RankingType( val title: String, val trId: String, val scrNo: String, val path: String, val extraParams: Map ) { // [1] 등락률 (RISE, FALL) // FID_INPUT_CNT_1: 0 (전체/당일), FID_TRGT_CLS_CODE: 11111111 (전체) RISE("상승률순", "FHPST01700000", "20170", "/uapi/domestic-stock/v1/ranking/fluctuation", mapOf("FID_RANK_SORT_CLS_CODE" to "0", "FID_TRGT_CLS_CODE" to "11111111", "FID_INPUT_CNT_1" to "0")), FALL("하락률순", "FHPST01700000", "20170", "/uapi/domestic-stock/v1/ranking/fluctuation", mapOf("FID_RANK_SORT_CLS_CODE" to "1", "FID_TRGT_CLS_CODE" to "11111111", "FID_INPUT_CNT_1" to "0")), // [2] 당사매매 (COMPANY_TRADE) - 날짜 빈값 = 당일 COMPANY_TRADE("당사매매", "FHPST01860000", "20186", "/uapi/domestic-stock/v1/ranking/traded-by-company", mapOf("FID_RANK_SORT_CLS_CODE" to "1", "FID_INPUT_DATE_1" to "", "FID_INPUT_DATE_2" to "", "FID_APLY_RANG_VOL" to "0", "FID_TRGT_CLS_CODE" to "11111111")), // [3] 거래량/거래대금 VOLUME("거래량순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank", mapOf("FID_RANK_SORT_CLS_CODE" to "2", "FID_TRGT_CLS_CODE" to "11111111")), VALUE("거래대금순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank", mapOf("FID_RANK_SORT_CLS_CODE" to "3", "FID_TRGT_CLS_CODE" to "11111111")), VOLUME0("거래량순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank", mapOf("FID_RANK_SORT_CLS_CODE" to "0", "FID_TRGT_CLS_CODE" to "11111111")), VOLUME1("거래량순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank", mapOf("FID_RANK_SORT_CLS_CODE" to "1", "FID_TRGT_CLS_CODE" to "11111111")), VOLUME4("거래량순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank", mapOf("FID_RANK_SORT_CLS_CODE" to "4", "FID_TRGT_CLS_CODE" to "11111111")), // [4] 기타 랭킹 (예상체결만 FID_MK_OP_CLS_CODE 필요) EXPECTED_RISE("예상상승", "FHPST01820000", "20182", "/uapi/domestic-stock/v1/ranking/exp-trans-updown", mapOf("FID_MK_OP_CLS_CODE" to "0", "FID_TRGT_CLS_CODE" to "11111111")), NEW_HIGH("52주신고가", "FHPST01690000", "20169", "/uapi/domestic-stock/v1/ranking/new-high-new-low", mapOf("FID_RANK_SORT_CLS_CODE" to "0", "FID_TRGT_CLS_CODE" to "11111111")), FINANCE("재무비율순", "FHPST01750000", "20175", "/uapi/domestic-stock/v1/ranking/finance-ratio", mapOf("FID_RANK_SORT_CLS_CODE" to "0", "FID_TRGT_CLS_CODE" to "0", "FID_INPUT_OPTION_1" to "2023", "FID_INPUT_OPTION_2" to "3")), MARKET_VALUE("시장가치순", "FHPST01790000", "20179", "/uapi/domestic-stock/v1/ranking/market-value", mapOf("FID_RANK_SORT_CLS_CODE" to "23", "FID_TRGT_CLS_CODE" to "0", "FID_INPUT_OPTION_1" to "2023", "FID_INPUT_OPTION_2" to "3")), SHORT_SALE("공매도상위", "FHPST04820000", "20482", "/uapi/domestic-stock/v1/ranking/short-sale", mapOf("FID_PERIOD_DIV_CODE" to "D", "FID_SELECT_DIV_CODE" to "1", "FID_INPUT_CNT_1" to "0", "FID_TRGT_CLS_CODE" to "0")), VOLUME_POWER("체결강도순", "FHPST01680000", "20168", "/uapi/domestic-stock/v1/ranking/volume-power", mapOf("FID_TRGT_CLS_CODE" to "11111111")), MARKET_CAP("시가총액순", "FHPST01740000", "20174", "/uapi/domestic-stock/v1/ranking/market-cap", mapOf("FID_TRGT_CLS_CODE" to "0")), HTS_TOP20("HTS조회상위", "HHMCM000100C0", "20175", "/uapi/domestic-stock/v1/ranking/hts-top-view", emptyMap()) } @Serializable data class RankingStock( val hts_kor_isnm: String = "", // 종목명 val hts_kor_alph_nm: String = "", // 종목명 val mkrtc_objt_iscd: String = "", // 종목코드 val mksc_shrn_iscd: String = "", // 종목코드 val stck_shrn_iscd: String = "", // 종목코드 val stck_prpr: String = "0", // 현재가 val prdy_ctrt: String = "0.0", // 등락률 val mrkt_div_cls_code : String = "J", ) { val name : String get() = listOf(hts_kor_isnm , hts_kor_alph_nm , mkrtc_objt_iscd).firstOrNull { it.isNotBlank() } ?: "" val code : String get() = listOf(mksc_shrn_iscd , mkrtc_objt_iscd , stck_shrn_iscd , hts_kor_isnm).firstOrNull { it.isNotBlank() } ?: "" } @Serializable data class OverseasRankingResponse( val rt_cd: String = "", val msg1: String = "", val output: List = emptyList() ) @Serializable data class OverseasRankingStock( val hts_kor_alph_nm: String, // 종목명 val mkrtc_objt_iscd: String, // 종목코드 (Ticker) val last: String, // 현재가 val diff: String, // 전일대비 val rate: String // 등락률 ) { // 국내용 RankingStock과 호환되도록 변환 함수 추가 fun toRankingStock() = RankingStock( hts_kor_alph_nm = hts_kor_alph_nm, mkrtc_objt_iscd = mkrtc_objt_iscd, stck_prpr = last, prdy_ctrt = rate ) } @Serializable data class UnifiedStockHolding( val code: String, // 종목코드 val name: String, // 종목명 val quantity: String, // 보유수량 val avgPrice: String, // 매입단가 val currentPrice: String, // 현재가 val profitRate: String, // 수익률 val evalAmount: String, // 평가금액 val isDomestic: Boolean // 국내/해외 구분 ) @Serializable data class UnifiedBalance( val totalAsset: String, // 총 평가자산 val totalProfitRate: String, // 총 수익률 val deposit: String, val holdings: List // 통합 보유 종목 리스트 ) @Serializable data class UnfilledOrder( val orgn_odno: String, @SerialName("odno") val ord_no: String, // JSON의 odno를 ord_no로 매핑 val pdno: String, @SerialName("prdt_name") val prdt_name: String, val ord_unpr: String, // JSON이 문자열이므로 String 권장 val ord_qty : String, val sll_buy_dvsn_cd: String, @SerialName("psbl_qty") val rmnd_qty: String, // JSON의 psbl_qty를 rmnd_qty로 매핑 val ord_dvsn_name: String, val rvse_cncl_dvsn_name: String ) @Serializable data class UnfilledResponse( val rt_cd: String, val msg1: String, val output: List = emptyList() ) fun UnfilledOrder.toAutoTradeItem(isDomestic: Boolean): AutoTradeItem { return AutoTradeItem( orderNo = this.ord_no, code = this.pdno, name = this.prdt_name, orderedPrice = this.ord_unpr.toDoubleOrNull() ?: 0.0, quantity = this.ord_qty.toIntOrNull() ?: 0, // 미체결 내역에서는 원 주문 수량을 알기 어려우므로 0 또는 별도 처리 remainedQuantity = this.rmnd_qty.toIntOrNull() ?: 0, status = if (this.sll_buy_dvsn_cd.equals("01")) "SELLING" else "PENDING_BUY", // 기본적으로 미체결은 매수/매도 대기 상태 isDomestic = isDomestic ) } // 단순 정보 전달용 데이터 클래스 data class StockBasicInfo( val code: String, val name: String, val isDomestic: Boolean, val quantity: String = "0" ) data class ExecutionData( val orderNo: String, val code: String, val price: String, val qty: String, val isFilled: Boolean )