@mulsok/traders-client 0.1.0
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.
- package/README.md +103 -0
- package/bin/cli.js +160 -0
- package/bin/postinstall.js +57 -0
- package/bin/preuninstall.js +36 -0
- package/dist/server/broker/kiwoom/cache.js +86 -0
- package/dist/server/broker/kiwoom/cache.js.map +1 -0
- package/dist/server/broker/kiwoom/client.js +256 -0
- package/dist/server/broker/kiwoom/client.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/_helpers.js +61 -0
- package/dist/server/broker/kiwoom/endpoints/_helpers.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/account.js +448 -0
- package/dist/server/broker/kiwoom/endpoints/account.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/detail.js +118 -0
- package/dist/server/broker/kiwoom/endpoints/detail.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/investor.js +139 -0
- package/dist/server/broker/kiwoom/endpoints/investor.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/order.js +134 -0
- package/dist/server/broker/kiwoom/endpoints/order.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/quote.js +165 -0
- package/dist/server/broker/kiwoom/endpoints/quote.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/ranking.js +180 -0
- package/dist/server/broker/kiwoom/endpoints/ranking.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/sector.js +135 -0
- package/dist/server/broker/kiwoom/endpoints/sector.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/theme.js +104 -0
- package/dist/server/broker/kiwoom/endpoints/theme.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/universe.js +119 -0
- package/dist/server/broker/kiwoom/endpoints/universe.js.map +1 -0
- package/dist/server/broker/kiwoom/index.js +59 -0
- package/dist/server/broker/kiwoom/index.js.map +1 -0
- package/dist/server/broker/kiwoom/order-tracker.js +353 -0
- package/dist/server/broker/kiwoom/order-tracker.js.map +1 -0
- package/dist/server/broker/kiwoom/price-feed.js +119 -0
- package/dist/server/broker/kiwoom/price-feed.js.map +1 -0
- package/dist/server/broker/kiwoom/rate-limiter.js +97 -0
- package/dist/server/broker/kiwoom/rate-limiter.js.map +1 -0
- package/dist/server/broker/kiwoom/types.js +13 -0
- package/dist/server/broker/kiwoom/types.js.map +1 -0
- package/dist/server/broker/kiwoom/ws/client.js +370 -0
- package/dist/server/broker/kiwoom/ws/client.js.map +1 -0
- package/dist/server/broker/kiwoom/ws/endpoints/condition.js +146 -0
- package/dist/server/broker/kiwoom/ws/endpoints/condition.js.map +1 -0
- package/dist/server/broker/kiwoom/ws/realtime-bus.js +42 -0
- package/dist/server/broker/kiwoom/ws/realtime-bus.js.map +1 -0
- package/dist/server/broker/kiwoom/ws/types.js +19 -0
- package/dist/server/broker/kiwoom/ws/types.js.map +1 -0
- package/dist/server/broker/news.js +34 -0
- package/dist/server/broker/news.js.map +1 -0
- package/dist/server/bundle.js +43 -0
- package/dist/server/bundle.js.map +1 -0
- package/dist/server/calendar/krx-holidays.js +162 -0
- package/dist/server/calendar/krx-holidays.js.map +1 -0
- package/dist/server/config.js +263 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/db/sqlite.js +252 -0
- package/dist/server/db/sqlite.js.map +1 -0
- package/dist/server/diary/writer.js +266 -0
- package/dist/server/diary/writer.js.map +1 -0
- package/dist/server/index.js +316 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/jobs/universe-sync.js +132 -0
- package/dist/server/jobs/universe-sync.js.map +1 -0
- package/dist/server/jobs/watchdog.js +87 -0
- package/dist/server/jobs/watchdog.js.map +1 -0
- package/dist/server/journal/pnl-stats.js +108 -0
- package/dist/server/journal/pnl-stats.js.map +1 -0
- package/dist/server/journal/telemetry.js +174 -0
- package/dist/server/journal/telemetry.js.map +1 -0
- package/dist/server/journal/trade-journal.js +239 -0
- package/dist/server/journal/trade-journal.js.map +1 -0
- package/dist/server/llm/anthropic.js +98 -0
- package/dist/server/llm/anthropic.js.map +1 -0
- package/dist/server/llm/claude-cli.js +204 -0
- package/dist/server/llm/claude-cli.js.map +1 -0
- package/dist/server/llm/context-builder.js +229 -0
- package/dist/server/llm/context-builder.js.map +1 -0
- package/dist/server/llm/gemini.js +86 -0
- package/dist/server/llm/gemini.js.map +1 -0
- package/dist/server/llm/index.js +36 -0
- package/dist/server/llm/index.js.map +1 -0
- package/dist/server/llm/openai.js +87 -0
- package/dist/server/llm/openai.js.map +1 -0
- package/dist/server/personas/executor.js +318 -0
- package/dist/server/personas/executor.js.map +1 -0
- package/dist/server/personas/loader.js +165 -0
- package/dist/server/personas/loader.js.map +1 -0
- package/dist/server/personas/persona-agent.js +386 -0
- package/dist/server/personas/persona-agent.js.map +1 -0
- package/dist/server/personas/persona-state.js +170 -0
- package/dist/server/personas/persona-state.js.map +1 -0
- package/dist/server/personas/runner.js +162 -0
- package/dist/server/personas/runner.js.map +1 -0
- package/dist/server/personas/schema.js +123 -0
- package/dist/server/personas/schema.js.map +1 -0
- package/dist/server/personas/wake-plan.js +313 -0
- package/dist/server/personas/wake-plan.js.map +1 -0
- package/dist/server/readiness.js +414 -0
- package/dist/server/readiness.js.map +1 -0
- package/dist/server/routes.js +1216 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/safety.js +153 -0
- package/dist/server/safety.js.map +1 -0
- package/dist/server/screener/engine.js +856 -0
- package/dist/server/screener/engine.js.map +1 -0
- package/dist/server/server-sync.js +427 -0
- package/dist/server/server-sync.js.map +1 -0
- package/dist/server/signing.js +39 -0
- package/dist/server/signing.js.map +1 -0
- package/dist/server/watchers/condition-watcher.js +519 -0
- package/dist/server/watchers/condition-watcher.js.map +1 -0
- package/dist/server/watchers/types.js +16 -0
- package/dist/server/watchers/types.js.map +1 -0
- package/dist/web/assets/index-62SMpbaf.js +79 -0
- package/dist/web/assets/index-BPLQR0wt.css +1 -0
- package/dist/web/index.html +14 -0
- package/package.json +93 -0
- package/scripts/com.mulsok.traders.client.plist.template +58 -0
- package/scripts/install-daemon.sh +156 -0
- package/scripts/uninstall-daemon.sh +62 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 키움 기관/외국인 endpoints
|
|
3
|
+
*
|
|
4
|
+
* - getForeignActivity(symbolCode) · 주식외국인 종목별 매매동향 (ka10008)
|
|
5
|
+
* - getInstitutionActivity(symbolCode) · 주식기관 요청 (ka10009)
|
|
6
|
+
* - getContinuousInvestorActivity(...) · 기관외국인 연속매매 현황 (ka10131)
|
|
7
|
+
*
|
|
8
|
+
* 공식 스펙: PDF p.39 · p.42 · p.238
|
|
9
|
+
* 모두 POST /api/dostk/frgnistt
|
|
10
|
+
*
|
|
11
|
+
* 배경:
|
|
12
|
+
* - 이전 구현에서 quote.ts 의 getInvestorActivity() 가 ka10101 (업종코드 리스트)
|
|
13
|
+
* 을 잘못 호출하던 버그 수정.
|
|
14
|
+
*/
|
|
15
|
+
import { kiwoomCall } from "../client.js";
|
|
16
|
+
import { cacheOrFetch, TTL } from "../cache.js";
|
|
17
|
+
export async function getForeignActivity(symbolCode, limit = 20) {
|
|
18
|
+
const cached = await cacheOrFetch(`investor:foreign:${symbolCode}:${limit}`, TTL.DAILY_CHART, // 60초
|
|
19
|
+
() => kiwoomCall({
|
|
20
|
+
path: "/api/dostk/frgnistt",
|
|
21
|
+
trId: "ka10008",
|
|
22
|
+
body: { stk_cd: symbolCode },
|
|
23
|
+
}));
|
|
24
|
+
if (!cached.value.ok)
|
|
25
|
+
return cached.value;
|
|
26
|
+
const arr = cached.value.data.stk_frgnr ?? [];
|
|
27
|
+
const items = arr.slice(0, limit).map((row) => ({
|
|
28
|
+
date: String(row.dt ?? ""),
|
|
29
|
+
closeKrw: Math.abs(toNum(row.close_pric)),
|
|
30
|
+
changeKrw: toNum(row.pred_pre),
|
|
31
|
+
volume: toNum(row.trde_qty),
|
|
32
|
+
changeQty: toNum(row.chg_qty),
|
|
33
|
+
possessedQty: toNum(row.poss_stkcnt),
|
|
34
|
+
weightPct: toNum(row.wght),
|
|
35
|
+
gainablePossQty: toNum(row.gain_pos_stkcnt),
|
|
36
|
+
foreignLimit: toNum(row.frgnr_limit),
|
|
37
|
+
foreignLimitChange: toNum(row.frgnr_limit_irds),
|
|
38
|
+
limitExhaustionPct: toNum(row.limit_exh_rt),
|
|
39
|
+
raw: row,
|
|
40
|
+
}));
|
|
41
|
+
return {
|
|
42
|
+
ok: true,
|
|
43
|
+
data: items,
|
|
44
|
+
meta: { ...cached.value.meta, cached: cached.cached },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export async function getInstitutionActivity(symbolCode) {
|
|
48
|
+
const cached = await cacheOrFetch(`investor:institution:${symbolCode}`, TTL.DAILY_CHART, () => kiwoomCall({
|
|
49
|
+
path: "/api/dostk/frgnistt",
|
|
50
|
+
trId: "ka10009",
|
|
51
|
+
body: { stk_cd: symbolCode },
|
|
52
|
+
}));
|
|
53
|
+
if (!cached.value.ok)
|
|
54
|
+
return cached.value;
|
|
55
|
+
const raw = cached.value.data;
|
|
56
|
+
return {
|
|
57
|
+
ok: true,
|
|
58
|
+
data: {
|
|
59
|
+
symbolCode,
|
|
60
|
+
date: String(raw.date ?? ""),
|
|
61
|
+
closeKrw: Math.abs(toNum(raw.close_pric)),
|
|
62
|
+
changeKrw: toNum(raw.pre),
|
|
63
|
+
institutionCumulative: toNum(raw.orgn_dt_acc),
|
|
64
|
+
institutionDailyNet: toNum(raw.orgn_daly_nettrde),
|
|
65
|
+
foreignDailyNet: toNum(raw.frgnr_daly_nettrde),
|
|
66
|
+
foreignShareholdingPct: toNum(raw.frgnr_qota_rt),
|
|
67
|
+
raw,
|
|
68
|
+
},
|
|
69
|
+
meta: { ...cached.value.meta, cached: cached.cached },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const PERIOD_MAP = {
|
|
73
|
+
latest: "1",
|
|
74
|
+
d3: "3",
|
|
75
|
+
d5: "5",
|
|
76
|
+
d10: "10",
|
|
77
|
+
d20: "20",
|
|
78
|
+
d120: "120",
|
|
79
|
+
custom: "0",
|
|
80
|
+
};
|
|
81
|
+
export async function getContinuousInvestorActivity(opts = {}) {
|
|
82
|
+
const period = opts.period ?? "latest";
|
|
83
|
+
const market = opts.market ?? "kospi";
|
|
84
|
+
const amountOrQty = opts.amountOrQty ?? "amount";
|
|
85
|
+
const cacheKey = `investor:continuous:${period}:${market}:${amountOrQty}:${opts.startDate ?? ""}:${opts.endDate ?? ""}`;
|
|
86
|
+
const cached = await cacheOrFetch(cacheKey, TTL.RANKING, // 30s
|
|
87
|
+
() => kiwoomCall({
|
|
88
|
+
path: "/api/dostk/frgnistt",
|
|
89
|
+
trId: "ka10131",
|
|
90
|
+
body: {
|
|
91
|
+
dt: PERIOD_MAP[period],
|
|
92
|
+
strt_dt: opts.startDate ?? "",
|
|
93
|
+
end_dt: opts.endDate ?? "",
|
|
94
|
+
mrkt_tp: market === "kospi" ? "001" : "101",
|
|
95
|
+
netslmt_tp: "2", // 순매수(고정)
|
|
96
|
+
stk_inds_tp: "0", // 0:종목(주식)
|
|
97
|
+
amt_qty_tp: amountOrQty === "amount" ? "0" : "1",
|
|
98
|
+
stex_tp: "1", // KRX
|
|
99
|
+
},
|
|
100
|
+
}));
|
|
101
|
+
if (!cached.value.ok)
|
|
102
|
+
return cached.value;
|
|
103
|
+
const raw = cached.value.data;
|
|
104
|
+
const arr = raw.orgn_frgnr_cont_trde_prst ?? [];
|
|
105
|
+
const items = arr.map((row) => ({
|
|
106
|
+
rank: toNum(row.rank),
|
|
107
|
+
symbolCode: String(row.stk_cd ?? ""),
|
|
108
|
+
symbolName: String(row.stk_nm ?? ""),
|
|
109
|
+
periodChangePct: toNum(row.prid_stkpc_flu_rt),
|
|
110
|
+
institutionNetAmount: toNum(row.orgn_nettrde_amt),
|
|
111
|
+
institutionNetQty: toNum(row.orgn_nettrde_qty),
|
|
112
|
+
institutionContinuousDays: toNum(row.orgn_cont_netprps_dys),
|
|
113
|
+
institutionContinuousQty: toNum(row.orgn_cont_netprps_qty),
|
|
114
|
+
institutionContinuousAmount: toNum(row.orgn_cont_netprps_amt),
|
|
115
|
+
foreignNetQty: toNum(row.frgnr_nettrde_qty),
|
|
116
|
+
foreignNetAmount: toNum(row.frgnr_nettrde_amt),
|
|
117
|
+
foreignContinuousDays: toNum(row.frgnr_cont_netprps_dys),
|
|
118
|
+
foreignContinuousQty: toNum(row.frgnr_cont_netprps_qty),
|
|
119
|
+
foreignContinuousAmount: toNum(row.frgnr_cont_netprps_amt),
|
|
120
|
+
totalNetQty: toNum(row.nettrde_qty),
|
|
121
|
+
totalNetAmount: toNum(row.nettrde_amt),
|
|
122
|
+
raw: row,
|
|
123
|
+
}));
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
data: items,
|
|
127
|
+
meta: { ...cached.value.meta, cached: cached.cached },
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function toNum(v) {
|
|
131
|
+
if (typeof v === "number")
|
|
132
|
+
return v;
|
|
133
|
+
if (typeof v === "string") {
|
|
134
|
+
const n = Number(v.replace(/,/g, "").replace(/^\+/, ""));
|
|
135
|
+
return Number.isFinite(n) ? n : 0;
|
|
136
|
+
}
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=investor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"investor.js","sourceRoot":"","sources":["../../../../../src-server/broker/kiwoom/endpoints/investor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 키움 주문 endpoints
|
|
3
|
+
*
|
|
4
|
+
* - placeOrder() · 매수 (kt10000) · 매도 (kt10001)
|
|
5
|
+
* - modifyOrder() · 정정 (kt10002) · Phase 2 stub
|
|
6
|
+
* - cancelOrder() · 취소 (kt10003) · Phase 2 stub
|
|
7
|
+
*
|
|
8
|
+
* 안전장치
|
|
9
|
+
* - 주문 결과는 캐시하지 않음 (매번 호출)
|
|
10
|
+
* - 주문 후 cache.cacheDeletePrefix("account:") 로 잔고/예수금 캐시 무효화
|
|
11
|
+
* - 모의/실전 분기는 client.ts host 자동 처리
|
|
12
|
+
* - 비즈니스 에러 (잔고 부족 · 호가 단위 위반 등) 는 KiwoomResult.error 로 전달
|
|
13
|
+
*/
|
|
14
|
+
import { kiwoomCall } from "../client.js";
|
|
15
|
+
import { cacheDeletePrefix } from "../cache.js";
|
|
16
|
+
import { pickNum, pickStr } from "./_helpers.js";
|
|
17
|
+
export async function placeOrder(input) {
|
|
18
|
+
// 입력 검증
|
|
19
|
+
if (!input.symbolCode || input.quantity <= 0) {
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
error: { kind: "schema", message: "symbolCode 또는 quantity 잘못됨" },
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const type = input.type ?? "limit";
|
|
26
|
+
if (type === "limit" && (!input.priceKrw || input.priceKrw <= 0)) {
|
|
27
|
+
return {
|
|
28
|
+
ok: false,
|
|
29
|
+
error: { kind: "schema", message: "limit 주문은 priceKrw 필수" },
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const trId = input.side === "buy" ? "kt10000" : "kt10001";
|
|
33
|
+
const path = input.side === "buy" ? "/api/dostk/ordr" : "/api/dostk/ordr";
|
|
34
|
+
const body = {
|
|
35
|
+
dmst_stex_tp: input.exchange ?? "KRX",
|
|
36
|
+
stk_cd: input.symbolCode,
|
|
37
|
+
ord_qty: String(input.quantity),
|
|
38
|
+
ord_uv: type === "market" ? "" : String(input.priceKrw ?? ""),
|
|
39
|
+
trde_tp: orderTypeCode(type),
|
|
40
|
+
cond_uv: "", // 조건 단가 (미사용)
|
|
41
|
+
};
|
|
42
|
+
const res = await kiwoomCall({
|
|
43
|
+
path,
|
|
44
|
+
trId,
|
|
45
|
+
body,
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok)
|
|
48
|
+
return res;
|
|
49
|
+
// 잔고/예수금 캐시 무효화 (다음 조회 시 신선)
|
|
50
|
+
cacheDeletePrefix("account:");
|
|
51
|
+
const raw = res.data;
|
|
52
|
+
const result = {
|
|
53
|
+
orderNo: pickStr(raw, ["ord_no", "odno", "order_no"]),
|
|
54
|
+
status: "accepted", // kiwoomCall 에서 return_code === 0 통과한 상태
|
|
55
|
+
acceptedQty: pickNum(raw, ["ord_qty", "accepted_qty"]) || input.quantity,
|
|
56
|
+
message: pickStr(raw, ["return_msg", "msg"]) || "주문 접수",
|
|
57
|
+
raw,
|
|
58
|
+
};
|
|
59
|
+
return { ok: true, data: result, meta: res.meta };
|
|
60
|
+
}
|
|
61
|
+
export async function modifyOrder(input) {
|
|
62
|
+
if (!input.orderNo || input.newQuantity <= 0 || input.newPriceKrw <= 0) {
|
|
63
|
+
return { ok: false, error: { kind: "schema", message: "정정 주문 입력 누락" } };
|
|
64
|
+
}
|
|
65
|
+
const body = {
|
|
66
|
+
dmst_stex_tp: input.exchange ?? "KRX",
|
|
67
|
+
orig_ord_no: input.orderNo,
|
|
68
|
+
stk_cd: input.symbolCode,
|
|
69
|
+
mdfy_qty: String(input.newQuantity),
|
|
70
|
+
mdfy_uv: String(input.newPriceKrw),
|
|
71
|
+
mdfy_cond_uv: "",
|
|
72
|
+
};
|
|
73
|
+
const res = await kiwoomCall({
|
|
74
|
+
path: "/api/dostk/ordr",
|
|
75
|
+
trId: "kt10002",
|
|
76
|
+
body,
|
|
77
|
+
});
|
|
78
|
+
if (!res.ok)
|
|
79
|
+
return res;
|
|
80
|
+
cacheDeletePrefix("account:");
|
|
81
|
+
const raw = res.data;
|
|
82
|
+
return {
|
|
83
|
+
ok: true,
|
|
84
|
+
data: {
|
|
85
|
+
orderNo: pickStr(raw, ["ord_no", "odno"]) || input.orderNo,
|
|
86
|
+
status: "accepted",
|
|
87
|
+
acceptedQty: input.newQuantity,
|
|
88
|
+
message: pickStr(raw, ["return_msg", "msg"]) || "정정 접수",
|
|
89
|
+
raw,
|
|
90
|
+
},
|
|
91
|
+
meta: res.meta,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export async function cancelOrder(input) {
|
|
95
|
+
if (!input.orderNo) {
|
|
96
|
+
return { ok: false, error: { kind: "schema", message: "orderNo 필수" } };
|
|
97
|
+
}
|
|
98
|
+
const body = {
|
|
99
|
+
dmst_stex_tp: input.exchange ?? "KRX",
|
|
100
|
+
orig_ord_no: input.orderNo,
|
|
101
|
+
stk_cd: input.symbolCode,
|
|
102
|
+
cncl_qty: String(input.quantity ?? 0), // 0 = 전량
|
|
103
|
+
};
|
|
104
|
+
const res = await kiwoomCall({
|
|
105
|
+
path: "/api/dostk/ordr",
|
|
106
|
+
trId: "kt10003",
|
|
107
|
+
body,
|
|
108
|
+
});
|
|
109
|
+
if (!res.ok)
|
|
110
|
+
return res;
|
|
111
|
+
cacheDeletePrefix("account:");
|
|
112
|
+
const raw = res.data;
|
|
113
|
+
return {
|
|
114
|
+
ok: true,
|
|
115
|
+
data: {
|
|
116
|
+
orderNo: pickStr(raw, ["ord_no", "odno"]) || input.orderNo,
|
|
117
|
+
status: "accepted",
|
|
118
|
+
acceptedQty: input.quantity ?? 0,
|
|
119
|
+
message: pickStr(raw, ["return_msg", "msg"]) || "취소 접수",
|
|
120
|
+
raw,
|
|
121
|
+
},
|
|
122
|
+
meta: res.meta,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/* ─────────── helpers ─────────── */
|
|
126
|
+
function orderTypeCode(type) {
|
|
127
|
+
switch (type) {
|
|
128
|
+
case "limit": return "0"; // 보통가
|
|
129
|
+
case "market": return "3"; // 시장가
|
|
130
|
+
case "best_limit": return "5"; // 최유리
|
|
131
|
+
default: return "0";
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"order.js","sourceRoot":"","sources":["../../../../../src-server/broker/kiwoom/endpoints/order.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 키움 시세 endpoints
|
|
3
|
+
*
|
|
4
|
+
* 구현:
|
|
5
|
+
* - getCurrentPrice(symbolCode) · 주식 기본정보 (ka10001)
|
|
6
|
+
* - getOrderbook(symbolCode) · 호가 (ka10004)
|
|
7
|
+
* - getDailyChart(symbolCode, days?) · 일봉 (ka10005)
|
|
8
|
+
* - getMinuteChart(symbolCode, intervalMin, count?) · 분봉 (ka10006)
|
|
9
|
+
*
|
|
10
|
+
* 캐시 정책 (cache.ts TTL):
|
|
11
|
+
* - 현재가 1s · 호가 1s · 분봉 10s · 일봉 60s
|
|
12
|
+
*/
|
|
13
|
+
import { kiwoomCall } from "../client.js";
|
|
14
|
+
import { cacheOrFetch, TTL } from "../cache.js";
|
|
15
|
+
import { findFirstArray, pickNum, pickStr, ymd } from "./_helpers.js";
|
|
16
|
+
export async function getCurrentPrice(symbolCode) {
|
|
17
|
+
const cached = await cacheOrFetch(`quote:current:${symbolCode}`, TTL.CURRENT_PRICE, () => kiwoomCall({
|
|
18
|
+
path: "/api/dostk/stkinfo",
|
|
19
|
+
trId: "ka10001",
|
|
20
|
+
body: { stk_cd: symbolCode },
|
|
21
|
+
}));
|
|
22
|
+
if (!cached.value.ok)
|
|
23
|
+
return cached.value;
|
|
24
|
+
const raw = cached.value.data;
|
|
25
|
+
const data = {
|
|
26
|
+
symbolCode,
|
|
27
|
+
symbolName: pickStr(raw, ["stk_nm", "hts_kor_isnm"]),
|
|
28
|
+
currentPriceKrw: pickNum(raw, ["cur_prc", "stck_prpr"]),
|
|
29
|
+
changeKrw: pickNum(raw, ["pred_pre", "prdy_vrss"]),
|
|
30
|
+
changePct: pickNum(raw, ["flu_rt", "prdy_ctrt"]),
|
|
31
|
+
openKrw: pickNum(raw, ["open_pric", "stck_oprc"]),
|
|
32
|
+
highKrw: pickNum(raw, ["high_pric", "stck_hgpr"]),
|
|
33
|
+
lowKrw: pickNum(raw, ["low_pric", "stck_lwpr"]),
|
|
34
|
+
prevCloseKrw: pickNum(raw, ["pred_close_pric", "stck_sdpr"]),
|
|
35
|
+
volume: pickNum(raw, ["trde_qty", "acml_vol"]),
|
|
36
|
+
tradingValueKrw: pickNum(raw, ["trde_amt", "acml_tr_pbmn"]),
|
|
37
|
+
raw,
|
|
38
|
+
};
|
|
39
|
+
return { ok: true, data, meta: { ...cached.value.meta, cached: cached.cached } };
|
|
40
|
+
}
|
|
41
|
+
export async function getStockMeta(symbolCode) {
|
|
42
|
+
const cached = await cacheOrFetch(`quote:meta:${symbolCode}`, TTL.STOCK_INFO, // 1일
|
|
43
|
+
() => kiwoomCall({
|
|
44
|
+
path: "/api/dostk/stkinfo",
|
|
45
|
+
trId: "ka10100",
|
|
46
|
+
body: { stk_cd: symbolCode },
|
|
47
|
+
}));
|
|
48
|
+
if (!cached.value.ok)
|
|
49
|
+
return cached.value;
|
|
50
|
+
const raw = cached.value.data;
|
|
51
|
+
// 관리종목 등 flag 매핑 · 키움 응답 키 추정 (실 응답으로 정정)
|
|
52
|
+
const adminFlag = pickStr(raw, ["mng_stk_yn", "mngs_yn"]);
|
|
53
|
+
const warningFlag = pickStr(raw, ["caut_stk_yn", "ivst_warn_yn"]);
|
|
54
|
+
const haltedFlag = pickStr(raw, ["trading_stop_yn", "halt_yn"]);
|
|
55
|
+
const marketRaw = pickStr(raw, ["mrkt_cd", "market_kor"]);
|
|
56
|
+
const productType = pickStr(raw, ["prdt_tp", "stk_kind"]);
|
|
57
|
+
const data = {
|
|
58
|
+
symbolCode,
|
|
59
|
+
symbolName: pickStr(raw, ["stk_nm", "hts_kor_isnm"]),
|
|
60
|
+
market: marketRaw.includes("KOSPI") || marketRaw === "001"
|
|
61
|
+
? "KOSPI"
|
|
62
|
+
: marketRaw.includes("KOSDAQ") || marketRaw === "101"
|
|
63
|
+
? "KOSDAQ"
|
|
64
|
+
: marketRaw.includes("KONEX")
|
|
65
|
+
? "KONEX"
|
|
66
|
+
: productType === "ETF"
|
|
67
|
+
? "ETF"
|
|
68
|
+
: "UNKNOWN",
|
|
69
|
+
isAdministrative: adminFlag === "Y" || adminFlag === "1",
|
|
70
|
+
isWarning: warningFlag === "Y" || warningFlag === "1",
|
|
71
|
+
isHalted: haltedFlag === "Y" || haltedFlag === "1",
|
|
72
|
+
isEtf: productType === "ETF",
|
|
73
|
+
raw,
|
|
74
|
+
};
|
|
75
|
+
return { ok: true, data, meta: { ...cached.value.meta, cached: cached.cached } };
|
|
76
|
+
}
|
|
77
|
+
export async function getOrderbook(symbolCode) {
|
|
78
|
+
const cached = await cacheOrFetch(`quote:orderbook:${symbolCode}`, TTL.ORDERBOOK, () => kiwoomCall({
|
|
79
|
+
path: "/api/dostk/mrkcond",
|
|
80
|
+
trId: "ka10004",
|
|
81
|
+
body: { stk_cd: symbolCode },
|
|
82
|
+
}));
|
|
83
|
+
if (!cached.value.ok)
|
|
84
|
+
return cached.value;
|
|
85
|
+
const raw = cached.value.data;
|
|
86
|
+
const asks = [];
|
|
87
|
+
const bids = [];
|
|
88
|
+
for (let i = 1; i <= 10; i++) {
|
|
89
|
+
const aPrice = pickNum(raw, [`sel_${i}p`, `askp${i}`]);
|
|
90
|
+
const aQty = pickNum(raw, [`sel_${i}q`, `askp_rsqn${i}`]);
|
|
91
|
+
if (aPrice > 0)
|
|
92
|
+
asks.push({ priceKrw: aPrice, quantity: aQty });
|
|
93
|
+
const bPrice = pickNum(raw, [`buy_${i}p`, `bidp${i}`]);
|
|
94
|
+
const bQty = pickNum(raw, [`buy_${i}q`, `bidp_rsqn${i}`]);
|
|
95
|
+
if (bPrice > 0)
|
|
96
|
+
bids.push({ priceKrw: bPrice, quantity: bQty });
|
|
97
|
+
}
|
|
98
|
+
const data = {
|
|
99
|
+
symbolCode,
|
|
100
|
+
asks,
|
|
101
|
+
bids,
|
|
102
|
+
totalAskQty: asks.reduce((s, l) => s + l.quantity, 0),
|
|
103
|
+
totalBidQty: bids.reduce((s, l) => s + l.quantity, 0),
|
|
104
|
+
raw,
|
|
105
|
+
};
|
|
106
|
+
return { ok: true, data, meta: { ...cached.value.meta, cached: cached.cached } };
|
|
107
|
+
}
|
|
108
|
+
export async function getDailyChart(symbolCode, days = 60) {
|
|
109
|
+
const cached = await cacheOrFetch(`quote:daily:${symbolCode}:${days}`, TTL.DAILY_CHART, () => kiwoomCall({
|
|
110
|
+
path: "/api/dostk/chart",
|
|
111
|
+
trId: "ka10081", // 정정: 주식 일봉 차트 (구 ka10005 추정 오류)
|
|
112
|
+
body: {
|
|
113
|
+
stk_cd: symbolCode,
|
|
114
|
+
base_dt: ymd(new Date()),
|
|
115
|
+
upd_stkpc_tp: "1", // 1: 수정주가 사용
|
|
116
|
+
},
|
|
117
|
+
}));
|
|
118
|
+
if (!cached.value.ok)
|
|
119
|
+
return cached.value;
|
|
120
|
+
const raw = cached.value.data;
|
|
121
|
+
const arr = findFirstArray(raw) ?? [];
|
|
122
|
+
const candles = arr.slice(0, days).map((row) => ({
|
|
123
|
+
date: pickStr(row, ["dt", "stck_bsop_date"]),
|
|
124
|
+
openKrw: pickNum(row, ["open_pric", "stck_oprc"]),
|
|
125
|
+
highKrw: pickNum(row, ["high_pric", "stck_hgpr"]),
|
|
126
|
+
lowKrw: pickNum(row, ["low_pric", "stck_lwpr"]),
|
|
127
|
+
closeKrw: pickNum(row, ["cur_prc", "close_pric", "stck_clpr"]),
|
|
128
|
+
volume: pickNum(row, ["trde_qty", "acml_vol"]),
|
|
129
|
+
}));
|
|
130
|
+
return {
|
|
131
|
+
ok: true,
|
|
132
|
+
data: { symbolCode, candles },
|
|
133
|
+
meta: { ...cached.value.meta, cached: cached.cached },
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
export async function getMinuteChart(symbolCode, intervalMin = 5, count = 100) {
|
|
137
|
+
const cached = await cacheOrFetch(`quote:minute:${symbolCode}:${intervalMin}:${count}`, TTL.MINUTE_CHART, () => kiwoomCall({
|
|
138
|
+
path: "/api/dostk/chart",
|
|
139
|
+
trId: "ka10080", // 정정: 주식 분봉 차트 (구 ka10006 추정 오류)
|
|
140
|
+
body: {
|
|
141
|
+
stk_cd: symbolCode,
|
|
142
|
+
tic_scope: String(intervalMin),
|
|
143
|
+
upd_stkpc_tp: "1",
|
|
144
|
+
},
|
|
145
|
+
}));
|
|
146
|
+
if (!cached.value.ok)
|
|
147
|
+
return cached.value;
|
|
148
|
+
const raw = cached.value.data;
|
|
149
|
+
const arr = findFirstArray(raw) ?? [];
|
|
150
|
+
const candles = arr.slice(0, count).map((row) => ({
|
|
151
|
+
date: pickStr(row, ["cntr_tm", "stck_bsop_date"]),
|
|
152
|
+
openKrw: pickNum(row, ["open_pric", "stck_oprc"]),
|
|
153
|
+
highKrw: pickNum(row, ["high_pric", "stck_hgpr"]),
|
|
154
|
+
lowKrw: pickNum(row, ["low_pric", "stck_lwpr"]),
|
|
155
|
+
closeKrw: pickNum(row, ["cur_prc", "close_pric", "stck_clpr"]),
|
|
156
|
+
volume: pickNum(row, ["trde_qty", "cntr_qty"]),
|
|
157
|
+
}));
|
|
158
|
+
return {
|
|
159
|
+
ok: true,
|
|
160
|
+
data: { symbolCode, intervalMin, candles },
|
|
161
|
+
meta: { ...cached.value.meta, cached: cached.cached },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// helpers: ./_helpers.ts (findFirstArray · pickNum · pickStr · ymd)
|
|
165
|
+
//# sourceMappingURL=quote.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quote.js","sourceRoot":"","sources":["../../../../../src-server/broker/kiwoom/endpoints/quote.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 키움 랭킹 endpoints (Phase 2 · 종목 발굴)
|
|
3
|
+
*
|
|
4
|
+
* - getVolumeRanking() · 당일 거래량 상위 (ka10030)
|
|
5
|
+
* - getChangeRanking() · 전일대비 등락률 상위 (ka10027)
|
|
6
|
+
*
|
|
7
|
+
* Reference: KiwoomRestApi.Net (github.com/dongbin300/KiwoomRestApi.Net)
|
|
8
|
+
* - path: /api/dostk/rkinfo
|
|
9
|
+
* - 모든 enum 값 검증된 spec
|
|
10
|
+
*/
|
|
11
|
+
import { kiwoomCall } from "../client.js";
|
|
12
|
+
import { cacheOrFetch, TTL } from "../cache.js";
|
|
13
|
+
import { findFirstArray, pickNum, pickStr } from "./_helpers.js";
|
|
14
|
+
/* enum codes (검증된 키움 spec)
|
|
15
|
+
* mrkt_tp: 0=전체 · 1=코스피 · 101=코스닥
|
|
16
|
+
* stex_tp: 1=KRX · 2=NXT · 3=통합
|
|
17
|
+
* stk_cnd: 0=전체 · 1=관리종목제외 · 14=ETF제외 · 16=ETF+ETN제외 · 20=ETF+ETN+스팩제외
|
|
18
|
+
* crd_cnd: 0=전체 · 9=신용융자전체
|
|
19
|
+
* pric_cnd: 0=전체 · 5=1만원이상 · 8=1천원이상 · 10=1만원미만
|
|
20
|
+
* trde_qty_cnd: 천주 단위 (50 = 5만주 이상)
|
|
21
|
+
* trde_prica_cnd: 천만원 단위 (10 = 1억원 이상)
|
|
22
|
+
*/
|
|
23
|
+
function marketCode(m) {
|
|
24
|
+
return m === "kospi" ? "1" : m === "kosdaq" ? "101" : "0";
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 당일 거래량 상위 (ka10030)
|
|
28
|
+
*
|
|
29
|
+
* @param market all/kospi/kosdaq · 기본 all
|
|
30
|
+
* @param limit 상위 N · 기본 50 (응답 max ~100)
|
|
31
|
+
*/
|
|
32
|
+
export async function getVolumeRanking(market = "all", limit = 50) {
|
|
33
|
+
const cached = await cacheOrFetch(`ranking:volume:${market}:${limit}`, TTL.RANKING, () => kiwoomCall({
|
|
34
|
+
path: "/api/dostk/rkinfo",
|
|
35
|
+
trId: "ka10030",
|
|
36
|
+
body: {
|
|
37
|
+
mrkt_tp: marketCode(market),
|
|
38
|
+
sort_tp: "1", // 1=거래량 · 2=거래회전율 · 3=거래대금
|
|
39
|
+
mang_stk_incls: "1", // 1=관리종목 제외
|
|
40
|
+
crd_tp: "0", // 0=전체
|
|
41
|
+
trde_qty_tp: "5", // 5=5만주 이상
|
|
42
|
+
pric_tp: "0", // 0=전체 (가격 무관)
|
|
43
|
+
trde_prica_tp: "0", // 0=전체 (거래대금 무관)
|
|
44
|
+
mrkt_open_tp: "0", // 0=전체
|
|
45
|
+
stex_tp: "1", // 1=KRX
|
|
46
|
+
},
|
|
47
|
+
}));
|
|
48
|
+
if (!cached.value.ok)
|
|
49
|
+
return cached.value;
|
|
50
|
+
const items = parseRanking(cached.value.data, limit);
|
|
51
|
+
return { ok: true, data: items, meta: { ...cached.value.meta, cached: cached.cached } };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 전일대비 등락률 상위 (ka10027)
|
|
55
|
+
*
|
|
56
|
+
* @param direction "up" 상승률 · "down" 하락률
|
|
57
|
+
*/
|
|
58
|
+
export async function getChangeRanking(direction = "up", market = "all", limit = 50) {
|
|
59
|
+
const cached = await cacheOrFetch(`ranking:change:${direction}:${market}:${limit}`, TTL.RANKING, () => kiwoomCall({
|
|
60
|
+
path: "/api/dostk/rkinfo",
|
|
61
|
+
trId: "ka10027",
|
|
62
|
+
body: {
|
|
63
|
+
mrkt_tp: marketCode(market),
|
|
64
|
+
sort_tp: direction === "up" ? "1" : "3", // 1=상승률 · 3=하락률
|
|
65
|
+
trde_qty_cnd: "10", // 10=1만주 이상
|
|
66
|
+
stk_cnd: "1", // 1=관리종목 제외
|
|
67
|
+
crd_cnd: "0", // 0=전체
|
|
68
|
+
updown_incls: "1", // 1=상한/하한 포함
|
|
69
|
+
pric_cnd: "8", // 8=1천원이상
|
|
70
|
+
trde_prica_cnd: "0", // 0=전체
|
|
71
|
+
stex_tp: "1", // 1=KRX
|
|
72
|
+
},
|
|
73
|
+
}));
|
|
74
|
+
if (!cached.value.ok)
|
|
75
|
+
return cached.value;
|
|
76
|
+
const items = parseRanking(cached.value.data, limit);
|
|
77
|
+
return { ok: true, data: items, meta: { ...cached.value.meta, cached: cached.cached } };
|
|
78
|
+
}
|
|
79
|
+
function parseRanking(raw, limit) {
|
|
80
|
+
const arr = findFirstArray(raw) ?? [];
|
|
81
|
+
return arr.slice(0, limit).map((row, i) => ({
|
|
82
|
+
rank: pickNum(row, ["rank", "rnk"]) || i + 1,
|
|
83
|
+
symbolCode: pickStr(row, ["stk_cd", "pdno"]),
|
|
84
|
+
symbolName: pickStr(row, ["stk_nm", "hts_kor_isnm"]),
|
|
85
|
+
currentPriceKrw: pickNum(row, ["cur_prc", "now_trde_qty"]),
|
|
86
|
+
changePct: pickNum(row, ["flu_rt", "prdy_ctrt"]),
|
|
87
|
+
volume: pickNum(row, ["trde_qty", "now_trde_qty", "acml_vol"]),
|
|
88
|
+
tradingValueKrw: pickNum(row, ["trde_amt", "trde_prica", "acml_tr_pbmn"]),
|
|
89
|
+
raw: row,
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 거래량이 이전 대비 급증한 종목.
|
|
94
|
+
*
|
|
95
|
+
* 용도:
|
|
96
|
+
* - 이슈 발생 · 대량 자금 유입 후보 포착
|
|
97
|
+
* - 페르소나 (예: 모멘텀 추종) 스크리너의 1차 후보 풀
|
|
98
|
+
*/
|
|
99
|
+
export async function getVolumeSurgeRanking(opts = {}) {
|
|
100
|
+
const { market = "all", sort = "surgeQty", timeBasis = "yesterday", minuteWindow = "", minVolume = "5", stockCondition = "1", priceCondition = "0", limit = 50, } = opts;
|
|
101
|
+
const sortTp = sort === "surgeQty" ? "1" : sort === "surgeRate" ? "2" : sort === "dropQty" ? "3" : "4";
|
|
102
|
+
const tmTp = timeBasis === "minute" ? "1" : "2";
|
|
103
|
+
const mrktTp = market === "kospi" ? "001" : market === "kosdaq" ? "101" : "000";
|
|
104
|
+
const cached = await cacheOrFetch(`ranking:volume-surge:${market}:${sort}:${timeBasis}:${minuteWindow}:${minVolume}:${stockCondition}:${priceCondition}`, TTL.RANKING, () => kiwoomCall({
|
|
105
|
+
path: "/api/dostk/rkinfo",
|
|
106
|
+
trId: "ka10023",
|
|
107
|
+
body: {
|
|
108
|
+
mrkt_tp: mrktTp,
|
|
109
|
+
sort_tp: sortTp,
|
|
110
|
+
tm_tp: tmTp,
|
|
111
|
+
trde_qty_tp: minVolume,
|
|
112
|
+
tm: minuteWindow,
|
|
113
|
+
stk_cnd: stockCondition,
|
|
114
|
+
pric_tp: priceCondition,
|
|
115
|
+
stex_tp: "1", // KRX
|
|
116
|
+
},
|
|
117
|
+
}));
|
|
118
|
+
if (!cached.value.ok)
|
|
119
|
+
return cached.value;
|
|
120
|
+
const arr = cached.value.data.trde_qty_sdnin ?? [];
|
|
121
|
+
const items = arr.slice(0, limit).map((row) => ({
|
|
122
|
+
symbolCode: String(row.stk_cd ?? ""),
|
|
123
|
+
symbolName: String(row.stk_nm ?? ""),
|
|
124
|
+
currentPriceKrw: Math.abs(toNumLocal(row.cur_prc)),
|
|
125
|
+
changePct: toNumLocal(row.flu_rt),
|
|
126
|
+
previousVolume: toNumLocal(row.prev_trde_qty),
|
|
127
|
+
currentVolume: toNumLocal(row.now_trde_qty),
|
|
128
|
+
surgeQty: toNumLocal(row.sdnin_qty),
|
|
129
|
+
surgeRate: toNumLocal(row.sdnin_rt),
|
|
130
|
+
raw: row,
|
|
131
|
+
}));
|
|
132
|
+
return {
|
|
133
|
+
ok: true,
|
|
134
|
+
data: items,
|
|
135
|
+
meta: { ...cached.value.meta, cached: cached.cached },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
export async function getYesterdayVolumeRanking(opts = {}) {
|
|
139
|
+
const { market = "all", kind = "volume", rankStart = 0, rankEnd = 100, } = opts;
|
|
140
|
+
const mrktTp = market === "kospi" ? "001" : market === "kosdaq" ? "101" : "000";
|
|
141
|
+
const qryTp = kind === "volume" ? "1" : "2";
|
|
142
|
+
const cached = await cacheOrFetch(`ranking:yesterday:${market}:${kind}:${rankStart}:${rankEnd}`, TTL.RANKING, () => kiwoomCall({
|
|
143
|
+
path: "/api/dostk/rkinfo",
|
|
144
|
+
trId: "ka10031",
|
|
145
|
+
body: {
|
|
146
|
+
mrkt_tp: mrktTp,
|
|
147
|
+
qry_tp: qryTp,
|
|
148
|
+
rank_strt: String(rankStart),
|
|
149
|
+
rank_end: String(rankEnd),
|
|
150
|
+
stex_tp: "1", // KRX
|
|
151
|
+
},
|
|
152
|
+
}));
|
|
153
|
+
if (!cached.value.ok)
|
|
154
|
+
return cached.value;
|
|
155
|
+
const arr = cached.value.data.pred_trde_qty_upper ?? [];
|
|
156
|
+
const items = arr.map((row) => ({
|
|
157
|
+
symbolCode: String(row.stk_cd ?? ""),
|
|
158
|
+
symbolName: String(row.stk_nm ?? ""),
|
|
159
|
+
currentPriceKrw: Math.abs(toNumLocal(row.cur_prc)),
|
|
160
|
+
changeKrw: toNumLocal(row.pred_pre),
|
|
161
|
+
volume: toNumLocal(row.trde_qty),
|
|
162
|
+
raw: row,
|
|
163
|
+
}));
|
|
164
|
+
return {
|
|
165
|
+
ok: true,
|
|
166
|
+
data: items,
|
|
167
|
+
meta: { ...cached.value.meta, cached: cached.cached },
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function toNumLocal(v) {
|
|
171
|
+
if (typeof v === "number")
|
|
172
|
+
return v;
|
|
173
|
+
if (typeof v === "string") {
|
|
174
|
+
const n = Number(v.replace(/,/g, "").replace(/^\+/, ""));
|
|
175
|
+
return Number.isFinite(n) ? n : 0;
|
|
176
|
+
}
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
// helpers: ./_helpers.ts (findFirstArray · pickNum · pickStr)
|
|
180
|
+
//# sourceMappingURL=ranking.js.map
|