@2oolkit/kiwoom-cli 0.1.1 → 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 CHANGED
@@ -147,6 +147,7 @@ kiwoom-cli ranking volume 당일 거래량 상위 (ka10030)
147
147
  kiwoom-cli ranking amount 거래대금 상위 (ka10032)
148
148
  kiwoom-cli ranking surge 거래량 급증 (ka10023)
149
149
  kiwoom-cli ranking prev-volume 전일 거래량 상위 (ka10031)
150
+ kiwoom-cli ranking net-buy [옵션] 수급: 외국인·기관 순매수 상위 (ka90009)
150
151
 
151
152
  kiwoom-cli sector price [-m -c] 업종 현재가 (ka20001)
152
153
  kiwoom-cli sector stocks 업종 구성종목 (ka20002)
@@ -156,6 +157,9 @@ kiwoom-cli sector codes 업종 코드 목록 (ka10101)
156
157
  ```
157
158
  순위: `-m 000=전체/001=코스피/101=코스닥`, `-x 1=KRX/2=NXT/3=통합`.
158
159
 
160
+ 수급(`net-buy`, alias `supply`)은 한 번의 ka90009 호출로 외국인·기관 매매상위를 함께 반환합니다:
161
+ `-b foreign|institution|both`(기본 both), `--side buy|sell`(기본 buy=순매수), `-n <1-50>`(기본 10), `-q 1=금액/2=수량`(기본 1), `-d YYYYMMDD`(기본 최신). 예: `kiwoom-cli ranking net-buy -b both -n 10`.
162
+
159
163
  ### `order` — ⚠ `real`에서는 실제 자금
160
164
  ```
161
165
  kiwoom-cli order buy <코드> <수량> [-p 가격] [-t 유형] [-x KRX|NXT|SOR] [--credit] [-y]
@@ -194,7 +198,7 @@ kiwoom-cli order cancel 0000139 005930 # 잔량 전부 취소
194
198
  }
195
199
  ```
196
200
 
197
- 도구: `get_stock_info`, `get_price`, `get_orderbook`, `get_daily_price`, `get_recent_trades`, `search_stocks`, `get_chart`, `get_balance`, `get_deposit`, `get_open_orders`, `get_executions`, `get_realized_pnl`, `get_ranking`, `get_sector`, `place_order`, `modify_order`, `cancel_order`.
201
+ 도구: `get_stock_info`, `get_price`, `get_orderbook`, `get_daily_price`, `get_recent_trades`, `search_stocks`, `get_chart`, `get_balance`, `get_deposit`, `get_open_orders`, `get_executions`, `get_realized_pnl`, `get_ranking`, `get_net_buy_ranking`, `get_sector`, `place_order`, `modify_order`, `cancel_order`.
198
202
 
199
203
  주문 도구는 **`confirm: true`를 넘기지 않으면 미리보기만 반환하고 아무것도 실행하지 않습니다** — 에이전트가 실수로 실주문을 낼 수 없습니다.
200
204
 
package/dist/index.js CHANGED
@@ -851,6 +851,7 @@ var ENDPOINTS = {
851
851
  rankTradeAmount: { apiId: "ka10032", path: PATHS.rkinfo, korean: "\uAC70\uB798\uB300\uAE08\uC0C1\uC704\uC694\uCCAD", listKey: "trde_prica_upper" },
852
852
  rankVolumeSurge: { apiId: "ka10023", path: PATHS.rkinfo, korean: "\uAC70\uB798\uB7C9\uAE09\uC99D\uC694\uCCAD", listKey: "trde_qty_sdnin" },
853
853
  rankPrevVolume: { apiId: "ka10031", path: PATHS.rkinfo, korean: "\uC804\uC77C\uAC70\uB798\uB7C9\uC0C1\uC704\uC694\uCCAD", listKey: "pred_trde_qty_upper" },
854
+ rankForeignInst: { apiId: "ka90009", path: PATHS.rkinfo, korean: "\uC678\uAD6D\uC778\uAE30\uAD00\uB9E4\uB9E4\uC0C1\uC704\uC694\uCCAD", listKey: "frgnr_orgn_trde_upper" },
854
855
  // ── Sector / industry (업종) ───────────────────────────────────────────────
855
856
  sectorPrice: { apiId: "ka20001", path: PATHS.sect, korean: "\uC5C5\uC885\uD604\uC7AC\uAC00\uC694\uCCAD", listKey: "inds_cur_prc_tm" },
856
857
  sectorStocks: { apiId: "ka20002", path: PATHS.sect, korean: "\uC5C5\uC885\uBCC4\uC8FC\uAC00\uC694\uCCAD", listKey: "inds_stkpc" },
@@ -1788,6 +1789,21 @@ ${side.toUpperCase()} ${credit ? "(credit) " : ""}${stk} qty ${qty} ${priceLab
1788
1789
  await submit(def, body, options);
1789
1790
  }
1790
1791
 
1792
+ // src/utils/ranking.ts
1793
+ var NETTRADE_FIELDS = {
1794
+ foreign: { buy: "for_netprps", sell: "for_netslmt" },
1795
+ institution: { buy: "orgn_netprps", sell: "orgn_netslmt" }
1796
+ };
1797
+ function extractNetTrade(rows, prefix, n) {
1798
+ return rows.slice(0, n).map((r, i) => ({
1799
+ rank: i + 1,
1800
+ code: r[`${prefix}_stk_cd`],
1801
+ name: r[`${prefix}_stk_nm`],
1802
+ \uAE08\uC561: won(unpad(r[`${prefix}_amt`] ?? "")),
1803
+ \uC218\uB7C9: won(unpad(r[`${prefix}_qty`] ?? ""))
1804
+ })).filter((x) => x.code);
1805
+ }
1806
+
1791
1807
  // src/commands/ranking.ts
1792
1808
  function formatRankRow(row) {
1793
1809
  return formatFields(row, {
@@ -1889,6 +1905,45 @@ function registerRankingCommands(program2) {
1889
1905
  handleError(err);
1890
1906
  }
1891
1907
  });
1908
+ ranking.command("net-buy").alias("supply").description("Foreign/institution net-buy \uC218\uAE09 ranking (ka90009)").option("-b, --by <foreign|institution|both>", "Investor: foreign=\uC678\uAD6D\uC778, institution=\uAE30\uAD00, both", "both").option("--side <buy|sell>", "buy=\uC21C\uB9E4\uC218, sell=\uC21C\uB9E4\uB3C4", "buy").option("-m, --market <000|001|101>", "Market: 000=all, 001=KOSPI, 101=KOSDAQ", "000").option("-x, --exchange <1|2|3>", "Exchange: 1=KRX, 2=NXT, 3=unified", "1").option("-n, --count <n>", "Top N (1-50)", "10").option("-q, --rank-by <1|2>", "Rank by 1=amount(\uAE08\uC561), 2=quantity(\uC218\uB7C9)", "1").option("-d, --date <YYYYMMDD>", "Query date (default: latest)").option("-o, --output <format>", "Output format (table/json)", "table").action(async (options) => {
1909
+ try {
1910
+ const side = options.side === "sell" ? "sell" : "buy";
1911
+ const by = ["foreign", "institution", "both"].includes(options.by) ? options.by : "both";
1912
+ const investors = by === "both" ? ["foreign", "institution"] : [by];
1913
+ const n = Math.min(Math.max(parseInt(String(options.count), 10) || 10, 1), 50);
1914
+ const client = createClient();
1915
+ const { data } = await client.callEndpoint(ENDPOINTS.rankForeignInst, {
1916
+ mrkt_tp: options.market,
1917
+ amt_qty_tp: options.rankBy === "2" ? "2" : "1",
1918
+ qry_dt_tp: options.date ? "1" : "0",
1919
+ date: options.date || "",
1920
+ stex_tp: options.exchange
1921
+ });
1922
+ const rows = Array.isArray(data?.[ENDPOINTS.rankForeignInst.listKey]) ? data[ENDPOINTS.rankForeignInst.listKey] : [];
1923
+ const fmt = getOutputFormat(options);
1924
+ const sideKo = side === "buy" ? "\uC21C\uB9E4\uC218" : "\uC21C\uB9E4\uB3C4";
1925
+ const labelKo = { foreign: "\uC678\uAD6D\uC778", institution: "\uAE30\uAD00" };
1926
+ if (fmt === "json") {
1927
+ const result = {
1928
+ side,
1929
+ rankBy: options.rankBy === "2" ? "quantity" : "amount"
1930
+ };
1931
+ for (const inv of investors) {
1932
+ result[inv] = extractNetTrade(rows, NETTRADE_FIELDS[inv][side], n);
1933
+ }
1934
+ output(result, "json");
1935
+ return;
1936
+ }
1937
+ for (const inv of investors) {
1938
+ const list = extractNetTrade(rows, NETTRADE_FIELDS[inv][side], n);
1939
+ console.log(`
1940
+ ${labelKo[inv]} ${sideKo} TOP${n} (${options.rankBy === "2" ? "\uC218\uB7C9" : "\uAE08\uC561"} \uAE30\uC900)`);
1941
+ output(list, "table");
1942
+ }
1943
+ } catch (err) {
1944
+ handleError(err);
1945
+ }
1946
+ });
1892
1947
  }
1893
1948
 
1894
1949
  // src/commands/sector.ts