@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 CHANGED
@@ -106,7 +106,25 @@ kiwoom-cli chart week <코드> 주봉 (ka10082)
106
106
  kiwoom-cli chart month <코드> 월봉 (ka10083)
107
107
  kiwoom-cli chart year <코드> 년봉 (ka10094)
108
108
  ```
109
- 모두 `-n, --count <n>`( 수, 기본 50), `--raw`(수정주가 미적용) 지원.
109
+ 모두 `-n, --count <n>`( 개수, 기본 50), `--raw`(수정주가 미적용), `-p, --paginate`(강제 다중 페이지) 지원.
110
+
111
+ **1회 요청당 최대 봉 개수 (per-request cap)** — 그 이상은 `cont-yn`/`next-key` 헤더로 **자동 페이지네이션**:
112
+
113
+ | 차트 | 1회 최대 | `--count` 한도 |
114
+ |---|---|---|
115
+ | tick / min | 900 | 100,000 (자동 분할) |
116
+ | day | 600 | 100,000 (자동 분할) |
117
+ | week | 300 | 100,000 (자동 분할) |
118
+ | month | 240 | 100,000 (자동 분할) |
119
+ | year | 30 | 100,000 (자동 분할) |
120
+
121
+ `--count`가 1회 최대를 넘으면 자동으로 여러 페이지를 받아 합칩니다(시간순 정렬·중복 제거는 API 순서를 그대로 유지). `-p/--paginate`로 강제할 수도 있습니다. `--count`는 양의 정수여야 하며 100,000으로 클램프됩니다.
122
+
123
+ ```bash
124
+ kiwoom-cli chart day 005930 -n 600 # 한 페이지(최대) — 일봉 600개
125
+ kiwoom-cli chart day 005930 -n 2000 -o json # 자동 페이지네이션 — 일봉 ~2000개
126
+ kiwoom-cli chart min 005930 -i 1 -n 2000 -o json # 1분봉 ~2000개 (여러 페이지)
127
+ ```
110
128
 
111
129
  ### `account` — 계좌
112
130
  ```
@@ -129,6 +147,7 @@ kiwoom-cli ranking volume 당일 거래량 상위 (ka10030)
129
147
  kiwoom-cli ranking amount 거래대금 상위 (ka10032)
130
148
  kiwoom-cli ranking surge 거래량 급증 (ka10023)
131
149
  kiwoom-cli ranking prev-volume 전일 거래량 상위 (ka10031)
150
+ kiwoom-cli ranking net-buy [옵션] 수급: 외국인·기관 순매수 상위 (ka90009)
132
151
 
133
152
  kiwoom-cli sector price [-m -c] 업종 현재가 (ka20001)
134
153
  kiwoom-cli sector stocks 업종 구성종목 (ka20002)
@@ -138,6 +157,9 @@ kiwoom-cli sector codes 업종 코드 목록 (ka10101)
138
157
  ```
139
158
  순위: `-m 000=전체/001=코스피/101=코스닥`, `-x 1=KRX/2=NXT/3=통합`.
140
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
+
141
163
  ### `order` — ⚠ `real`에서는 실제 자금
142
164
  ```
143
165
  kiwoom-cli order buy <코드> <수량> [-p 가격] [-t 유형] [-x KRX|NXT|SOR] [--credit] [-y]
@@ -176,7 +198,7 @@ kiwoom-cli order cancel 0000139 005930 # 잔량 전부 취소
176
198
  }
177
199
  ```
178
200
 
179
- 도구: `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`.
180
202
 
181
203
  주문 도구는 **`confirm: true`를 넘기지 않으면 미리보기만 반환하고 아무것도 실행하지 않습니다** — 에이전트가 실수로 실주문을 낼 수 없습니다.
182
204
 
package/dist/index.js CHANGED
@@ -89,6 +89,15 @@ var ORDER_TYPES = {
89
89
  "81": "\uC7A5\uB9C8\uAC10\uD6C4\uC2DC\uAC04\uC678"
90
90
  };
91
91
  var MARKET_ORDER_TYPES = /* @__PURE__ */ new Set(["3", "13", "23"]);
92
+ var CHART_PER_PAGE_CAP = {
93
+ tick: 900,
94
+ minute: 900,
95
+ day: 600,
96
+ week: 300,
97
+ month: 240,
98
+ year: 30
99
+ };
100
+ var CHART_MAX_COUNT = 1e5;
92
101
 
93
102
  // src/config/store.ts
94
103
  var DEFAULT_CONFIG = { env: "real" };
@@ -438,7 +447,7 @@ var KiwoomClient = class {
438
447
  */
439
448
  async callEndpoint(def, body = {}, opts = {}) {
440
449
  if (opts.paginate && def.listKey) {
441
- const data = await this.requestAll(def.apiId, def.path, body, def.listKey);
450
+ const data = await this.requestAll(def.apiId, def.path, body, def.listKey, opts.maxPages);
442
451
  return { data, contYn: false, nextKey: "" };
443
452
  }
444
453
  return this.request(def.apiId, def.path, body, {
@@ -448,9 +457,11 @@ var KiwoomClient = class {
448
457
  }
449
458
  /**
450
459
  * Fetch all pages of a TR, concatenating the array under `listKey`.
451
- * Caps at `maxPages` to avoid runaway loops.
460
+ * Caps at `maxPages` to avoid runaway loops (default 100 — high enough for
461
+ * large chart pulls; callers pass a tighter bound when they know how many
462
+ * pages a target row count needs).
452
463
  */
453
- async requestAll(apiId, path2, body, listKey, maxPages = 20) {
464
+ async requestAll(apiId, path2, body, listKey, maxPages = 100) {
454
465
  let page = await this.request(apiId, path2, body);
455
466
  const acc = Array.isArray(page.data[listKey]) ? [...page.data[listKey]] : [];
456
467
  let pages = 1;
@@ -840,6 +851,7 @@ var ENDPOINTS = {
840
851
  rankTradeAmount: { apiId: "ka10032", path: PATHS.rkinfo, korean: "\uAC70\uB798\uB300\uAE08\uC0C1\uC704\uC694\uCCAD", listKey: "trde_prica_upper" },
841
852
  rankVolumeSurge: { apiId: "ka10023", path: PATHS.rkinfo, korean: "\uAC70\uB798\uB7C9\uAE09\uC99D\uC694\uCCAD", listKey: "trde_qty_sdnin" },
842
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" },
843
855
  // ── Sector / industry (업종) ───────────────────────────────────────────────
844
856
  sectorPrice: { apiId: "ka20001", path: PATHS.sect, korean: "\uC5C5\uC885\uD604\uC7AC\uAC00\uC694\uCCAD", listKey: "inds_cur_prc_tm" },
845
857
  sectorStocks: { apiId: "ka20002", path: PATHS.sect, korean: "\uC5C5\uC885\uBCC4\uC8FC\uAC00\uC694\uCCAD", listKey: "inds_stkpc" },
@@ -1254,81 +1266,102 @@ var CHART_ROW_FORMATTERS = {
1254
1266
  pred_pre: unpad,
1255
1267
  trde_tern_rt: unpad
1256
1268
  };
1269
+ var PER_PAGE_CAP = CHART_PER_PAGE_CAP;
1270
+ function parseCount(raw) {
1271
+ const n = parseIntStrict(raw, "count");
1272
+ if (n < 1) {
1273
+ throw new ActionableError(`--count must be a positive integer (got ${n}).`);
1274
+ }
1275
+ return Math.min(n, CHART_MAX_COUNT);
1276
+ }
1257
1277
  function emitChart(data, ep, fmt, count) {
1278
+ const all = Array.isArray(data[ep.listKey]) ? data[ep.listKey] : [];
1279
+ const sliced = all.slice(0, count);
1258
1280
  if (fmt === "json") {
1259
- output(data, "json");
1281
+ output({ ...data, [ep.listKey]: sliced }, "json");
1260
1282
  return;
1261
1283
  }
1262
- const all = Array.isArray(data[ep.listKey]) ? data[ep.listKey] : [];
1263
- if (all.length === 0) {
1284
+ if (sliced.length === 0) {
1264
1285
  console.log("No data");
1265
1286
  return;
1266
1287
  }
1267
1288
  output(
1268
- all.slice(0, count).map((row) => formatFields(row, CHART_ROW_FORMATTERS)),
1289
+ sliced.map((row) => formatFields(row, CHART_ROW_FORMATTERS)),
1269
1290
  "table"
1270
1291
  );
1271
1292
  }
1272
1293
  function registerChartCommands(program2) {
1273
- const chart = program2.command("chart").description("OHLC charts \u2014 tick / minute / daily / weekly / monthly / yearly");
1274
- async function runIntraday(ep, code, ticScope, options) {
1294
+ const chart = program2.command("chart").description(
1295
+ "OHLC charts \u2014 tick / minute / daily / weekly / monthly / yearly. Per-request caps: tick/min 900, day 600, week 300, month 240, year 30. --count beyond the cap auto-paginates via cont-yn/next-key."
1296
+ );
1297
+ function planFetch(type, count, paginate) {
1298
+ const cap = PER_PAGE_CAP[type];
1299
+ const active = paginate || count > cap;
1300
+ const maxPages = active ? Math.max(1, Math.ceil(count / cap)) : 1;
1301
+ return { paginate: active, maxPages };
1302
+ }
1303
+ async function runIntraday(ep, type, code, ticScope, options) {
1275
1304
  const client = createClient();
1276
1305
  const stk = normalizeStockCode(code);
1277
- const { data } = await client.callEndpoint(ep, {
1278
- stk_cd: stk,
1279
- tic_scope: ticScope,
1280
- upd_stkpc_tp: options.raw ? "0" : "1"
1281
- });
1282
- emitChart(data, ep, getOutputFormat(options), parseIntStrict(options.count, "count"));
1306
+ const count = parseCount(options.count);
1307
+ const plan = planFetch(type, count, !!options.paginate);
1308
+ const { data } = await client.callEndpoint(
1309
+ ep,
1310
+ { stk_cd: stk, tic_scope: ticScope, upd_stkpc_tp: options.raw ? "0" : "1" },
1311
+ { paginate: plan.paginate, maxPages: plan.maxPages }
1312
+ );
1313
+ emitChart(data, ep, getOutputFormat(options), count);
1283
1314
  }
1284
- async function runPeriod(ep, code, baseDt, options) {
1315
+ async function runPeriod(ep, type, code, baseDt, options) {
1285
1316
  const client = createClient();
1286
1317
  const stk = normalizeStockCode(code);
1287
- const { data } = await client.callEndpoint(ep, {
1288
- stk_cd: stk,
1289
- base_dt: baseDt,
1290
- upd_stkpc_tp: options.raw ? "0" : "1"
1291
- });
1292
- emitChart(data, ep, getOutputFormat(options), parseIntStrict(options.count, "count"));
1318
+ const count = parseCount(options.count);
1319
+ const plan = planFetch(type, count, !!options.paginate);
1320
+ const { data } = await client.callEndpoint(
1321
+ ep,
1322
+ { stk_cd: stk, base_dt: baseDt, upd_stkpc_tp: options.raw ? "0" : "1" },
1323
+ { paginate: plan.paginate, maxPages: plan.maxPages }
1324
+ );
1325
+ emitChart(data, ep, getOutputFormat(options), count);
1293
1326
  }
1294
- chart.command("tick <code>").description("Tick chart (ka10079)").option("-s, --scope <n>", "Ticks per candle (1/3/5/10/30)", "1").option("-n, --count <n>", "Number of candles to display", "50").option("--raw", "Unadjusted (\uC218\uC815\uC8FC\uAC00 \uBBF8\uBC18\uC601) prices").option("-o, --output <format>", "Output format (table/json)", "table").action(async (code, options) => {
1327
+ chart.command("tick <code>").description(`Tick chart (ka10079) \u2014 up to ${PER_PAGE_CAP.tick} candles/request, auto-paginates beyond that`).option("-s, --scope <n>", "Ticks per candle (1/3/5/10/30)", "1").option("-n, --count <n>", "Number of candles (auto-paginates when > 900)", "50").option("-p, --paginate", "Force fetching multiple pages (cont-yn/next-key)").option("--raw", "Unadjusted (\uC218\uC815\uC8FC\uAC00 \uBBF8\uBC18\uC601) prices").option("-o, --output <format>", "Output format (table/json)", "table").action(async (code, options) => {
1295
1328
  try {
1296
- await runIntraday(ENDPOINTS.tickChart, code, options.scope, options);
1329
+ await runIntraday(ENDPOINTS.tickChart, "tick", code, options.scope, options);
1297
1330
  } catch (err) {
1298
1331
  handleError(err);
1299
1332
  }
1300
1333
  });
1301
- chart.command("min <code>").alias("minute").description("Minute chart (ka10080)").option("-i, --interval <n>", "Minutes per candle (1/3/5/10/15/30/45/60)", "1").option("-n, --count <n>", "Number of candles to display", "50").option("--raw", "Unadjusted (\uC218\uC815\uC8FC\uAC00 \uBBF8\uBC18\uC601) prices").option("-o, --output <format>", "Output format (table/json)", "table").action(async (code, options) => {
1334
+ chart.command("min <code>").alias("minute").description(`Minute chart (ka10080) \u2014 up to ${PER_PAGE_CAP.minute} candles/request, auto-paginates beyond that`).option("-i, --interval <n>", "Minutes per candle (1/3/5/10/15/30/45/60)", "1").option("-n, --count <n>", "Number of candles (auto-paginates when > 900)", "50").option("-p, --paginate", "Force fetching multiple pages (cont-yn/next-key)").option("--raw", "Unadjusted (\uC218\uC815\uC8FC\uAC00 \uBBF8\uBC18\uC601) prices").option("-o, --output <format>", "Output format (table/json)", "table").action(async (code, options) => {
1302
1335
  try {
1303
- await runIntraday(ENDPOINTS.minuteChart, code, options.interval, options);
1336
+ await runIntraday(ENDPOINTS.minuteChart, "minute", code, options.interval, options);
1304
1337
  } catch (err) {
1305
1338
  handleError(err);
1306
1339
  }
1307
1340
  });
1308
- chart.command("day <code>").alias("daily").description("Daily chart (ka10081)").option("-d, --date <yyyymmdd>", "Base date (most recent candle)", todayKst()).option("-n, --count <n>", "Number of candles to display", "50").option("--raw", "Unadjusted (\uC218\uC815\uC8FC\uAC00 \uBBF8\uBC18\uC601) prices").option("-o, --output <format>", "Output format (table/json)", "table").action(async (code, options) => {
1341
+ chart.command("day <code>").alias("daily").description(`Daily chart (ka10081) \u2014 up to ${PER_PAGE_CAP.day} candles/request, auto-paginates beyond that`).option("-d, --date <yyyymmdd>", "Base date (most recent candle)", todayKst()).option("-n, --count <n>", "Number of candles (auto-paginates when > 600)", "50").option("-p, --paginate", "Force fetching multiple pages (cont-yn/next-key)").option("--raw", "Unadjusted (\uC218\uC815\uC8FC\uAC00 \uBBF8\uBC18\uC601) prices").option("-o, --output <format>", "Output format (table/json)", "table").action(async (code, options) => {
1309
1342
  try {
1310
- await runPeriod(ENDPOINTS.dailyChart, code, options.date, options);
1343
+ await runPeriod(ENDPOINTS.dailyChart, "day", code, options.date, options);
1311
1344
  } catch (err) {
1312
1345
  handleError(err);
1313
1346
  }
1314
1347
  });
1315
- chart.command("week <code>").description("Weekly chart (ka10082)").option("-d, --date <yyyymmdd>", "Base date (most recent candle)", todayKst()).option("-n, --count <n>", "Number of candles to display", "50").option("--raw", "Unadjusted (\uC218\uC815\uC8FC\uAC00 \uBBF8\uBC18\uC601) prices").option("-o, --output <format>", "Output format (table/json)", "table").action(async (code, options) => {
1348
+ chart.command("week <code>").description(`Weekly chart (ka10082) \u2014 up to ${PER_PAGE_CAP.week} candles/request, auto-paginates beyond that`).option("-d, --date <yyyymmdd>", "Base date (most recent candle)", todayKst()).option("-n, --count <n>", "Number of candles (auto-paginates when > 300)", "50").option("-p, --paginate", "Force fetching multiple pages (cont-yn/next-key)").option("--raw", "Unadjusted (\uC218\uC815\uC8FC\uAC00 \uBBF8\uBC18\uC601) prices").option("-o, --output <format>", "Output format (table/json)", "table").action(async (code, options) => {
1316
1349
  try {
1317
- await runPeriod(ENDPOINTS.weeklyChart, code, options.date, options);
1350
+ await runPeriod(ENDPOINTS.weeklyChart, "week", code, options.date, options);
1318
1351
  } catch (err) {
1319
1352
  handleError(err);
1320
1353
  }
1321
1354
  });
1322
- chart.command("month <code>").description("Monthly chart (ka10083)").option("-d, --date <yyyymmdd>", "Base date (most recent candle)", todayKst()).option("-n, --count <n>", "Number of candles to display", "50").option("--raw", "Unadjusted (\uC218\uC815\uC8FC\uAC00 \uBBF8\uBC18\uC601) prices").option("-o, --output <format>", "Output format (table/json)", "table").action(async (code, options) => {
1355
+ chart.command("month <code>").description(`Monthly chart (ka10083) \u2014 up to ${PER_PAGE_CAP.month} candles/request, auto-paginates beyond that`).option("-d, --date <yyyymmdd>", "Base date (most recent candle)", todayKst()).option("-n, --count <n>", "Number of candles (auto-paginates when > 240)", "50").option("-p, --paginate", "Force fetching multiple pages (cont-yn/next-key)").option("--raw", "Unadjusted (\uC218\uC815\uC8FC\uAC00 \uBBF8\uBC18\uC601) prices").option("-o, --output <format>", "Output format (table/json)", "table").action(async (code, options) => {
1323
1356
  try {
1324
- await runPeriod(ENDPOINTS.monthlyChart, code, options.date, options);
1357
+ await runPeriod(ENDPOINTS.monthlyChart, "month", code, options.date, options);
1325
1358
  } catch (err) {
1326
1359
  handleError(err);
1327
1360
  }
1328
1361
  });
1329
- chart.command("year <code>").description("Yearly chart (ka10094)").option("-d, --date <yyyymmdd>", "Base date (most recent candle)", todayKst()).option("-n, --count <n>", "Number of candles to display", "50").option("--raw", "Unadjusted (\uC218\uC815\uC8FC\uAC00 \uBBF8\uBC18\uC601) prices").option("-o, --output <format>", "Output format (table/json)", "table").action(async (code, options) => {
1362
+ chart.command("year <code>").description(`Yearly chart (ka10094) \u2014 up to ${PER_PAGE_CAP.year} candles/request, auto-paginates beyond that`).option("-d, --date <yyyymmdd>", "Base date (most recent candle)", todayKst()).option("-n, --count <n>", "Number of candles (auto-paginates when > 30)", "50").option("-p, --paginate", "Force fetching multiple pages (cont-yn/next-key)").option("--raw", "Unadjusted (\uC218\uC815\uC8FC\uAC00 \uBBF8\uBC18\uC601) prices").option("-o, --output <format>", "Output format (table/json)", "table").action(async (code, options) => {
1330
1363
  try {
1331
- await runPeriod(ENDPOINTS.yearlyChart, code, options.date, options);
1364
+ await runPeriod(ENDPOINTS.yearlyChart, "year", code, options.date, options);
1332
1365
  } catch (err) {
1333
1366
  handleError(err);
1334
1367
  }
@@ -1756,6 +1789,21 @@ ${side.toUpperCase()} ${credit ? "(credit) " : ""}${stk} qty ${qty} ${priceLab
1756
1789
  await submit(def, body, options);
1757
1790
  }
1758
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
+
1759
1807
  // src/commands/ranking.ts
1760
1808
  function formatRankRow(row) {
1761
1809
  return formatFields(row, {
@@ -1857,6 +1905,45 @@ function registerRankingCommands(program2) {
1857
1905
  handleError(err);
1858
1906
  }
1859
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
+ });
1860
1947
  }
1861
1948
 
1862
1949
  // src/commands/sector.ts