@2oolkit/kiwoom-cli 0.1.0 → 0.1.2
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 +24 -2
- package/dist/index.js +121 -34
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +90 -7
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +16 -9
- package/skill/references/market-data.md +23 -13
package/dist/mcp.js
CHANGED
|
@@ -84,6 +84,14 @@ var ORDER_TYPES = {
|
|
|
84
84
|
"81": "\uC7A5\uB9C8\uAC10\uD6C4\uC2DC\uAC04\uC678"
|
|
85
85
|
};
|
|
86
86
|
var MARKET_ORDER_TYPES = /* @__PURE__ */ new Set(["3", "13", "23"]);
|
|
87
|
+
var CHART_PER_PAGE_CAP = {
|
|
88
|
+
tick: 900,
|
|
89
|
+
minute: 900,
|
|
90
|
+
day: 600,
|
|
91
|
+
week: 300,
|
|
92
|
+
month: 240,
|
|
93
|
+
year: 30
|
|
94
|
+
};
|
|
87
95
|
|
|
88
96
|
// src/config/store.ts
|
|
89
97
|
var fs = __toESM(require("fs"));
|
|
@@ -374,7 +382,7 @@ var KiwoomClient = class {
|
|
|
374
382
|
*/
|
|
375
383
|
async callEndpoint(def, body = {}, opts = {}) {
|
|
376
384
|
if (opts.paginate && def.listKey) {
|
|
377
|
-
const data = await this.requestAll(def.apiId, def.path, body, def.listKey);
|
|
385
|
+
const data = await this.requestAll(def.apiId, def.path, body, def.listKey, opts.maxPages);
|
|
378
386
|
return { data, contYn: false, nextKey: "" };
|
|
379
387
|
}
|
|
380
388
|
return this.request(def.apiId, def.path, body, {
|
|
@@ -384,9 +392,11 @@ var KiwoomClient = class {
|
|
|
384
392
|
}
|
|
385
393
|
/**
|
|
386
394
|
* Fetch all pages of a TR, concatenating the array under `listKey`.
|
|
387
|
-
* Caps at `maxPages` to avoid runaway loops
|
|
395
|
+
* Caps at `maxPages` to avoid runaway loops (default 100 — high enough for
|
|
396
|
+
* large chart pulls; callers pass a tighter bound when they know how many
|
|
397
|
+
* pages a target row count needs).
|
|
388
398
|
*/
|
|
389
|
-
async requestAll(apiId, path2, body, listKey, maxPages =
|
|
399
|
+
async requestAll(apiId, path2, body, listKey, maxPages = 100) {
|
|
390
400
|
let page = await this.request(apiId, path2, body);
|
|
391
401
|
const acc = Array.isArray(page.data[listKey]) ? [...page.data[listKey]] : [];
|
|
392
402
|
let pages = 1;
|
|
@@ -508,6 +518,7 @@ var ENDPOINTS = {
|
|
|
508
518
|
rankTradeAmount: { apiId: "ka10032", path: PATHS.rkinfo, korean: "\uAC70\uB798\uB300\uAE08\uC0C1\uC704\uC694\uCCAD", listKey: "trde_prica_upper" },
|
|
509
519
|
rankVolumeSurge: { apiId: "ka10023", path: PATHS.rkinfo, korean: "\uAC70\uB798\uB7C9\uAE09\uC99D\uC694\uCCAD", listKey: "trde_qty_sdnin" },
|
|
510
520
|
rankPrevVolume: { apiId: "ka10031", path: PATHS.rkinfo, korean: "\uC804\uC77C\uAC70\uB798\uB7C9\uC0C1\uC704\uC694\uCCAD", listKey: "pred_trde_qty_upper" },
|
|
521
|
+
rankForeignInst: { apiId: "ka90009", path: PATHS.rkinfo, korean: "\uC678\uAD6D\uC778\uAE30\uAD00\uB9E4\uB9E4\uC0C1\uC704\uC694\uCCAD", listKey: "frgnr_orgn_trde_upper" },
|
|
511
522
|
// ── Sector / industry (업종) ───────────────────────────────────────────────
|
|
512
523
|
sectorPrice: { apiId: "ka20001", path: PATHS.sect, korean: "\uC5C5\uC885\uD604\uC7AC\uAC00\uC694\uCCAD", listKey: "inds_cur_prc_tm" },
|
|
513
524
|
sectorStocks: { apiId: "ka20002", path: PATHS.sect, korean: "\uC5C5\uC885\uBCC4\uC8FC\uAC00\uC694\uCCAD", listKey: "inds_stkpc" },
|
|
@@ -526,6 +537,15 @@ function unpad(value) {
|
|
|
526
537
|
const intPart = m[2].replace(/^0+(?=\d)/, "");
|
|
527
538
|
return `${sign}${intPart}${m[3] ?? ""}`;
|
|
528
539
|
}
|
|
540
|
+
function withCommas(value) {
|
|
541
|
+
const m = value.match(/^([+-]?)(\d+)(\.\d+)?$/);
|
|
542
|
+
if (!m) return value;
|
|
543
|
+
const grouped = m[2].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
|
544
|
+
return `${m[1]}${grouped}${m[3] ?? ""}`;
|
|
545
|
+
}
|
|
546
|
+
function won(value) {
|
|
547
|
+
return withCommas(unpad(value));
|
|
548
|
+
}
|
|
529
549
|
function formatStamp(value) {
|
|
530
550
|
if (!value) return "";
|
|
531
551
|
const s = String(value).trim();
|
|
@@ -687,25 +707,30 @@ var PERIOD_EP = {
|
|
|
687
707
|
month: ENDPOINTS.monthlyChart,
|
|
688
708
|
year: ENDPOINTS.yearlyChart
|
|
689
709
|
};
|
|
710
|
+
var DEFAULT_COUNT = 50;
|
|
711
|
+
var MAX_COUNT = 1e5;
|
|
690
712
|
function registerChartTools(server2) {
|
|
691
713
|
tool(
|
|
692
714
|
server2,
|
|
693
715
|
"get_chart",
|
|
694
716
|
{
|
|
695
|
-
description: "Get OHLC chart data for a stock. Period charts (day/week/month/year) end at base date; tick/minute use an aggregation scope. Returns latest-first; use count to cap rows.",
|
|
717
|
+
description: "Get OHLC chart data for a stock. Period charts (day/week/month/year) end at base date; tick/minute use an aggregation scope. Returns latest-first; use count to cap rows. Per-request caps: tick/minute 900, day 600, week 300, month 240, year 30. When count exceeds the cap the tool auto-paginates (cont-yn/next-key) and returns up to count rows.",
|
|
696
718
|
inputSchema: {
|
|
697
719
|
code: import_zod2.z.string().describe("6-digit stock code"),
|
|
698
720
|
timeframe: import_zod2.z.enum(["tick", "minute", "day", "week", "month", "year"]).describe("Chart timeframe"),
|
|
699
721
|
scope: import_zod2.z.string().optional().describe("Tick units (1/3/5/10/30) or minute interval (1/3/5/10/15/30/45/60); default 1"),
|
|
700
722
|
date: import_zod2.z.string().optional().describe("Base date YYYYMMDD for period charts (default today)"),
|
|
701
723
|
adjusted: import_zod2.z.boolean().optional().describe("Adjust for splits/rights (default true)"),
|
|
702
|
-
count: import_zod2.z.number().min(1).max(
|
|
724
|
+
count: import_zod2.z.number().min(1).max(MAX_COUNT).optional().describe(
|
|
725
|
+
"Max rows to return (default 50). Exceeding the per-request cap (tick/min 900, day 600, week 300, month 240, year 30) auto-paginates."
|
|
726
|
+
)
|
|
703
727
|
}
|
|
704
728
|
},
|
|
705
729
|
async ({ code, timeframe, scope, date, adjusted, count }) => withErrorHandling(async () => {
|
|
706
730
|
const client = clientOrThrow();
|
|
707
731
|
const stk = normalizeStockCode(code);
|
|
708
732
|
const upd = adjusted === false ? "0" : "1";
|
|
733
|
+
const want = count ?? DEFAULT_COUNT;
|
|
709
734
|
let def;
|
|
710
735
|
let body;
|
|
711
736
|
if (timeframe === "tick") {
|
|
@@ -718,12 +743,15 @@ function registerChartTools(server2) {
|
|
|
718
743
|
def = PERIOD_EP[timeframe];
|
|
719
744
|
body = { stk_cd: stk, base_dt: date ?? todayKst(), upd_stkpc_tp: upd };
|
|
720
745
|
}
|
|
721
|
-
const
|
|
746
|
+
const cap = CHART_PER_PAGE_CAP[timeframe];
|
|
747
|
+
const paginate = want > cap;
|
|
748
|
+
const maxPages = paginate ? Math.max(1, Math.ceil(want / cap)) : 1;
|
|
749
|
+
const { data } = await client.callEndpoint(def, body, { paginate, maxPages });
|
|
722
750
|
const rows = Array.isArray(data[def.listKey]) ? data[def.listKey] : [];
|
|
723
751
|
return mcpJson({
|
|
724
752
|
code: stk,
|
|
725
753
|
timeframe,
|
|
726
|
-
rows: rows.slice(0,
|
|
754
|
+
rows: rows.slice(0, want)
|
|
727
755
|
});
|
|
728
756
|
})
|
|
729
757
|
);
|
|
@@ -968,6 +996,28 @@ ${JSON.stringify(preview, null, 2)}`);
|
|
|
968
996
|
|
|
969
997
|
// src/mcp/tools/ranking.ts
|
|
970
998
|
var import_zod5 = require("zod");
|
|
999
|
+
|
|
1000
|
+
// src/utils/ranking.ts
|
|
1001
|
+
var NETTRADE_FIELDS = {
|
|
1002
|
+
foreign: { buy: "for_netprps", sell: "for_netslmt" },
|
|
1003
|
+
institution: { buy: "orgn_netprps", sell: "orgn_netslmt" }
|
|
1004
|
+
};
|
|
1005
|
+
function extractNetTrade(rows, prefix, n) {
|
|
1006
|
+
return rows.slice(0, n).map((r, i) => ({
|
|
1007
|
+
rank: i + 1,
|
|
1008
|
+
code: r[`${prefix}_stk_cd`],
|
|
1009
|
+
name: r[`${prefix}_stk_nm`],
|
|
1010
|
+
\uAE08\uC561: won(unpad(r[`${prefix}_amt`] ?? "")),
|
|
1011
|
+
\uC218\uB7C9: won(unpad(r[`${prefix}_qty`] ?? ""))
|
|
1012
|
+
})).filter((x) => x.code);
|
|
1013
|
+
}
|
|
1014
|
+
function buildNetTradeResult(rows, investors, side, n) {
|
|
1015
|
+
const result = {};
|
|
1016
|
+
for (const inv of investors) result[inv] = extractNetTrade(rows, NETTRADE_FIELDS[inv][side], n);
|
|
1017
|
+
return result;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// src/mcp/tools/ranking.ts
|
|
971
1021
|
function registerRankingTools(server2) {
|
|
972
1022
|
tool(
|
|
973
1023
|
server2,
|
|
@@ -1040,6 +1090,39 @@ function registerRankingTools(server2) {
|
|
|
1040
1090
|
return mcpJson(data);
|
|
1041
1091
|
})
|
|
1042
1092
|
);
|
|
1093
|
+
tool(
|
|
1094
|
+
server2,
|
|
1095
|
+
"get_net_buy_ranking",
|
|
1096
|
+
{
|
|
1097
|
+
description: "Get the foreign/institution net-buy (\uC218\uAE09) ranking (ka90009): top stocks by foreign and/or institutional net buying or selling. One call returns parallel rankings for both investor types.",
|
|
1098
|
+
inputSchema: {
|
|
1099
|
+
by: import_zod5.z.enum(["foreign", "institution", "both"]).optional().describe("Investor type: foreign=\uC678\uAD6D\uC778, institution=\uAE30\uAD00, both (default both)"),
|
|
1100
|
+
side: import_zod5.z.enum(["buy", "sell"]).optional().describe("buy=\uC21C\uB9E4\uC218 (default), sell=\uC21C\uB9E4\uB3C4"),
|
|
1101
|
+
market: import_zod5.z.enum(["000", "001", "101"]).optional().describe("000=all, 001=KOSPI, 101=KOSDAQ"),
|
|
1102
|
+
exchange: import_zod5.z.enum(["1", "2", "3"]).optional().describe("1=KRX (default), 2=NXT, 3=unified"),
|
|
1103
|
+
count: import_zod5.z.number().int().min(1).max(50).optional().describe("Top N (default 10)"),
|
|
1104
|
+
rankBy: import_zod5.z.enum(["1", "2"]).optional().describe("Rank by 1=amount \uAE08\uC561 (default), 2=quantity \uC218\uB7C9"),
|
|
1105
|
+
date: import_zod5.z.string().optional().describe("Query date YYYYMMDD (default: latest)")
|
|
1106
|
+
}
|
|
1107
|
+
},
|
|
1108
|
+
async ({ by, side, market, exchange, count, rankBy, date }) => withErrorHandling(async () => {
|
|
1109
|
+
const investors = by === "foreign" ? ["foreign"] : by === "institution" ? ["institution"] : ["foreign", "institution"];
|
|
1110
|
+
const netSide = side === "sell" ? "sell" : "buy";
|
|
1111
|
+
const { data } = await clientOrThrow().callEndpoint(ENDPOINTS.rankForeignInst, {
|
|
1112
|
+
mrkt_tp: market ?? "000",
|
|
1113
|
+
amt_qty_tp: rankBy === "2" ? "2" : "1",
|
|
1114
|
+
qry_dt_tp: date ? "1" : "0",
|
|
1115
|
+
date: date ?? "",
|
|
1116
|
+
stex_tp: exchange ?? "1"
|
|
1117
|
+
});
|
|
1118
|
+
const rows = Array.isArray(data?.[ENDPOINTS.rankForeignInst.listKey]) ? data[ENDPOINTS.rankForeignInst.listKey] : [];
|
|
1119
|
+
return mcpJson({
|
|
1120
|
+
side: netSide,
|
|
1121
|
+
rankBy: rankBy === "2" ? "quantity" : "amount",
|
|
1122
|
+
...buildNetTradeResult(rows, investors, netSide, count ?? 10)
|
|
1123
|
+
});
|
|
1124
|
+
})
|
|
1125
|
+
);
|
|
1043
1126
|
tool(
|
|
1044
1127
|
server2,
|
|
1045
1128
|
"get_sector",
|