@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.
Files changed (119) hide show
  1. package/README.md +103 -0
  2. package/bin/cli.js +160 -0
  3. package/bin/postinstall.js +57 -0
  4. package/bin/preuninstall.js +36 -0
  5. package/dist/server/broker/kiwoom/cache.js +86 -0
  6. package/dist/server/broker/kiwoom/cache.js.map +1 -0
  7. package/dist/server/broker/kiwoom/client.js +256 -0
  8. package/dist/server/broker/kiwoom/client.js.map +1 -0
  9. package/dist/server/broker/kiwoom/endpoints/_helpers.js +61 -0
  10. package/dist/server/broker/kiwoom/endpoints/_helpers.js.map +1 -0
  11. package/dist/server/broker/kiwoom/endpoints/account.js +448 -0
  12. package/dist/server/broker/kiwoom/endpoints/account.js.map +1 -0
  13. package/dist/server/broker/kiwoom/endpoints/detail.js +118 -0
  14. package/dist/server/broker/kiwoom/endpoints/detail.js.map +1 -0
  15. package/dist/server/broker/kiwoom/endpoints/investor.js +139 -0
  16. package/dist/server/broker/kiwoom/endpoints/investor.js.map +1 -0
  17. package/dist/server/broker/kiwoom/endpoints/order.js +134 -0
  18. package/dist/server/broker/kiwoom/endpoints/order.js.map +1 -0
  19. package/dist/server/broker/kiwoom/endpoints/quote.js +165 -0
  20. package/dist/server/broker/kiwoom/endpoints/quote.js.map +1 -0
  21. package/dist/server/broker/kiwoom/endpoints/ranking.js +180 -0
  22. package/dist/server/broker/kiwoom/endpoints/ranking.js.map +1 -0
  23. package/dist/server/broker/kiwoom/endpoints/sector.js +135 -0
  24. package/dist/server/broker/kiwoom/endpoints/sector.js.map +1 -0
  25. package/dist/server/broker/kiwoom/endpoints/theme.js +104 -0
  26. package/dist/server/broker/kiwoom/endpoints/theme.js.map +1 -0
  27. package/dist/server/broker/kiwoom/endpoints/universe.js +119 -0
  28. package/dist/server/broker/kiwoom/endpoints/universe.js.map +1 -0
  29. package/dist/server/broker/kiwoom/index.js +59 -0
  30. package/dist/server/broker/kiwoom/index.js.map +1 -0
  31. package/dist/server/broker/kiwoom/order-tracker.js +353 -0
  32. package/dist/server/broker/kiwoom/order-tracker.js.map +1 -0
  33. package/dist/server/broker/kiwoom/price-feed.js +119 -0
  34. package/dist/server/broker/kiwoom/price-feed.js.map +1 -0
  35. package/dist/server/broker/kiwoom/rate-limiter.js +97 -0
  36. package/dist/server/broker/kiwoom/rate-limiter.js.map +1 -0
  37. package/dist/server/broker/kiwoom/types.js +13 -0
  38. package/dist/server/broker/kiwoom/types.js.map +1 -0
  39. package/dist/server/broker/kiwoom/ws/client.js +370 -0
  40. package/dist/server/broker/kiwoom/ws/client.js.map +1 -0
  41. package/dist/server/broker/kiwoom/ws/endpoints/condition.js +146 -0
  42. package/dist/server/broker/kiwoom/ws/endpoints/condition.js.map +1 -0
  43. package/dist/server/broker/kiwoom/ws/realtime-bus.js +42 -0
  44. package/dist/server/broker/kiwoom/ws/realtime-bus.js.map +1 -0
  45. package/dist/server/broker/kiwoom/ws/types.js +19 -0
  46. package/dist/server/broker/kiwoom/ws/types.js.map +1 -0
  47. package/dist/server/broker/news.js +34 -0
  48. package/dist/server/broker/news.js.map +1 -0
  49. package/dist/server/bundle.js +43 -0
  50. package/dist/server/bundle.js.map +1 -0
  51. package/dist/server/calendar/krx-holidays.js +162 -0
  52. package/dist/server/calendar/krx-holidays.js.map +1 -0
  53. package/dist/server/config.js +263 -0
  54. package/dist/server/config.js.map +1 -0
  55. package/dist/server/db/sqlite.js +252 -0
  56. package/dist/server/db/sqlite.js.map +1 -0
  57. package/dist/server/diary/writer.js +266 -0
  58. package/dist/server/diary/writer.js.map +1 -0
  59. package/dist/server/index.js +316 -0
  60. package/dist/server/index.js.map +1 -0
  61. package/dist/server/jobs/universe-sync.js +132 -0
  62. package/dist/server/jobs/universe-sync.js.map +1 -0
  63. package/dist/server/jobs/watchdog.js +87 -0
  64. package/dist/server/jobs/watchdog.js.map +1 -0
  65. package/dist/server/journal/pnl-stats.js +108 -0
  66. package/dist/server/journal/pnl-stats.js.map +1 -0
  67. package/dist/server/journal/telemetry.js +174 -0
  68. package/dist/server/journal/telemetry.js.map +1 -0
  69. package/dist/server/journal/trade-journal.js +239 -0
  70. package/dist/server/journal/trade-journal.js.map +1 -0
  71. package/dist/server/llm/anthropic.js +98 -0
  72. package/dist/server/llm/anthropic.js.map +1 -0
  73. package/dist/server/llm/claude-cli.js +204 -0
  74. package/dist/server/llm/claude-cli.js.map +1 -0
  75. package/dist/server/llm/context-builder.js +229 -0
  76. package/dist/server/llm/context-builder.js.map +1 -0
  77. package/dist/server/llm/gemini.js +86 -0
  78. package/dist/server/llm/gemini.js.map +1 -0
  79. package/dist/server/llm/index.js +36 -0
  80. package/dist/server/llm/index.js.map +1 -0
  81. package/dist/server/llm/openai.js +87 -0
  82. package/dist/server/llm/openai.js.map +1 -0
  83. package/dist/server/personas/executor.js +318 -0
  84. package/dist/server/personas/executor.js.map +1 -0
  85. package/dist/server/personas/loader.js +165 -0
  86. package/dist/server/personas/loader.js.map +1 -0
  87. package/dist/server/personas/persona-agent.js +386 -0
  88. package/dist/server/personas/persona-agent.js.map +1 -0
  89. package/dist/server/personas/persona-state.js +170 -0
  90. package/dist/server/personas/persona-state.js.map +1 -0
  91. package/dist/server/personas/runner.js +162 -0
  92. package/dist/server/personas/runner.js.map +1 -0
  93. package/dist/server/personas/schema.js +123 -0
  94. package/dist/server/personas/schema.js.map +1 -0
  95. package/dist/server/personas/wake-plan.js +313 -0
  96. package/dist/server/personas/wake-plan.js.map +1 -0
  97. package/dist/server/readiness.js +414 -0
  98. package/dist/server/readiness.js.map +1 -0
  99. package/dist/server/routes.js +1216 -0
  100. package/dist/server/routes.js.map +1 -0
  101. package/dist/server/safety.js +153 -0
  102. package/dist/server/safety.js.map +1 -0
  103. package/dist/server/screener/engine.js +856 -0
  104. package/dist/server/screener/engine.js.map +1 -0
  105. package/dist/server/server-sync.js +427 -0
  106. package/dist/server/server-sync.js.map +1 -0
  107. package/dist/server/signing.js +39 -0
  108. package/dist/server/signing.js.map +1 -0
  109. package/dist/server/watchers/condition-watcher.js +519 -0
  110. package/dist/server/watchers/condition-watcher.js.map +1 -0
  111. package/dist/server/watchers/types.js +16 -0
  112. package/dist/server/watchers/types.js.map +1 -0
  113. package/dist/web/assets/index-62SMpbaf.js +79 -0
  114. package/dist/web/assets/index-BPLQR0wt.css +1 -0
  115. package/dist/web/index.html +14 -0
  116. package/package.json +93 -0
  117. package/scripts/com.mulsok.traders.client.plist.template +58 -0
  118. package/scripts/install-daemon.sh +156 -0
  119. 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