2026-01-10 18:16:50 +09:00
|
|
|
package model
|
|
|
|
|
|
2026-01-19 17:09:37 +09:00
|
|
|
import AutoTradeItem
|
2026-01-13 16:04:25 +09:00
|
|
|
import kotlinx.serialization.SerialName
|
2026-01-10 18:16:50 +09:00
|
|
|
import kotlinx.serialization.Serializable
|
|
|
|
|
|
|
|
|
|
@Serializable
|
|
|
|
|
data class StockBalanceResponse(
|
|
|
|
|
val rt_cd: String = "",
|
|
|
|
|
val msg1: String = "",
|
|
|
|
|
val output1: List<StockHolding> = emptyList(),
|
|
|
|
|
val output2: List<BalanceSummary> = 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", // 일부 환경에서 수익률 필드명
|
2026-02-03 18:07:18 +09:00
|
|
|
val nass_amt: String = "0" , // 순자산 금액
|
|
|
|
|
val dnca_tot_amt: String = "0"
|
2026-01-10 18:16:50 +09:00
|
|
|
)
|
|
|
|
|
@Serializable
|
|
|
|
|
data class RankingResponse(
|
2026-01-13 16:04:25 +09:00
|
|
|
var rt_cd : String,
|
|
|
|
|
var msg1 : String,
|
|
|
|
|
var msg_cd : String,
|
|
|
|
|
val output1: List<RankingStock> = emptyList(),
|
2026-01-10 18:16:50 +09:00
|
|
|
val output: List<RankingStock> = emptyList()
|
2026-01-13 16:04:25 +09:00
|
|
|
) {
|
|
|
|
|
val list = output + output1 + emptyList()
|
2026-01-10 18:16:50 +09:00
|
|
|
}
|
|
|
|
|
|
2026-01-13 16:04:25 +09:00
|
|
|
enum class RankingType(
|
|
|
|
|
val title: String,
|
|
|
|
|
val trId: String,
|
|
|
|
|
val scrNo: String,
|
|
|
|
|
val path: String,
|
|
|
|
|
val sortCode: String // 추가: 각 TR ID에 맞는 정렬 코드
|
|
|
|
|
) {
|
|
|
|
|
VOLUME("거래량순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank", "0"),
|
|
|
|
|
VALUE("거래대금순", "FHPST01710000", "20171", "/uapi/domestic-stock/v1/quotations/volume-rank", "3"),
|
|
|
|
|
RISE("상승률순", "FHPST01700000", "20170", "/uapi/domestic-stock/v1/ranking/fluctuation", "0"),
|
|
|
|
|
FALL("하락률순", "FHPST01700000", "20170", "/uapi/domestic-stock/v1/ranking/fluctuation", "1"),
|
|
|
|
|
// MARKET_CAP("시가총액순", "FHPST01740000", "20174", "/uapi/domestic-stock/v1/quotations/market-cap", "0"),
|
|
|
|
|
// HTS_TOP20("HTS조회상위", "HHMCM000100C0", "20175", "/uapi/domestic-stock/v1/ranking/hts-top-view", "0"),
|
|
|
|
|
// 링크로 전달주신 추가 기능 보완
|
|
|
|
|
VOLUME_POWER("체결강도순", "FHPST01680000", "20168", "/uapi/domestic-stock/v1/ranking/volume-power", "0"),
|
|
|
|
|
// BEFORE("장전예상", "FHPST01820000", "20182", "/uapi/domestic-stock/v1/ranking/exp-trans-updown", "0"),
|
|
|
|
|
// AFTER("장후예상", "FHPST01820000", "20182", "/uapi/domestic-stock/v1/ranking/exp-trans-updown", "1")
|
2026-02-05 14:26:02 +09:00
|
|
|
FOREIGNER_BUY("외인순매수", "FHPST01720000", "20172", "uapidomestic-stockv1quotationsfrgn-buy-rank", "0"),
|
|
|
|
|
INSTITUTION_BUY("기관순매수", "FHPST01730000", "20173", "uapidomestic-stockv1quotationsinst-buy-rank", "0"),
|
|
|
|
|
|
|
|
|
|
// 신규: 재무/지표
|
|
|
|
|
PER_RANK("PER", "FHPST01760000", "20176", "uapidomestic-stockv1quotationsper-rank", "0"),
|
|
|
|
|
PBR_RANK("PBR", "FHPST01770000", "20177", "uapidomestic-stockv1quotationspbr-rank", "0"),
|
|
|
|
|
DIVIDEND("배당률", "FHPST01800000", "20180", "uapidomestic-stockv1quotationsdividend-rank", "0"),
|
|
|
|
|
|
|
|
|
|
// 신규: 시간외/예상
|
|
|
|
|
AFTER_HOURS_VOLUME("시간외거래량", "FHPST01810000", "20181", "uapidomestic-stockv1rankingafterhours-volume", "0"),
|
|
|
|
|
EXPECTED_RISE("예상상승", "FHPST01820000", "20182", "uapidomestic-stockv1rankingexp-trans-updown", "0"),
|
|
|
|
|
EXPECTED_FALL("예상하락", "FHPST01820000", "20182", "uapidomestic-stockv1rankingexp-trans-updown", "1"),
|
|
|
|
|
|
|
|
|
|
// 신규: 체결/호가/신고가 등
|
|
|
|
|
EXEC_STRENGTH("체결강도", "FHPST01780000", "20178", "uapidomestic-stockv1quotationsexec-strength", "0"),
|
|
|
|
|
BID_ASK_VOLUME("호가잔량", "FHPST01790000", "20179", "uapidomestic-stockv1quotationsbid-ask-volume", "0"),
|
|
|
|
|
NEW_HIGH("52주신고가", "FHPST01690000", "20169", "uapidomestic-stockv1rankingsh-52w-high", "0"),
|
|
|
|
|
|
|
|
|
|
// 신규: 신용/공매도/대량
|
|
|
|
|
MARGIN_BALANCE("신용잔고", "FHPST01830000", "20183", "uapidomestic-stockv1quotationsmargin-balance", "0"),
|
|
|
|
|
SHORT_SELL("공매도", "FHPST01840000", "20184", "uapidomestic-stockv1quotationsshort-sell", "0"),
|
|
|
|
|
LARGE_DEAL("대량체결", "FHPST01850000", "20185", "uapidomestic-stockv1quotationslarge-deal", "0"),
|
|
|
|
|
|
|
|
|
|
// 기타 인기 (KIS HTS 순위분석 기반)
|
|
|
|
|
INTEREST_TOP("관심순", "FHPST01860000", "20186", "uapidomestic-stockv1rankinginterest-top", "0"),
|
|
|
|
|
COMPANY_TRADE("당사매매", "FHPST01870000", "20187", "uapidomestic-stockv1rankingcompany-trade", "0")
|
2026-01-13 16:04:25 +09:00
|
|
|
}
|
2026-01-10 18:16:50 +09:00
|
|
|
|
|
|
|
|
@Serializable
|
|
|
|
|
data class RankingStock(
|
2026-01-13 16:04:25 +09:00
|
|
|
val hts_kor_isnm: String = "", // 종목명
|
2026-01-10 18:16:50 +09:00
|
|
|
val hts_kor_alph_nm: String = "", // 종목명
|
|
|
|
|
val mkrtc_objt_iscd: String = "", // 종목코드
|
2026-01-13 16:04:25 +09:00
|
|
|
val mksc_shrn_iscd: String = "", // 종목코드
|
2026-01-14 15:42:26 +09:00
|
|
|
val stck_shrn_iscd: String = "", // 종목코드
|
2026-01-10 18:16:50 +09:00
|
|
|
val stck_prpr: String = "0", // 현재가
|
2026-01-13 16:04:25 +09:00
|
|
|
val prdy_ctrt: String = "0.0", // 등락률
|
|
|
|
|
val mrkt_div_cls_code : String = "J",
|
|
|
|
|
) {
|
|
|
|
|
val name : String
|
2026-01-14 15:42:26 +09:00
|
|
|
get() = listOf(hts_kor_isnm , hts_kor_alph_nm , mkrtc_objt_iscd).firstOrNull { it.isNotBlank() } ?: ""
|
2026-01-13 16:04:25 +09:00
|
|
|
val code : String
|
2026-01-14 15:42:26 +09:00
|
|
|
get() = listOf(mksc_shrn_iscd , mkrtc_objt_iscd , stck_shrn_iscd , hts_kor_isnm).firstOrNull { it.isNotBlank() } ?: ""
|
2026-01-13 16:04:25 +09:00
|
|
|
}
|
2026-01-10 18:16:50 +09:00
|
|
|
@Serializable
|
|
|
|
|
data class OverseasRankingResponse(
|
2026-01-13 16:04:25 +09:00
|
|
|
val rt_cd: String = "",
|
|
|
|
|
val msg1: String = "",
|
2026-01-10 18:16:50 +09:00
|
|
|
val output: List<OverseasRankingStock> = 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
|
|
|
|
|
)
|
2026-01-13 16:04:25 +09:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@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, // 총 수익률
|
2026-02-03 18:07:18 +09:00
|
|
|
val deposit: String,
|
2026-01-13 16:04:25 +09:00
|
|
|
val holdings: List<UnifiedStockHolding> // 통합 보유 종목 리스트
|
|
|
|
|
)
|
2026-01-14 15:42:26 +09:00
|
|
|
|
|
|
|
|
|
|
|
|
|
@Serializable
|
|
|
|
|
data class UnfilledOrder(
|
2026-01-19 17:09:37 +09:00
|
|
|
val orgn_odno: String,
|
2026-01-20 15:13:50 +09:00
|
|
|
@SerialName("odno")
|
|
|
|
|
val ord_no: String, // JSON의 odno를 ord_no로 매핑
|
2026-01-19 17:09:37 +09:00
|
|
|
val pdno: String,
|
2026-01-20 15:13:50 +09:00
|
|
|
@SerialName("prdt_name")
|
|
|
|
|
val prdt_name: String,
|
2026-01-19 17:09:37 +09:00
|
|
|
val ord_unpr: String, // JSON이 문자열이므로 String 권장
|
2026-01-20 15:13:50 +09:00
|
|
|
val ord_qty : String,
|
2026-01-21 11:49:30 +09:00
|
|
|
val sll_buy_dvsn_cd: String,
|
2026-01-20 15:13:50 +09:00
|
|
|
@SerialName("psbl_qty")
|
|
|
|
|
val rmnd_qty: String, // JSON의 psbl_qty를 rmnd_qty로 매핑
|
2026-01-19 17:09:37 +09:00
|
|
|
val ord_dvsn_name: String,
|
|
|
|
|
val rvse_cncl_dvsn_name: String
|
2026-01-14 15:42:26 +09:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@Serializable
|
|
|
|
|
data class UnfilledResponse(
|
|
|
|
|
val rt_cd: String,
|
|
|
|
|
val msg1: String,
|
|
|
|
|
val output: List<UnfilledOrder> = emptyList()
|
|
|
|
|
)
|
|
|
|
|
|
2026-01-19 17:09:37 +09:00
|
|
|
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,
|
2026-01-20 15:13:50 +09:00
|
|
|
quantity = this.ord_qty.toIntOrNull() ?: 0, // 미체결 내역에서는 원 주문 수량을 알기 어려우므로 0 또는 별도 처리
|
2026-01-19 17:09:37 +09:00
|
|
|
remainedQuantity = this.rmnd_qty.toIntOrNull() ?: 0,
|
2026-01-21 11:49:30 +09:00
|
|
|
status = if (this.sll_buy_dvsn_cd.equals("01")) "SELLING" else "PENDING_BUY", // 기본적으로 미체결은 매수/매도 대기 상태
|
2026-01-19 17:09:37 +09:00
|
|
|
isDomestic = isDomestic
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-01-14 15:42:26 +09:00
|
|
|
|
2026-01-19 17:09:37 +09:00
|
|
|
// 단순 정보 전달용 데이터 클래스
|
|
|
|
|
data class StockBasicInfo(
|
2026-01-14 15:42:26 +09:00
|
|
|
val code: String,
|
|
|
|
|
val name: String,
|
2026-01-19 17:09:37 +09:00
|
|
|
val isDomestic: Boolean,
|
|
|
|
|
val quantity: String = "0"
|
2026-01-21 11:49:30 +09:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
data class ExecutionData(
|
|
|
|
|
val orderNo: String,
|
|
|
|
|
val code: String,
|
|
|
|
|
val price: String,
|
|
|
|
|
val qty: String,
|
|
|
|
|
val isFilled: Boolean
|
2026-01-22 16:21:18 +09:00
|
|
|
)
|