kispilot 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kispilot/__init__.py +3 -0
- kispilot/__main__.py +5 -0
- kispilot/api/__init__.py +1 -0
- kispilot/api/account/__init__.py +24 -0
- kispilot/api/account/account_asset_balance.py +130 -0
- kispilot/api/account/account_inquire_balance_rlz_pl.py +169 -0
- kispilot/api/account/account_inquire_period_profit.py +198 -0
- kispilot/api/account/account_inquire_period_rights.py +197 -0
- kispilot/api/account/account_inquire_period_trade_profit.py +206 -0
- kispilot/api/account/account_intgr_margin.py +196 -0
- kispilot/api/config.py +29 -0
- kispilot/api/data/__init__.py +1 -0
- kispilot/api/data/_mst.py +20 -0
- kispilot/api/data/kosdaq.py +75 -0
- kispilot/api/data/kospi.py +75 -0
- kispilot/api/data/master.py +138 -0
- kispilot/api/data/sector.py +30 -0
- kispilot/api/info/__init__.py +27 -0
- kispilot/api/info/info_balance_sheet.py +96 -0
- kispilot/api/info/info_financial_ratio.py +95 -0
- kispilot/api/info/info_growth_ratio.py +90 -0
- kispilot/api/info/info_income_statement.py +99 -0
- kispilot/api/info/info_other_major_ratios.py +90 -0
- kispilot/api/info/info_profit_ratio.py +90 -0
- kispilot/api/info/info_search_stock_info.py +149 -0
- kispilot/api/info/info_stability_ratio.py +90 -0
- kispilot/api/oauth/__init__.py +1 -0
- kispilot/api/oauth/kis_token.py +237 -0
- kispilot/api/order/__init__.py +40 -0
- kispilot/api/order/order_cash.py +200 -0
- kispilot/api/order/order_credit.py +189 -0
- kispilot/api/order/order_inquire_balance.py +290 -0
- kispilot/api/order/order_inquire_credit_psamount.py +114 -0
- kispilot/api/order/order_inquire_daily_ccld.py +277 -0
- kispilot/api/order/order_inquire_psbl_order.py +125 -0
- kispilot/api/order/order_inquire_psbl_sell.py +90 -0
- kispilot/api/order/order_inquire_reserve.py +188 -0
- kispilot/api/order/order_inquire_revise_cancel.py +161 -0
- kispilot/api/order/order_reserve.py +151 -0
- kispilot/api/order/order_reserve_revise_cancel.py +151 -0
- kispilot/api/order/order_revise_cancel.py +217 -0
- kispilot/api/price/__init__.py +45 -0
- kispilot/api/price/price_inquire_asking_price.py +182 -0
- kispilot/api/price/price_inquire_ccnl.py +95 -0
- kispilot/api/price/price_inquire_daily_itemchartprice.py +154 -0
- kispilot/api/price/price_inquire_daily_price.py +108 -0
- kispilot/api/price/price_inquire_etf_component_stock_price.py +132 -0
- kispilot/api/price/price_inquire_etf_price.py +146 -0
- kispilot/api/price/price_inquire_investor.py +111 -0
- kispilot/api/price/price_inquire_member.py +156 -0
- kispilot/api/price/price_inquire_price.py +168 -0
- kispilot/api/price/price_inquire_time_dailychartprice.py +128 -0
- kispilot/api/price/price_inquire_time_itemchartprice.py +128 -0
- kispilot/api/price/price_inquire_time_itemconclusion.py +120 -0
- kispilot/api/price/price_nav_comparison_daily_trend.py +108 -0
- kispilot/api/price/price_nav_comparison_time_trend.py +105 -0
- kispilot/api/price/price_nav_comparison_trend.py +118 -0
- kispilot/api/price_anal/__init__.py +37 -0
- kispilot/api/price_anal/price_anal_daily_loan_trans.py +142 -0
- kispilot/api/price_anal/price_anal_daily_short_sale.py +138 -0
- kispilot/api/price_anal/price_anal_foreign_institution_total.py +125 -0
- kispilot/api/price_anal/price_anal_frgnmem_pchs_trend.py +97 -0
- kispilot/api/price_anal/price_anal_inquire_investor_daily_by_market.py +146 -0
- kispilot/api/price_anal/price_anal_intstock_multprice.py +136 -0
- kispilot/api/price_anal/price_anal_investor_program_trade_today.py +110 -0
- kispilot/api/price_anal/price_anal_investor_trade_by_stock_daily.py +215 -0
- kispilot/api/price_anal/price_anal_mktfunds.py +104 -0
- kispilot/api/price_anal/price_anal_pbar_tratio.py +116 -0
- kispilot/api/price_anal/price_anal_program_trade_by_stock.py +101 -0
- kispilot/api/price_anal/price_anal_program_trade_by_stock_daily.py +107 -0
- kispilot/api/ranking_anal/__init__.py +38 -0
- kispilot/api/ranking_anal/ranking_anal_credit_balance.py +134 -0
- kispilot/api/ranking_anal/ranking_anal_disparity.py +136 -0
- kispilot/api/ranking_anal/ranking_anal_exp_trans_updown.py +131 -0
- kispilot/api/ranking_anal/ranking_anal_finance_ratio.py +162 -0
- kispilot/api/ranking_anal/ranking_anal_fluctuation.py +153 -0
- kispilot/api/ranking_anal/ranking_anal_market_cap.py +122 -0
- kispilot/api/ranking_anal/ranking_anal_market_value.py +146 -0
- kispilot/api/ranking_anal/ranking_anal_prefer_disparate_ratio.py +134 -0
- kispilot/api/ranking_anal/ranking_anal_profit_asset_index.py +142 -0
- kispilot/api/ranking_anal/ranking_anal_quote_balance.py +129 -0
- kispilot/api/ranking_anal/ranking_anal_short_sale.py +134 -0
- kispilot/api/ranking_anal/ranking_anal_volume_power.py +129 -0
- kispilot/api/ranking_anal/ranking_anal_volume_rank.py +135 -0
- kispilot/api/realtime/__init__.py +9 -0
- kispilot/api/realtime/realtime_asking_price_total.py +227 -0
- kispilot/api/realtime/realtime_ccnl_total.py +206 -0
- kispilot/api/realtime/realtime_member_total.py +239 -0
- kispilot/api/realtime/realtime_program_trade_total.py +172 -0
- kispilot/api/sector/__init__.py +28 -0
- kispilot/api/sector/sector_exp_index_trend.py +97 -0
- kispilot/api/sector/sector_exp_total_index.py +122 -0
- kispilot/api/sector/sector_inquire_daily_indexchartprice.py +131 -0
- kispilot/api/sector/sector_inquire_index_category_price.py +136 -0
- kispilot/api/sector/sector_inquire_index_daily_price.py +134 -0
- kispilot/api/sector/sector_inquire_index_price.py +117 -0
- kispilot/api/sector/sector_inquire_index_timeprice.py +94 -0
- kispilot/api/sector/sector_news_title.py +114 -0
- kispilot/api/utils/__init__.py +1 -0
- kispilot/api/utils/api_tracker.py +44 -0
- kispilot/api/utils/http_session.py +66 -0
- kispilot/app/__init__.py +1 -0
- kispilot/app/backtest/__init__.py +48 -0
- kispilot/app/backtest/custom_strategy.py +599 -0
- kispilot/app/backtest/engine.py +184 -0
- kispilot/app/backtest/indicators_lib.py +1454 -0
- kispilot/app/backtest/metrics.py +76 -0
- kispilot/app/backtest/strategies.py +320 -0
- kispilot/app/main.py +799 -0
- kispilot/app/ui/account.html +71 -0
- kispilot/app/ui/assets/css/base.css +468 -0
- kispilot/app/ui/assets/favicon.svg +1 -0
- kispilot/app/ui/assets/js/api.js +201 -0
- kispilot/app/ui/assets/js/chart-tools.js +186 -0
- kispilot/app/ui/assets/js/chart.js +466 -0
- kispilot/app/ui/assets/js/indicators.js +475 -0
- kispilot/app/ui/assets/js/layout.js +268 -0
- kispilot/app/ui/assets/js/live.js +143 -0
- kispilot/app/ui/assets/js/pages/account.js +133 -0
- kispilot/app/ui/assets/js/pages/backtest.js +813 -0
- kispilot/app/ui/assets/js/pages/dashboard.js +175 -0
- kispilot/app/ui/assets/js/pages/etf.js +98 -0
- kispilot/app/ui/assets/js/pages/market.js +121 -0
- kispilot/app/ui/assets/js/pages/ranking.js +171 -0
- kispilot/app/ui/assets/js/pages/stock-finance.js +105 -0
- kispilot/app/ui/assets/js/pages/stock-flow.js +169 -0
- kispilot/app/ui/assets/js/pages/stock.js +145 -0
- kispilot/app/ui/assets/js/pages/trade.js +495 -0
- kispilot/app/ui/assets/js/ui.js +215 -0
- kispilot/app/ui/backtest.html +251 -0
- kispilot/app/ui/etf.html +73 -0
- kispilot/app/ui/index.html +98 -0
- kispilot/app/ui/market.html +101 -0
- kispilot/app/ui/ranking.html +74 -0
- kispilot/app/ui/stock-finance.html +63 -0
- kispilot/app/ui/stock-flow.html +72 -0
- kispilot/app/ui/stock.html +87 -0
- kispilot/app/ui/trade.html +170 -0
- kispilot/app/utils/__init__.py +1 -0
- kispilot/app/utils/backtest.py +347 -0
- kispilot/app/utils/chart.py +304 -0
- kispilot/app/utils/realtime_hub.py +297 -0
- kispilot/app/utils/utils.py +79 -0
- kispilot/cli.py +300 -0
- kispilot/credentials.py +109 -0
- kispilot/install.py +185 -0
- kispilot/mcp_server/__init__.py +1 -0
- kispilot/mcp_server/analysis_tools.py +398 -0
- kispilot/mcp_server/prompts.py +185 -0
- kispilot/mcp_server/server.py +411 -0
- kispilot/paths.py +35 -0
- kispilot-0.1.0.dist-info/METADATA +204 -0
- kispilot-0.1.0.dist-info/RECORD +157 -0
- kispilot-0.1.0.dist-info/WHEEL +5 -0
- kispilot-0.1.0.dist-info/entry_points.txt +2 -0
- kispilot-0.1.0.dist-info/licenses/LICENSE +21 -0
- kispilot-0.1.0.dist-info/top_level.txt +1 -0
kispilot/__init__.py
ADDED
kispilot/__main__.py
ADDED
kispilot/api/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""계좌 단위(종목 개별이 아닌 계좌 전체) 조회 API 모음.
|
|
2
|
+
|
|
3
|
+
from kispilot.api.account import (
|
|
4
|
+
inquire_balance_rlz_pl, inquire_account_balance,
|
|
5
|
+
inquire_period_profit, inquire_period_trade_profit,
|
|
6
|
+
inquire_intgr_margin, inquire_period_rights,
|
|
7
|
+
)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .account_inquire_balance_rlz_pl import inquire_balance_rlz_pl
|
|
11
|
+
from .account_asset_balance import inquire_account_balance
|
|
12
|
+
from .account_inquire_period_profit import inquire_period_profit
|
|
13
|
+
from .account_inquire_period_trade_profit import inquire_period_trade_profit
|
|
14
|
+
from .account_intgr_margin import inquire_intgr_margin
|
|
15
|
+
from .account_inquire_period_rights import inquire_period_rights
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
"inquire_balance_rlz_pl",
|
|
19
|
+
"inquire_account_balance",
|
|
20
|
+
"inquire_period_profit",
|
|
21
|
+
"inquire_period_trade_profit",
|
|
22
|
+
"inquire_intgr_margin",
|
|
23
|
+
"inquire_period_rights",
|
|
24
|
+
]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# 투자계좌자산현황조회[v1_국내주식-048]
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
from kispilot.api.config import DOMAIN, CANO, ACNT_PRDT_CD, REAL_APPKEY, REAL_APP_SECRET
|
|
6
|
+
from kispilot.api.oauth.kis_token import load_token
|
|
7
|
+
from kispilot.api.utils.http_session import get_session
|
|
8
|
+
|
|
9
|
+
URL = "/uapi/domestic-stock/v1/trading/inquire-account-balance"
|
|
10
|
+
# ※ 모의투자 미지원 (실전투자 전용).
|
|
11
|
+
# ※ output1은 HTS(eFriend Plus) [0891] 계좌 자산비중(결제기준) 화면 아래 테이블과 동일.
|
|
12
|
+
# 자산종류별로 고정된 순서(주식/펀드/채권/... /예수금/합계 등 최대 20개, 21번 계좌는 17개)로 내려온다.
|
|
13
|
+
# ※ 응답 헤더에 tr_cont(F/M/D/E)가 있긴 하지만, 다음 페이지 요청에 필요한 CTX_AREA_FK100/NK100 같은
|
|
14
|
+
# 연속조회 키 파라미터 자체가 Query Parameter에 없다. output1이 최대 20개로 고정된 목록이라
|
|
15
|
+
# 실질적으로 연속조회가 필요 없는 것으로 보고, 페이지네이션 없이 1회 호출로 구현했다.
|
|
16
|
+
# ※ INQR_DVSN_1, BSPR_BF_DT_APLY_YN은 문서상 "공란입력"이 고정값이라 파라미터로 노출하지 않는다.
|
|
17
|
+
|
|
18
|
+
_TR_ID = "CTRP6548R"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ── DataClass 정의 ──────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class ResponseBodyOutput1:
|
|
25
|
+
pchs_amt: Optional[str] = None # 매입금액
|
|
26
|
+
evlu_amt: Optional[str] = None # 평가금액
|
|
27
|
+
evlu_pfls_amt: Optional[str] = None # 평가손익금액
|
|
28
|
+
crdt_lnd_amt: Optional[str] = None # 신용대출금액
|
|
29
|
+
real_nass_amt: Optional[str] = None # 실제순자산금액
|
|
30
|
+
whol_weit_rt: Optional[str] = None # 전체비중율
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class ResponseBodyOutput2:
|
|
35
|
+
pchs_amt_smtl: Optional[str] = None # 매입금액합계(유가매입금액)
|
|
36
|
+
nass_tot_amt: Optional[str] = None # 순자산총금액
|
|
37
|
+
loan_amt_smtl: Optional[str] = None # 대출금액합계
|
|
38
|
+
evlu_pfls_amt_smtl: Optional[str] = None # 평가손익금액합계
|
|
39
|
+
evlu_amt_smtl: Optional[str] = None # 평가금액합계(유가평가금액)
|
|
40
|
+
tot_asst_amt: Optional[str] = None # 총자산금액
|
|
41
|
+
tot_lnda_tot_ulst_lnda: Optional[str] = None # 총대출금액총융자대출금액
|
|
42
|
+
cma_auto_loan_amt: Optional[str] = None # CMA자동대출금액
|
|
43
|
+
tot_mgln_amt: Optional[str] = None # 총담보대출금액
|
|
44
|
+
stln_evlu_amt: Optional[str] = None # 대주평가금액
|
|
45
|
+
crdt_fncg_amt: Optional[str] = None # 신용융자금액
|
|
46
|
+
ocl_apl_loan_amt: Optional[str] = None # OCL_APL대출금액
|
|
47
|
+
pldg_stup_amt: Optional[str] = None # 질권설정금액
|
|
48
|
+
frcr_evlu_tota: Optional[str] = None # 외화평가총액
|
|
49
|
+
tot_dncl_amt: Optional[str] = None # 총예수금액
|
|
50
|
+
cma_evlu_amt: Optional[str] = None # CMA평가금액
|
|
51
|
+
dncl_amt: Optional[str] = None # 예수금액
|
|
52
|
+
tot_sbst_amt: Optional[str] = None # 총대용금액
|
|
53
|
+
thdt_rcvb_amt: Optional[str] = None # 당일미수금액
|
|
54
|
+
ovrs_stck_evlu_amt1: Optional[str] = None # 해외주식평가금액1
|
|
55
|
+
ovrs_bond_evlu_amt: Optional[str] = None # 해외채권평가금액
|
|
56
|
+
mmf_cma_mgge_loan_amt: Optional[str] = None # MMF·CMA담보대출금액
|
|
57
|
+
sbsc_dncl_amt: Optional[str] = None # 청약예수금액
|
|
58
|
+
pbst_sbsc_fnds_loan_use_amt: Optional[str] = None # 공모주청약자금대출사용금액
|
|
59
|
+
etpr_crdt_grnt_loan_amt: Optional[str] = None # 기업신용공여대출금액
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class ResponseBody:
|
|
64
|
+
rt_cd: str # 성공 실패 여부
|
|
65
|
+
msg_cd: str # 응답코드
|
|
66
|
+
msg1: str # 응답메세지
|
|
67
|
+
output1: List[ResponseBodyOutput1] = field(default_factory=list) # 자산종류별 현황(배열, 최대 20개)
|
|
68
|
+
output2: ResponseBodyOutput2 = field(default_factory=ResponseBodyOutput2) # 계좌 자산 요약(단일)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# ── 요청 함수 ───────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
def inquire_account_balance() -> ResponseBody:
|
|
74
|
+
"""투자계좌 자산현황을 조회한다. (모의투자 미지원, 실전 계좌 전용)
|
|
75
|
+
|
|
76
|
+
HTS(eFriend Plus) [0891] 계좌 자산비중(결제기준) 화면과 동일한 데이터.
|
|
77
|
+
output1은 주식/펀드/채권/... /예수금/합계 등 자산종류별 현황이 고정된 순서로 최대 20개
|
|
78
|
+
(21번 계좌는 17개) 내려오고, output2는 계좌 전체의 자산/대출 요약이다.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
rt_cd/msg_cd/msg1과 자산종류별 현황(output1), 계좌 자산 요약(output2)을 담은 ResponseBody.
|
|
82
|
+
"""
|
|
83
|
+
token = load_token("real")
|
|
84
|
+
|
|
85
|
+
headers = {
|
|
86
|
+
"content-type": "application/json; charset=utf-8",
|
|
87
|
+
"authorization": f"Bearer {token}",
|
|
88
|
+
"appkey": REAL_APPKEY,
|
|
89
|
+
"appsecret": REAL_APP_SECRET,
|
|
90
|
+
"tr_id": _TR_ID,
|
|
91
|
+
"custtype": "P",
|
|
92
|
+
}
|
|
93
|
+
params = {
|
|
94
|
+
"CANO": CANO,
|
|
95
|
+
"ACNT_PRDT_CD": ACNT_PRDT_CD,
|
|
96
|
+
"INQR_DVSN_1": "",
|
|
97
|
+
"BSPR_BF_DT_APLY_YN": "",
|
|
98
|
+
}
|
|
99
|
+
response = get_session().get(DOMAIN["real"] + URL, headers=headers, params=params, timeout=10)
|
|
100
|
+
raw = response.json()
|
|
101
|
+
|
|
102
|
+
# 에러 응답(rt_cd != "0")은 output1/output2 키가 없거나 null일 수 있어 안전 파싱.
|
|
103
|
+
# 문서 표기가 Output1/Output2(대문자)라 실제 JSON 키가 소문자일 가능성을 대비해 둘 다 확인한다.
|
|
104
|
+
fields1 = set(ResponseBodyOutput1.__dataclass_fields__)
|
|
105
|
+
raw_output1 = raw.get("output1") or raw.get("Output1") or []
|
|
106
|
+
output1 = [ResponseBodyOutput1(**{k: v for k, v in item.items() if k in fields1}) for item in raw_output1]
|
|
107
|
+
|
|
108
|
+
fields2 = set(ResponseBodyOutput2.__dataclass_fields__)
|
|
109
|
+
raw_output2 = raw.get("output2") or raw.get("Output2") or {}
|
|
110
|
+
output2 = ResponseBodyOutput2(**{k: v for k, v in raw_output2.items() if k in fields2})
|
|
111
|
+
|
|
112
|
+
return ResponseBody(
|
|
113
|
+
rt_cd=raw.get("rt_cd", ""),
|
|
114
|
+
msg_cd=raw.get("msg_cd", ""),
|
|
115
|
+
msg1=raw.get("msg1", ""),
|
|
116
|
+
output1=output1,
|
|
117
|
+
output2=output2,
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# ── 실행 ────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
result = inquire_account_balance()
|
|
125
|
+
if result.rt_cd != "0":
|
|
126
|
+
print(f"조회 실패: {result.msg_cd} - {result.msg1}")
|
|
127
|
+
else:
|
|
128
|
+
for row in result.output1:
|
|
129
|
+
print(row)
|
|
130
|
+
print(result.output2)
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# 주식잔고조회_실현손익[v1_국내주식-041]
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import List, Literal, Optional
|
|
4
|
+
|
|
5
|
+
from kispilot.api.config import DOMAIN, CANO, ACNT_PRDT_CD, REAL_APPKEY, REAL_APP_SECRET
|
|
6
|
+
from kispilot.api.oauth.kis_token import load_token
|
|
7
|
+
from kispilot.api.utils.http_session import get_session
|
|
8
|
+
|
|
9
|
+
URL = "/uapi/domestic-stock/v1/trading/inquire-balance-rlz-pl"
|
|
10
|
+
# ※ 모의투자 미지원 (실전투자 전용).
|
|
11
|
+
# ※ order_inquire_balance.py(주식잔고조회)와 거의 같지만, output2에 실현손익 관련 필드
|
|
12
|
+
# (rlzt_pfls/rlzt_erng_rt/real_evlu_pfls/real_evlu_pfls_erng_rt)가 추가된 버전이다.
|
|
13
|
+
# ※ CTX_AREA_FK100/NK100 쿼리 파라미터는 있지만, 응답 헤더 설명상 tr_cont로 다음 페이지를 이어받는
|
|
14
|
+
# 연속조회는 지원하지 않는 API라서(항상 공란으로 보냄) 페이지네이션 없이 1회 호출로 끝난다.
|
|
15
|
+
# (요청 헤더 표에는 F/M/D/E 설명이 있지만, 응답 헤더 표는 "다음조회 불가 API"라고 명시돼 있어 후자를 따름.)
|
|
16
|
+
|
|
17
|
+
_TR_ID = "TTTC8494R"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ── DataClass 정의 ──────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ResponseBodyOutput1:
|
|
24
|
+
pdno: Optional[str] = None # 상품번호(종목번호 뒤 6자리)
|
|
25
|
+
prdt_name: Optional[str] = None # 상품명(종목명)
|
|
26
|
+
trad_dvsn_name: Optional[str] = None # 매매구분명(매수/매도구분)
|
|
27
|
+
bfdy_buy_qty: Optional[str] = None # 전일매수수량
|
|
28
|
+
bfdy_sll_qty: Optional[str] = None # 전일매도수량
|
|
29
|
+
thdt_buyqty: Optional[str] = None # 금일매수수량
|
|
30
|
+
thdt_sll_qty: Optional[str] = None # 금일매도수량
|
|
31
|
+
hldg_qty: Optional[str] = None # 보유수량
|
|
32
|
+
ord_psbl_qty: Optional[str] = None # 주문가능수량
|
|
33
|
+
pchs_avg_pric: Optional[str] = None # 매입평균가격(매입금액/보유수량)
|
|
34
|
+
pchs_amt: Optional[str] = None # 매입금액
|
|
35
|
+
prpr: Optional[str] = None # 현재가
|
|
36
|
+
evlu_amt: Optional[str] = None # 평가금액
|
|
37
|
+
evlu_pfls_amt: Optional[str] = None # 평가손익금액(평가금액-매입금액)
|
|
38
|
+
evlu_pfls_rt: Optional[str] = None # 평가손익율
|
|
39
|
+
evlu_erng_rt: Optional[str] = None # 평가수익율
|
|
40
|
+
loan_dt: Optional[str] = None # 대출일자
|
|
41
|
+
loan_amt: Optional[str] = None # 대출금액
|
|
42
|
+
stln_slng_chgs: Optional[str] = None # 대주매각대금(신용거래 시 대부받은 주식의 매각대금)
|
|
43
|
+
expd_dt: Optional[str] = None # 만기일자
|
|
44
|
+
stck_loan_unpr: Optional[str] = None # 주식대출단가
|
|
45
|
+
bfdy_cprs_icdc: Optional[str] = None # 전일대비증감
|
|
46
|
+
fltt_rt: Optional[str] = None # 등락율
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class ResponseBodyOutput2:
|
|
51
|
+
dnca_tot_amt: Optional[str] = None # 예수금총금액
|
|
52
|
+
nxdy_excc_amt: Optional[str] = None # 익일정산금액(D+1 예수금)
|
|
53
|
+
prvs_rcdl_excc_amt: Optional[str] = None # 가수도정산금액(D+2 예수금)
|
|
54
|
+
cma_evlu_amt: Optional[str] = None # CMA평가금액
|
|
55
|
+
bfdy_buy_amt: Optional[str] = None # 전일매수금액
|
|
56
|
+
thdt_buy_amt: Optional[str] = None # 금일매수금액
|
|
57
|
+
nxdy_auto_rdpt_amt: Optional[str] = None # 익일자동상환금액
|
|
58
|
+
bfdy_sll_amt: Optional[str] = None # 전일매도금액
|
|
59
|
+
thdt_sll_amt: Optional[str] = None # 금일매도금액
|
|
60
|
+
d2_auto_rdpt_amt: Optional[str] = None # D+2자동상환금액
|
|
61
|
+
bfdy_tlex_amt: Optional[str] = None # 전일제비용금액
|
|
62
|
+
thdt_tlex_amt: Optional[str] = None # 금일제비용금액
|
|
63
|
+
tot_loan_amt: Optional[str] = None # 총대출금액
|
|
64
|
+
scts_evlu_amt: Optional[str] = None # 유가평가금액
|
|
65
|
+
tot_evlu_amt: Optional[str] = None # 총평가금액
|
|
66
|
+
nass_amt: Optional[str] = None # 순자산금액
|
|
67
|
+
fncg_gld_auto_rdpt_yn: Optional[str] = None # 융자금자동상환여부
|
|
68
|
+
pchs_amt_smtl_amt: Optional[str] = None # 매입금액합계금액
|
|
69
|
+
evlu_amt_smtl_amt: Optional[str] = None # 평가금액합계금액
|
|
70
|
+
evlu_pfls_smtl_amt: Optional[str] = None # 평가손익합계금액
|
|
71
|
+
tot_stln_slng_chgs: Optional[str] = None # 총대주매각대금
|
|
72
|
+
bfdy_tot_asst_evlu_amt: Optional[str] = None # 전일총자산평가금액
|
|
73
|
+
asst_icdc_amt: Optional[str] = None # 자산증감액
|
|
74
|
+
asst_icdc_erng_rt: Optional[str] = None # 자산증감수익율
|
|
75
|
+
rlzt_pfls: Optional[str] = None # 실현손익
|
|
76
|
+
rlzt_erng_rt: Optional[str] = None # 실현수익율
|
|
77
|
+
real_evlu_pfls: Optional[str] = None # 실평가손익
|
|
78
|
+
real_evlu_pfls_erng_rt: Optional[str] = None # 실평가손익수익율
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass
|
|
82
|
+
class ResponseBody:
|
|
83
|
+
rt_cd: str # 성공 실패 여부
|
|
84
|
+
msg_cd: str # 응답코드
|
|
85
|
+
msg1: str # 응답메세지
|
|
86
|
+
output1: List[ResponseBodyOutput1] = field(default_factory=list) # 보유종목 목록(배열)
|
|
87
|
+
output2: List[ResponseBodyOutput2] = field(default_factory=list) # 계좌 요약 + 실현손익(배열, 보통 원소 1개)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# ── 요청 함수 ───────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
def inquire_balance_rlz_pl(
|
|
93
|
+
afhr_flpr_yn: Literal["N", "X", "Y"] = "N",
|
|
94
|
+
inqr_dvsn: Literal["00"] = "00",
|
|
95
|
+
fund_sttl_icld_yn: Literal["N", "Y"] = "N",
|
|
96
|
+
prcs_dvsn: Literal["00", "01"] = "00",
|
|
97
|
+
cost_icld_yn: Literal["Y", "N"] = "N",
|
|
98
|
+
) -> ResponseBody:
|
|
99
|
+
"""주식잔고(실현손익 포함)를 조회한다. (모의투자 미지원, 실전 계좌 전용)
|
|
100
|
+
|
|
101
|
+
한국투자 HTS(eFriend Plus) [0800] 국내 체결기준잔고 화면과 동일한 데이터.
|
|
102
|
+
order_inquire_balance.py와 달리 output2에 실현손익(rlzt_pfls 등) 필드가 있다.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
afhr_flpr_yn: 시장 구분(AFHR_FLPR_YN). N KRX정규장종가 / X NXT / Y KRX+NXT 통합시세. 기본값 "N".
|
|
106
|
+
inqr_dvsn: 조회구분(INQR_DVSN). 00 전체(문서상 유일한 값).
|
|
107
|
+
fund_sttl_icld_yn: 펀드결제포함여부(FUND_STTL_ICLD_YN). N 포함하지 않음 / Y 포함. 기본값 "N".
|
|
108
|
+
prcs_dvsn: 처리구분(PRCS_DVSN). 00 전일매매포함 / 01 전일매매미포함. 기본값 "00".
|
|
109
|
+
cost_icld_yn: 비용포함여부(COST_ICLD_YN). Y 포함 / N 미포함. 기본값 "N".
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
rt_cd/msg_cd/msg1과 보유종목 목록(output1), 계좌 요약+실현손익(output2)을 담은 ResponseBody.
|
|
113
|
+
"""
|
|
114
|
+
token = load_token("real")
|
|
115
|
+
|
|
116
|
+
headers = {
|
|
117
|
+
"content-type": "application/json; charset=utf-8",
|
|
118
|
+
"authorization": f"Bearer {token}",
|
|
119
|
+
"appkey": REAL_APPKEY,
|
|
120
|
+
"appsecret": REAL_APP_SECRET,
|
|
121
|
+
"tr_id": _TR_ID,
|
|
122
|
+
"custtype": "P",
|
|
123
|
+
}
|
|
124
|
+
params = {
|
|
125
|
+
"CANO": CANO,
|
|
126
|
+
"ACNT_PRDT_CD": ACNT_PRDT_CD,
|
|
127
|
+
"AFHR_FLPR_YN": afhr_flpr_yn,
|
|
128
|
+
"OFL_YN": "",
|
|
129
|
+
"INQR_DVSN": inqr_dvsn,
|
|
130
|
+
"UNPR_DVSN": "01",
|
|
131
|
+
"FUND_STTL_ICLD_YN": fund_sttl_icld_yn,
|
|
132
|
+
"FNCG_AMT_AUTO_RDPT_YN": "N",
|
|
133
|
+
"PRCS_DVSN": prcs_dvsn,
|
|
134
|
+
"COST_ICLD_YN": cost_icld_yn,
|
|
135
|
+
"CTX_AREA_FK100": "",
|
|
136
|
+
"CTX_AREA_NK100": "",
|
|
137
|
+
}
|
|
138
|
+
response = get_session().get(DOMAIN["real"] + URL, headers=headers, params=params, timeout=10)
|
|
139
|
+
raw = response.json()
|
|
140
|
+
|
|
141
|
+
# 에러 응답(rt_cd != "0")은 output1/output2 키가 없거나 null일 수 있어 안전 파싱.
|
|
142
|
+
fields1 = set(ResponseBodyOutput1.__dataclass_fields__)
|
|
143
|
+
raw_output1 = raw.get("output1") or []
|
|
144
|
+
output1 = [ResponseBodyOutput1(**{k: v for k, v in item.items() if k in fields1}) for item in raw_output1]
|
|
145
|
+
|
|
146
|
+
fields2 = set(ResponseBodyOutput2.__dataclass_fields__)
|
|
147
|
+
raw_output2 = raw.get("output2") or []
|
|
148
|
+
output2 = [ResponseBodyOutput2(**{k: v for k, v in item.items() if k in fields2}) for item in raw_output2]
|
|
149
|
+
|
|
150
|
+
return ResponseBody(
|
|
151
|
+
rt_cd=raw.get("rt_cd", ""),
|
|
152
|
+
msg_cd=raw.get("msg_cd", ""),
|
|
153
|
+
msg1=raw.get("msg1", ""),
|
|
154
|
+
output1=output1,
|
|
155
|
+
output2=output2,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ── 실행 ────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
result = inquire_balance_rlz_pl()
|
|
163
|
+
if result.rt_cd != "0":
|
|
164
|
+
print(f"조회 실패: {result.msg_cd} - {result.msg1}")
|
|
165
|
+
else:
|
|
166
|
+
for row in result.output1:
|
|
167
|
+
print(row)
|
|
168
|
+
for row in result.output2:
|
|
169
|
+
print(row)
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
# 기간별손익일별합산조회[v1_국내주식-052]
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import List, Literal, Optional
|
|
4
|
+
|
|
5
|
+
from kispilot.api.config import DOMAIN, CANO, ACNT_PRDT_CD, REAL_APPKEY, REAL_APP_SECRET
|
|
6
|
+
from kispilot.api.oauth.kis_token import load_token
|
|
7
|
+
from kispilot.api.utils.http_session import get_session
|
|
8
|
+
|
|
9
|
+
URL = "/uapi/domestic-stock/v1/trading/inquire-period-profit"
|
|
10
|
+
# ※ 모의투자 미지원 (실전투자 전용).
|
|
11
|
+
# ※ HTS(eFriend Plus) [0856] 기간별 매매손익 화면에서 "일별" 클릭 시와 동일한 데이터.
|
|
12
|
+
# ※ output2의 총손익률(우측 하단 표시값)은 이 API가 아니라 기간별매매손익현황조회(TTTC8715R)의
|
|
13
|
+
# output2.tot_pftrt(총수익률)로 확인해야 한다 — 이 파일에는 없음(문서에 명시된 주의사항).
|
|
14
|
+
# ※ 연속조회 지원: CTX_AREA_FK100/CTX_AREA_NK100이 실제 쿼리 파라미터에 있음(다른 계좌 API와 다름).
|
|
15
|
+
|
|
16
|
+
_TR_ID = "TTTC8708R"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# ── DataClass 정의 ──────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ResponseBodyOutput1:
|
|
23
|
+
trad_dt: Optional[str] = None # 매매일자
|
|
24
|
+
buy_amt: Optional[str] = None # 매수금액
|
|
25
|
+
sll_amt: Optional[str] = None # 매도금액
|
|
26
|
+
rlzt_pfls: Optional[str] = None # 실현손익
|
|
27
|
+
fee: Optional[str] = None # 수수료
|
|
28
|
+
loan_int: Optional[str] = None # 대출이자
|
|
29
|
+
tl_tax: Optional[str] = None # 제세금
|
|
30
|
+
pfls_rt: Optional[str] = None # 손익률
|
|
31
|
+
sll_qty1: Optional[str] = None # 매도수량1
|
|
32
|
+
buy_qty1: Optional[str] = None # 매수수량1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class ResponseBodyOutput2:
|
|
37
|
+
sll_qty_smtl: Optional[str] = None # 매도수량합계
|
|
38
|
+
sll_tr_amt_smtl: Optional[str] = None # 매도거래금액합계
|
|
39
|
+
sll_fee_smtl: Optional[str] = None # 매도수수료합계
|
|
40
|
+
sll_tltx_smtl: Optional[str] = None # 매도제세금합계
|
|
41
|
+
sll_excc_amt_smtl: Optional[str] = None # 매도정산금액합계
|
|
42
|
+
buy_qty_smtl: Optional[str] = None # 매수수량합계
|
|
43
|
+
buy_tr_amt_smtl: Optional[str] = None # 매수거래금액합계
|
|
44
|
+
buy_fee_smtl: Optional[str] = None # 매수수수료합계
|
|
45
|
+
buy_tax_smtl: Optional[str] = None # 매수제세금합계
|
|
46
|
+
buy_excc_amt_smtl: Optional[str] = None # 매수정산금액합계
|
|
47
|
+
tot_qty: Optional[str] = None # 총수량
|
|
48
|
+
tot_tr_amt: Optional[str] = None # 총거래금액
|
|
49
|
+
tot_fee: Optional[str] = None # 총수수료
|
|
50
|
+
tot_tltx: Optional[str] = None # 총제세금
|
|
51
|
+
tot_excc_amt: Optional[str] = None # 총정산금액
|
|
52
|
+
tot_rlzt_pfls: Optional[str] = None # 총실현손익
|
|
53
|
+
loan_int: Optional[str] = None # 대출이자
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class ResponseBody:
|
|
58
|
+
rt_cd: str # 성공 실패 여부
|
|
59
|
+
msg_cd: str # 응답코드
|
|
60
|
+
msg1: str # 응답메세지
|
|
61
|
+
output1: List[ResponseBodyOutput1] = field(default_factory=list) # 매매일자별 손익(배열)
|
|
62
|
+
output2: ResponseBodyOutput2 = field(default_factory=ResponseBodyOutput2) # 기간 합계(단일)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# ── 요청 함수 ───────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
def _fetch_page(
|
|
68
|
+
inqr_strt_dt: str,
|
|
69
|
+
inqr_end_dt: str,
|
|
70
|
+
pdno: str,
|
|
71
|
+
sort_dvsn: str,
|
|
72
|
+
ctx_area_fk100: str,
|
|
73
|
+
ctx_area_nk100: str,
|
|
74
|
+
tr_cont: str,
|
|
75
|
+
) -> tuple[ResponseBody, str, str, str]:
|
|
76
|
+
"""기간별손익 일별합산 내역을 한 페이지만 조회한다.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
inqr_strt_dt: 조회시작일자(INQR_STRT_DT), YYYYMMDD.
|
|
80
|
+
inqr_end_dt: 조회종료일자(INQR_END_DT), YYYYMMDD.
|
|
81
|
+
pdno: 상품번호(PDNO). 특정 종목만, 공란이면 전체.
|
|
82
|
+
sort_dvsn: 정렬구분(SORT_DVSN). 00/02 최근 순, 01 과거 순.
|
|
83
|
+
ctx_area_fk100: 연속조회검색조건100(CTX_AREA_FK100). 최초 조회는 "".
|
|
84
|
+
ctx_area_nk100: 연속조회키100(CTX_AREA_NK100). 최초 조회는 "".
|
|
85
|
+
tr_cont: 요청 헤더의 연속 거래 여부. 최초 조회는 "", 다음 페이지는 "N".
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
(이번 페이지 ResponseBody, 응답 헤더의 tr_cont, 다음 페이지용 ctx_area_fk100, ctx_area_nk100).
|
|
89
|
+
응답 tr_cont가 "F" 또는 "M"이면 다음 페이지가 더 있다는 뜻이고, "D"/"E"면 마지막 페이지다.
|
|
90
|
+
"""
|
|
91
|
+
token = load_token("real")
|
|
92
|
+
headers = {
|
|
93
|
+
"content-type": "application/json; charset=utf-8",
|
|
94
|
+
"authorization": f"Bearer {token}",
|
|
95
|
+
"appkey": REAL_APPKEY,
|
|
96
|
+
"appsecret": REAL_APP_SECRET,
|
|
97
|
+
"tr_id": _TR_ID,
|
|
98
|
+
"tr_cont": tr_cont,
|
|
99
|
+
"custtype": "P",
|
|
100
|
+
}
|
|
101
|
+
params = {
|
|
102
|
+
"ACNT_PRDT_CD": ACNT_PRDT_CD,
|
|
103
|
+
"CANO": CANO,
|
|
104
|
+
"INQR_STRT_DT": inqr_strt_dt,
|
|
105
|
+
"PDNO": pdno,
|
|
106
|
+
"CTX_AREA_NK100": ctx_area_nk100,
|
|
107
|
+
"INQR_END_DT": inqr_end_dt,
|
|
108
|
+
"SORT_DVSN": sort_dvsn,
|
|
109
|
+
"INQR_DVSN": "00",
|
|
110
|
+
"CBLC_DVSN": "00",
|
|
111
|
+
"CTX_AREA_FK100": ctx_area_fk100,
|
|
112
|
+
}
|
|
113
|
+
response = get_session().get(DOMAIN["real"] + URL, headers=headers, params=params, timeout=10)
|
|
114
|
+
raw = response.json()
|
|
115
|
+
|
|
116
|
+
# 에러 응답(rt_cd != "0")은 output1/output2 키가 없거나 null일 수 있어 안전 파싱.
|
|
117
|
+
fields1 = set(ResponseBodyOutput1.__dataclass_fields__)
|
|
118
|
+
raw_output1 = raw.get("output1") or []
|
|
119
|
+
output1 = [ResponseBodyOutput1(**{k: v for k, v in item.items() if k in fields1}) for item in raw_output1]
|
|
120
|
+
|
|
121
|
+
fields2 = set(ResponseBodyOutput2.__dataclass_fields__)
|
|
122
|
+
raw_output2 = raw.get("output2") or {}
|
|
123
|
+
output2 = ResponseBodyOutput2(**{k: v for k, v in raw_output2.items() if k in fields2})
|
|
124
|
+
|
|
125
|
+
body = ResponseBody(
|
|
126
|
+
rt_cd=raw.get("rt_cd", ""),
|
|
127
|
+
msg_cd=raw.get("msg_cd", ""),
|
|
128
|
+
msg1=raw.get("msg1", ""),
|
|
129
|
+
output1=output1,
|
|
130
|
+
output2=output2,
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
next_tr_cont = response.headers.get("tr_cont", "")
|
|
134
|
+
next_fk100 = raw.get("ctx_area_fk100", "") or ""
|
|
135
|
+
next_nk100 = raw.get("ctx_area_nk100", "") or ""
|
|
136
|
+
|
|
137
|
+
return body, next_tr_cont, next_fk100, next_nk100
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def inquire_period_profit(
|
|
141
|
+
inqr_strt_dt: str,
|
|
142
|
+
inqr_end_dt: str,
|
|
143
|
+
pdno: str = "",
|
|
144
|
+
sort_dvsn: Literal["00", "01", "02"] = "00",
|
|
145
|
+
) -> ResponseBody:
|
|
146
|
+
"""기간 내 매매일자별 손익 합산 내역을 전부 조회한다(연속조회 자동 처리). (모의투자 미지원, 실전 계좌 전용)
|
|
147
|
+
|
|
148
|
+
HTS(eFriend Plus) [0856] 기간별 매매손익 화면의 "일별" 탭과 동일한 데이터.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
inqr_strt_dt: 조회시작일자, YYYYMMDD.
|
|
152
|
+
inqr_end_dt: 조회종료일자, YYYYMMDD.
|
|
153
|
+
pdno: 특정 종목코드만 조회할 때 지정. 기본값 "" (전체).
|
|
154
|
+
sort_dvsn: 정렬구분. "00"/"02" 최근 순, "01" 과거 순. 기본값 "00".
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
rt_cd/msg_cd/msg1(마지막으로 받은 페이지 기준)과, 모든 페이지를 합친 매매일자별
|
|
158
|
+
손익(output1), 기간 합계(output2)를 담은 ResponseBody. 실패 여부는 반드시 rt_cd로 확인할 것.
|
|
159
|
+
(참고: 화면 우측 하단의 "총손익률"은 이 API가 아니라 기간별매매손익현황조회의
|
|
160
|
+
output2.tot_pftrt로 확인해야 한다.)
|
|
161
|
+
"""
|
|
162
|
+
ctx_area_fk100 = ""
|
|
163
|
+
ctx_area_nk100 = ""
|
|
164
|
+
tr_cont = "" # 공백: 최초 조회
|
|
165
|
+
|
|
166
|
+
all_output1: List[ResponseBodyOutput1] = []
|
|
167
|
+
body = ResponseBody(rt_cd="", msg_cd="", msg1="")
|
|
168
|
+
|
|
169
|
+
while True:
|
|
170
|
+
body, next_tr_cont, ctx_area_fk100, ctx_area_nk100 = _fetch_page(
|
|
171
|
+
inqr_strt_dt, inqr_end_dt, pdno, sort_dvsn,
|
|
172
|
+
ctx_area_fk100, ctx_area_nk100, tr_cont,
|
|
173
|
+
)
|
|
174
|
+
all_output1.extend(body.output1)
|
|
175
|
+
|
|
176
|
+
if body.rt_cd != "0" or next_tr_cont not in ("F", "M"):
|
|
177
|
+
break
|
|
178
|
+
tr_cont = "N" # 다음 페이지 조회
|
|
179
|
+
|
|
180
|
+
return ResponseBody(
|
|
181
|
+
rt_cd=body.rt_cd,
|
|
182
|
+
msg_cd=body.msg_cd,
|
|
183
|
+
msg1=body.msg1,
|
|
184
|
+
output1=all_output1,
|
|
185
|
+
output2=body.output2,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
# ── 실행 ────────────────────────────────────────────────────
|
|
190
|
+
|
|
191
|
+
if __name__ == "__main__":
|
|
192
|
+
result = inquire_period_profit(inqr_strt_dt="20260101", inqr_end_dt="20261231")
|
|
193
|
+
if result.rt_cd != "0":
|
|
194
|
+
print(f"조회 실패: {result.msg_cd} - {result.msg1}")
|
|
195
|
+
else:
|
|
196
|
+
for row in result.output1:
|
|
197
|
+
print(row)
|
|
198
|
+
print(result.output2)
|