@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,353 @@
1
+ /**
2
+ * Order tracker (apps/client)
3
+ *
4
+ * 책임 (사용자 사고 사례 방지):
5
+ * 1. **중복 주문 방지** · 같은 symbolCode 에 in-flight order 있으면 신규 거부
6
+ * 2. **주문 상태 머신** · pending → partial → filled / cancelled
7
+ * 3. **체결 polling** · placeOrder 후 N초 간격으로 getPendingOrders 조회 → 상태 갱신
8
+ * 4. **idempotency key** · 클라이언트 측 중복 호출 차단 (예: LLM 이 같은 결정을 2번 보냄)
9
+ *
10
+ * 사용자 명시 사고 사례 (taeeun-life 60일선):
11
+ * "주문 10주 → 체결 안 됨 → AI 재 wake → 10주 더 매도 → 결국 20주 매도"
12
+ *
13
+ * 방지 메커니즘:
14
+ * - placeOrderTracked() 가 OrderTracker 통해 lock 잡고 placeOrder 호출
15
+ * - 같은 symbol+side 의 active 주문 있으면 reject ("이미 X주 미체결 중 · M분 후 재시도")
16
+ * - 주문 후 자동 polling 시작 (10초 간격 · 최대 N분)
17
+ * - 체결 완료 또는 취소 시 lock 해제
18
+ *
19
+ * 영구화 (P+1):
20
+ * - 현재 인메모리 · 프로세스 재시작 시 in-flight 추적 손실
21
+ * - 향후 SQLite 또는 file based JSONL 영구화
22
+ */
23
+ import { randomUUID } from "node:crypto";
24
+ import { placeOrder } from "./endpoints/order.js";
25
+ import { getPendingOrders, getOrderableQty } from "./endpoints/account.js";
26
+ /** 호가 단위 (한국 KRX 기준 · 가격대별 다름) */
27
+ function tickSize(priceKrw) {
28
+ if (priceKrw < 1000)
29
+ return 1;
30
+ if (priceKrw < 5000)
31
+ return 5;
32
+ if (priceKrw < 10000)
33
+ return 10;
34
+ if (priceKrw < 50000)
35
+ return 50;
36
+ if (priceKrw < 100000)
37
+ return 100;
38
+ if (priceKrw < 500000)
39
+ return 500;
40
+ return 1000;
41
+ }
42
+ /** KST 기준 장 시간 09:00~15:30 (15:20 이후는 너울 hard 제약 · 여기는 Layer 1) */
43
+ export function isMarketOpen(now = new Date()) {
44
+ // KST = UTC+9
45
+ const kst = new Date(now.getTime() + 9 * 3600_000);
46
+ const day = kst.getUTCDay();
47
+ if (day === 0 || day === 6)
48
+ return false; // 일/토
49
+ const hh = kst.getUTCHours();
50
+ const mm = kst.getUTCMinutes();
51
+ const totalMin = hh * 60 + mm;
52
+ return totalMin >= 9 * 60 && totalMin <= 15 * 60 + 30;
53
+ }
54
+ class OrderTracker {
55
+ orders = new Map(); // clientOrderId → tracked
56
+ activeBySymbol = new Map(); // symbolCode:side → Set<clientOrderId>
57
+ pollers = new Map(); // clientOrderId → polling timer
58
+ /** 외부 콜백 (history 저장 · LLM wake 트리거 등) */
59
+ onStatusChange;
60
+ setStatusChangeCallback(cb) {
61
+ this.onStatusChange = cb;
62
+ }
63
+ /** 같은 symbol+side 의 active 주문 (pending · partial · submitting) 카운트 */
64
+ countActiveOrders(symbolCode, side) {
65
+ const ids = this.activeBySymbol.get(`${symbolCode}:${side}`);
66
+ if (!ids)
67
+ return 0;
68
+ let count = 0;
69
+ for (const id of ids) {
70
+ const o = this.orders.get(id);
71
+ if (!o)
72
+ continue;
73
+ if (o.status === "submitting" || o.status === "pending" || o.status === "partial") {
74
+ count++;
75
+ }
76
+ }
77
+ return count;
78
+ }
79
+ /** active 주문 목록 (디버깅용) */
80
+ list(opts = {}) {
81
+ return Array.from(this.orders.values()).filter((o) => {
82
+ if (opts.symbolCode && o.symbolCode !== opts.symbolCode)
83
+ return false;
84
+ if (opts.activeOnly && o.status !== "pending" && o.status !== "partial" && o.status !== "submitting") {
85
+ return false;
86
+ }
87
+ return true;
88
+ });
89
+ }
90
+ get(clientOrderId) {
91
+ return this.orders.get(clientOrderId);
92
+ }
93
+ stats() {
94
+ const activeBySymbol = {};
95
+ for (const [key, ids] of this.activeBySymbol) {
96
+ let count = 0;
97
+ for (const id of ids) {
98
+ const o = this.orders.get(id);
99
+ if (o && (o.status === "submitting" || o.status === "pending" || o.status === "partial")) {
100
+ count++;
101
+ }
102
+ }
103
+ if (count > 0)
104
+ activeBySymbol[key] = count;
105
+ }
106
+ return { total: this.orders.size, activeBySymbol };
107
+ }
108
+ /**
109
+ * 주문 등록 + 키움 호출 + 자동 polling 시작
110
+ *
111
+ * 중복 방지:
112
+ * - 같은 symbol+side 의 active 주문 있으면 reject (예외 throw 대신 KiwoomResult 반환)
113
+ *
114
+ * @param personaSlug · history 추적용 (null 가능)
115
+ */
116
+ async placeTrackedOrder(input, options = {}) {
117
+ // 1) 중복 방지
118
+ if (!options.allowDuplicate) {
119
+ const activeCount = this.countActiveOrders(input.symbolCode, input.side);
120
+ if (activeCount > 0) {
121
+ return {
122
+ ok: false,
123
+ error: {
124
+ kind: "business",
125
+ message: `중복 주문 거부 · ${input.symbolCode} ${input.side} 에 이미 ${activeCount}건 미체결/진행중 (allowDuplicate=true 로 우회 가능)`,
126
+ },
127
+ };
128
+ }
129
+ }
130
+ // 1.5) 사전 검증 (skipPreCheck 로 우회 가능)
131
+ if (!options.skipPreCheck) {
132
+ // 1.5a) 시장 시간 외 거부
133
+ if (!isMarketOpen()) {
134
+ return {
135
+ ok: false,
136
+ error: {
137
+ kind: "business",
138
+ message: "장 운영 시간 외 (KST 09:00~15:30 평일) · 주문 거부 (Layer 1 §3)",
139
+ },
140
+ };
141
+ }
142
+ // 1.5b) 호가 단위 검증
143
+ if (input.priceKrw && input.priceKrw > 0) {
144
+ const tick = tickSize(input.priceKrw);
145
+ if (input.priceKrw % tick !== 0) {
146
+ return {
147
+ ok: false,
148
+ error: {
149
+ kind: "schema",
150
+ message: `호가 단위 위반 · ${input.priceKrw}원 → ${tick}원 단위 (가격대 ${input.priceKrw} 의 정상 호가)`,
151
+ },
152
+ };
153
+ }
154
+ }
155
+ // 1.5c) 잔고/수량 검증 (매수 시 가능 금액 · 매도 시 보유 수량)
156
+ const orderableRes = await getOrderableQty(input.symbolCode, input.priceKrw);
157
+ if (orderableRes.ok) {
158
+ if (input.side === "buy" && input.quantity > orderableRes.data.buyableQty) {
159
+ return {
160
+ ok: false,
161
+ error: {
162
+ kind: "business",
163
+ message: `매수 가능 수량 초과 · 요청 ${input.quantity}주 / 가능 ${orderableRes.data.buyableQty}주 (예수금 ${orderableRes.data.buyableAmountKrw.toLocaleString()}원)`,
164
+ },
165
+ };
166
+ }
167
+ if (input.side === "sell" && input.quantity > orderableRes.data.sellableQty) {
168
+ return {
169
+ ok: false,
170
+ error: {
171
+ kind: "business",
172
+ message: `매도 가능 수량 초과 · 요청 ${input.quantity}주 / 가능 ${orderableRes.data.sellableQty}주 (보유 - 미체결매도)`,
173
+ },
174
+ };
175
+ }
176
+ }
177
+ // orderable 조회 실패 시 검증 skip · 키움이 거부할 것
178
+ }
179
+ // 2) tracked order 생성
180
+ const clientOrderId = `co_${randomUUID().slice(0, 12)}`;
181
+ const tracked = {
182
+ clientOrderId,
183
+ brokerOrderNo: null,
184
+ symbolCode: input.symbolCode,
185
+ side: input.side,
186
+ intendedQty: input.quantity,
187
+ intendedPriceKrw: input.priceKrw ?? 0,
188
+ status: "submitting",
189
+ filledQty: 0,
190
+ submittedAt: new Date().toISOString(),
191
+ lastSyncedAt: new Date().toISOString(),
192
+ pollCount: 0,
193
+ personaSlug: options.personaSlug ?? null,
194
+ };
195
+ this.orders.set(clientOrderId, tracked);
196
+ this.addToActiveSymbol(tracked);
197
+ // 3) 키움 호출
198
+ const res = await placeOrder(input);
199
+ if (!res.ok) {
200
+ tracked.status = "rejected";
201
+ tracked.error = res.error.message;
202
+ tracked.lastSyncedAt = new Date().toISOString();
203
+ this.removeFromActiveSymbol(tracked);
204
+ await this.notifyStatusChange(tracked, "submitting");
205
+ return { ok: false, error: res.error };
206
+ }
207
+ // 4) tracked 갱신
208
+ tracked.brokerOrderNo = res.data.orderNo;
209
+ tracked.status = "pending";
210
+ tracked.lastSyncedAt = new Date().toISOString();
211
+ await this.notifyStatusChange(tracked, "submitting");
212
+ // 5) polling 시작
213
+ this.startPolling(clientOrderId);
214
+ return { ok: true, data: tracked };
215
+ }
216
+ /** clientOrderId 단위 polling · 10초 간격 · 최대 30회 (5분) */
217
+ startPolling(clientOrderId) {
218
+ if (this.pollers.has(clientOrderId))
219
+ return;
220
+ const POLL_INTERVAL_MS = 10_000;
221
+ const MAX_POLLS = 30;
222
+ const timer = setInterval(() => {
223
+ void this.pollOnce(clientOrderId).catch((e) => {
224
+ console.error(`[OrderTracker] poll ${clientOrderId} err:`, e);
225
+ });
226
+ }, POLL_INTERVAL_MS);
227
+ this.pollers.set(clientOrderId, timer);
228
+ // 첫 polling 즉시 시도 (1초 후 · 키움 처리 시간 고려)
229
+ setTimeout(() => {
230
+ void this.pollOnce(clientOrderId).catch(() => { });
231
+ }, 1000);
232
+ // MAX_POLLS 초과 시 자동 중단
233
+ setTimeout(() => {
234
+ const order = this.orders.get(clientOrderId);
235
+ if (order && (order.status === "pending" || order.status === "partial")) {
236
+ console.warn(`[OrderTracker] ${clientOrderId} polling timeout · ${MAX_POLLS}회 시도 · 마지막 상태 ${order.status}`);
237
+ }
238
+ this.stopPolling(clientOrderId);
239
+ }, POLL_INTERVAL_MS * MAX_POLLS + 5000);
240
+ }
241
+ stopPolling(clientOrderId) {
242
+ const timer = this.pollers.get(clientOrderId);
243
+ if (timer) {
244
+ clearInterval(timer);
245
+ this.pollers.delete(clientOrderId);
246
+ }
247
+ }
248
+ /**
249
+ * Graceful shutdown: 모든 polling timer 정리.
250
+ * 활성 주문 자체 (메모리 + 키움 broker 측 brokerOrderNo) 는 유지 — restart 후 재polling 가능.
251
+ */
252
+ pauseAllPolling() {
253
+ let paused = 0;
254
+ for (const id of Array.from(this.pollers.keys())) {
255
+ this.stopPolling(id);
256
+ paused++;
257
+ }
258
+ if (paused > 0) {
259
+ console.log(`[OrderTracker] paused ${paused} polling timers (orders preserved in memory)`);
260
+ }
261
+ }
262
+ async pollOnce(clientOrderId) {
263
+ const order = this.orders.get(clientOrderId);
264
+ if (!order || !order.brokerOrderNo)
265
+ return;
266
+ if (order.status === "filled" || order.status === "cancelled" || order.status === "rejected") {
267
+ this.stopPolling(clientOrderId);
268
+ return;
269
+ }
270
+ order.pollCount++;
271
+ order.lastSyncedAt = new Date().toISOString();
272
+ // 미체결 조회 · 같은 symbol 만
273
+ const pendingRes = await getPendingOrders(order.symbolCode);
274
+ if (!pendingRes.ok) {
275
+ console.warn(`[OrderTracker] ${clientOrderId} poll fetch fail · ${pendingRes.error.message}`);
276
+ return;
277
+ }
278
+ const matched = pendingRes.data.find((p) => p.orderNo === order.brokerOrderNo);
279
+ const prevStatus = order.status;
280
+ if (!matched) {
281
+ // 미체결 목록에 없음 = 전량 체결 또는 취소
282
+ // 추가로 체결 내역 조회로 확인 가능 (선택 · 비용 절감 위해 일단 filled 가정)
283
+ order.status = "filled";
284
+ order.filledQty = order.intendedQty;
285
+ this.removeFromActiveSymbol(order);
286
+ this.stopPolling(clientOrderId);
287
+ }
288
+ else {
289
+ order.filledQty = matched.filledQty;
290
+ order.status = matched.status === "partial" ? "partial" : "pending";
291
+ }
292
+ if (order.status !== prevStatus) {
293
+ await this.notifyStatusChange(order, prevStatus);
294
+ const terminalStatuses = ["filled", "cancelled"];
295
+ if (terminalStatuses.includes(order.status)) {
296
+ this.removeFromActiveSymbol(order);
297
+ this.stopPolling(clientOrderId);
298
+ }
299
+ }
300
+ }
301
+ addToActiveSymbol(order) {
302
+ const key = `${order.symbolCode}:${order.side}`;
303
+ let set = this.activeBySymbol.get(key);
304
+ if (!set) {
305
+ set = new Set();
306
+ this.activeBySymbol.set(key, set);
307
+ }
308
+ set.add(order.clientOrderId);
309
+ }
310
+ removeFromActiveSymbol(order) {
311
+ const key = `${order.symbolCode}:${order.side}`;
312
+ const set = this.activeBySymbol.get(key);
313
+ if (!set)
314
+ return;
315
+ set.delete(order.clientOrderId);
316
+ if (set.size === 0)
317
+ this.activeBySymbol.delete(key);
318
+ }
319
+ async notifyStatusChange(order, prev) {
320
+ if (!this.onStatusChange)
321
+ return;
322
+ try {
323
+ await this.onStatusChange(order, prev);
324
+ }
325
+ catch (e) {
326
+ console.error(`[OrderTracker] onStatusChange callback err:`, e);
327
+ }
328
+ }
329
+ /** 외부 강제 cancel (UI 또는 LLM) · 키움 cancelOrder 별도 호출 후 본 함수 */
330
+ markCancelled(clientOrderId, reason) {
331
+ const order = this.orders.get(clientOrderId);
332
+ if (!order)
333
+ return;
334
+ const prev = order.status;
335
+ order.status = "cancelled";
336
+ order.error = reason;
337
+ order.lastSyncedAt = new Date().toISOString();
338
+ this.removeFromActiveSymbol(order);
339
+ this.stopPolling(clientOrderId);
340
+ void this.notifyStatusChange(order, prev);
341
+ }
342
+ }
343
+ let singleton = null;
344
+ export function getOrderTracker() {
345
+ if (!singleton)
346
+ singleton = new OrderTracker();
347
+ return singleton;
348
+ }
349
+ /** placeTrackedOrder + journal 자동 호출 헬퍼 (외부에서 한 줄로 사용) */
350
+ export async function placeOrderWithTracking(input, options = {}) {
351
+ return getOrderTracker().placeTrackedOrder(input, options);
352
+ }
353
+ //# sourceMappingURL=order-tracker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-tracker.js","sourceRoot":"","sources":["../../../../src-server/broker/kiwoom/order-tracker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,119 @@
1
+ import { getCurrentPrice } from "./endpoints/quote.js";
2
+ const TICK_INTERVAL_MS = 10_000;
3
+ const STAGGER_MS = 200;
4
+ class PriceFeed {
5
+ /** stockCode → Map<watcherId, SubscriberInfo> */
6
+ subscribers = new Map();
7
+ timer = null;
8
+ running = false;
9
+ start() {
10
+ if (this.timer)
11
+ return;
12
+ this.timer = setInterval(() => {
13
+ void this.tick();
14
+ }, TICK_INTERVAL_MS);
15
+ console.log(`[PriceFeed] started · tick ${TICK_INTERVAL_MS}ms · stagger ${STAGGER_MS}ms`);
16
+ }
17
+ stop() {
18
+ if (this.timer)
19
+ clearInterval(this.timer);
20
+ this.timer = null;
21
+ console.log("[PriceFeed] stopped");
22
+ }
23
+ subscribe(symbolCode, watcherId, intervalMs, callback) {
24
+ let subs = this.subscribers.get(symbolCode);
25
+ if (!subs) {
26
+ subs = new Map();
27
+ this.subscribers.set(symbolCode, subs);
28
+ }
29
+ subs.set(watcherId, { callback, intervalMs, lastDeliveredAt: 0 });
30
+ console.log(`[PriceFeed] subscribe ${symbolCode}/${watcherId} (intervalMs=${intervalMs}) · 종목 ${this.subscribers.size} · 구독자 ${this.totalSubscribers()}`);
31
+ }
32
+ unsubscribe(symbolCode, watcherId) {
33
+ const subs = this.subscribers.get(symbolCode);
34
+ if (!subs)
35
+ return;
36
+ subs.delete(watcherId);
37
+ if (subs.size === 0) {
38
+ this.subscribers.delete(symbolCode);
39
+ }
40
+ console.log(`[PriceFeed] unsubscribe ${symbolCode}/${watcherId} · 종목 ${this.subscribers.size} · 구독자 ${this.totalSubscribers()}`);
41
+ }
42
+ /** 디버깅용 stat */
43
+ stats() {
44
+ return {
45
+ stocks: this.subscribers.size,
46
+ totalSubscribers: this.totalSubscribers(),
47
+ running: this.timer !== null,
48
+ tickInProgress: this.running,
49
+ };
50
+ }
51
+ totalSubscribers() {
52
+ let total = 0;
53
+ for (const subs of this.subscribers.values())
54
+ total += subs.size;
55
+ return total;
56
+ }
57
+ async tick() {
58
+ if (this.running) {
59
+ // 이전 tick 이 아직 진행 중 (느린 응답) · 이번 tick skip
60
+ console.warn("[PriceFeed] tick overlap · skip");
61
+ return;
62
+ }
63
+ if (this.subscribers.size === 0)
64
+ return;
65
+ this.running = true;
66
+ let count = 0;
67
+ try {
68
+ for (const [code, subs] of this.subscribers) {
69
+ if (subs.size === 0)
70
+ continue;
71
+ if (count > 0) {
72
+ await sleep(STAGGER_MS);
73
+ }
74
+ count++;
75
+ try {
76
+ const res = await getCurrentPrice(code);
77
+ if (!res.ok) {
78
+ console.error(`[PriceFeed] ${code} fetch fail · ${res.error.kind} · ${res.error.message}`);
79
+ continue;
80
+ }
81
+ await this.deliverToSubscribers(code, res.data);
82
+ }
83
+ catch (e) {
84
+ console.error(`[PriceFeed] ${code} unexpected:`, e);
85
+ }
86
+ }
87
+ }
88
+ finally {
89
+ this.running = false;
90
+ }
91
+ }
92
+ async deliverToSubscribers(code, data) {
93
+ const subs = this.subscribers.get(code);
94
+ if (!subs)
95
+ return;
96
+ const now = Date.now();
97
+ for (const [watcherId, info] of subs) {
98
+ if (now - info.lastDeliveredAt < info.intervalMs)
99
+ continue;
100
+ info.lastDeliveredAt = now;
101
+ try {
102
+ await info.callback(data);
103
+ }
104
+ catch (e) {
105
+ console.error(`[PriceFeed] deliver ${code}/${watcherId} callback err:`, e);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ let singleton = null;
111
+ export function getPriceFeed() {
112
+ if (!singleton)
113
+ singleton = new PriceFeed();
114
+ return singleton;
115
+ }
116
+ function sleep(ms) {
117
+ return new Promise((res) => setTimeout(res, ms));
118
+ }
119
+ //# sourceMappingURL=price-feed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"price-feed.js","sourceRoot":"","sources":["../../../../src-server/broker/kiwoom/price-feed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Token bucket rate limiter for Kiwoom REST API.
3
+ *
4
+ * 키움 제한:
5
+ * - 실전 (api.kiwoom.com) · 초당 20 회
6
+ * - 모의 (mockapi.kiwoom.com) · 초당 2 회
7
+ *
8
+ * 안전 마진:
9
+ * - 실전: 5 tokens/sec (200ms stagger · 타 페르소나 동시 호출 여유)
10
+ * - 모의: 1.5 tokens/sec (모의 2/s 의 75% · spike 회피)
11
+ *
12
+ * 동작:
13
+ * - take(): 토큰이 있으면 즉시 반환 (consume 1) · 없으면 fill 대기 후 반환
14
+ * - 호출 간 stagger 보장 (Promise queue) · 동시성 안전
15
+ *
16
+ * 사용:
17
+ * const limiter = getRateLimiter(isDemo);
18
+ * await limiter.take();
19
+ * await fetch(...);
20
+ */
21
+ const CONFIG = {
22
+ live: {
23
+ capacity: 20,
24
+ refillPerSec: 5, // 실전 20/s 의 25% · 안전 마진
25
+ minIntervalMs: 100, // 다중 페르소나 동시 호출 여유
26
+ },
27
+ demo: {
28
+ capacity: 4,
29
+ refillPerSec: 1.5, // 모의 2/s 의 75% · spike 회피
30
+ minIntervalMs: 500, // 모의는 보수적
31
+ },
32
+ };
33
+ class TokenBucket {
34
+ cfg;
35
+ tokens;
36
+ lastRefillAt;
37
+ lastTakeAt = 0;
38
+ waitChain = Promise.resolve();
39
+ constructor(cfg) {
40
+ this.cfg = cfg;
41
+ this.tokens = cfg.capacity;
42
+ this.lastRefillAt = Date.now();
43
+ }
44
+ refill() {
45
+ const now = Date.now();
46
+ const elapsed = (now - this.lastRefillAt) / 1000;
47
+ this.tokens = Math.min(this.cfg.capacity, this.tokens + elapsed * this.cfg.refillPerSec);
48
+ this.lastRefillAt = now;
49
+ }
50
+ /** 호출 슬롯 1개 확보 (대기 가능) · 반환 시 호출 가능 상태 */
51
+ async take() {
52
+ // 직렬화: 동시 take 가 와도 순서대로 stagger 보장
53
+ const previous = this.waitChain;
54
+ let release;
55
+ this.waitChain = new Promise((res) => { release = res; });
56
+ try {
57
+ await previous;
58
+ // 1) stagger 보장
59
+ const sinceLastTake = Date.now() - this.lastTakeAt;
60
+ if (sinceLastTake < this.cfg.minIntervalMs) {
61
+ await sleep(this.cfg.minIntervalMs - sinceLastTake);
62
+ }
63
+ // 2) 토큰 확보 (없으면 refill 대기)
64
+ this.refill();
65
+ while (this.tokens < 1) {
66
+ // 1 토큰 보충에 필요한 시간
67
+ const waitMs = Math.ceil((1 - this.tokens) / this.cfg.refillPerSec * 1000);
68
+ await sleep(Math.max(50, waitMs));
69
+ this.refill();
70
+ }
71
+ this.tokens -= 1;
72
+ this.lastTakeAt = Date.now();
73
+ }
74
+ finally {
75
+ release();
76
+ }
77
+ }
78
+ /** 현재 잔여 토큰 (디버깅용 · 실시간 정확치 아님) */
79
+ remaining() {
80
+ this.refill();
81
+ return Math.floor(this.tokens);
82
+ }
83
+ }
84
+ const buckets = new Map();
85
+ export function getRateLimiter(isDemo) {
86
+ const env = isDemo ? "demo" : "live";
87
+ let bucket = buckets.get(env);
88
+ if (!bucket) {
89
+ bucket = new TokenBucket(CONFIG[env]);
90
+ buckets.set(env, bucket);
91
+ }
92
+ return bucket;
93
+ }
94
+ function sleep(ms) {
95
+ return new Promise((res) => setTimeout(res, ms));
96
+ }
97
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../../../src-server/broker/kiwoom/rate-limiter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 키움 REST / WebSocket API 공통 타입 (apps/client)
3
+ *
4
+ * 키움 응답 envelope:
5
+ * { return_code: number, return_msg: string, ...payload }
6
+ * - return_code === 0 → 성공
7
+ * - 그 외 → 비즈니스 에러 (return_msg 에 사유)
8
+ *
9
+ * 공식 스펙: docs/kiwoom/키움 REST API 문서.pdf
10
+ * - REST 186 TR · WebSocket 4 TR (조건검색)
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src-server/broker/kiwoom/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}