@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,108 @@
1
+ /**
2
+ * 일별/누적 손익 집계 (apps/client)
3
+ *
4
+ * trade-journal 의 order_status (filled) 이벤트를 FIFO 매칭으로 실현 손익 계산.
5
+ * MVP 단순화: 종목별 매수가 평균 vs 매도가 차이 (정확한 lot 단위 FIFO 는 P+1).
6
+ */
7
+ import { readEvents } from "./trade-journal.js";
8
+ /** 날짜별 실현 손익 (단순화 · 종목별 매수가 평균 기준) */
9
+ export function computeDailyPnl(opts) {
10
+ const events = readEvents({
11
+ from: opts.from,
12
+ to: opts.to,
13
+ personaSlug: opts.personaSlug,
14
+ eventTypes: ["order_status"],
15
+ limit: 100_000,
16
+ });
17
+ // 종목별 매수가 누적 평균
18
+ const symbolAvgPrice = new Map();
19
+ // 날짜별 집계
20
+ const byDate = new Map();
21
+ const bySymbol = {};
22
+ let totalBuy = 0;
23
+ let totalSell = 0;
24
+ let totalRealized = 0;
25
+ for (const ev of events) {
26
+ if (ev.meta?.next !== "filled")
27
+ continue;
28
+ if (!ev.symbolCode || !ev.side)
29
+ continue;
30
+ const qty = ev.filledQty ?? ev.quantity ?? 0;
31
+ const price = ev.priceKrw ?? 0;
32
+ if (qty <= 0 || price <= 0)
33
+ continue;
34
+ const amount = qty * price;
35
+ const date = ev.timestamp.slice(0, 10);
36
+ if (!byDate.has(date)) {
37
+ byDate.set(date, {
38
+ date,
39
+ realizedPnlKrw: 0,
40
+ buyAmountKrw: 0,
41
+ sellAmountKrw: 0,
42
+ tradeCount: 0,
43
+ symbols: [],
44
+ });
45
+ }
46
+ const day = byDate.get(date);
47
+ day.tradeCount++;
48
+ if (!day.symbols.includes(ev.symbolCode))
49
+ day.symbols.push(ev.symbolCode);
50
+ if (!bySymbol[ev.symbolCode])
51
+ bySymbol[ev.symbolCode] = { pnl: 0, trades: 0 };
52
+ bySymbol[ev.symbolCode].trades++;
53
+ if (ev.side === "buy") {
54
+ totalBuy += amount;
55
+ day.buyAmountKrw += amount;
56
+ const existing = symbolAvgPrice.get(ev.symbolCode) ?? { totalQty: 0, totalCost: 0 };
57
+ existing.totalQty += qty;
58
+ existing.totalCost += amount;
59
+ symbolAvgPrice.set(ev.symbolCode, existing);
60
+ }
61
+ else if (ev.side === "sell") {
62
+ totalSell += amount;
63
+ day.sellAmountKrw += amount;
64
+ const existing = symbolAvgPrice.get(ev.symbolCode);
65
+ if (existing && existing.totalQty > 0) {
66
+ const avgPrice = existing.totalCost / existing.totalQty;
67
+ const pnl = (price - avgPrice) * qty;
68
+ day.realizedPnlKrw += pnl;
69
+ bySymbol[ev.symbolCode].pnl += pnl;
70
+ totalRealized += pnl;
71
+ // 매도분만큼 평균 cost 차감
72
+ existing.totalQty = Math.max(0, existing.totalQty - qty);
73
+ existing.totalCost = existing.totalQty > 0 ? existing.totalCost - avgPrice * qty : 0;
74
+ }
75
+ }
76
+ }
77
+ const sortedDates = Array.from(byDate.values()).sort((a, b) => a.date.localeCompare(b.date));
78
+ return {
79
+ fromDate: sortedDates[0]?.date ?? "",
80
+ toDate: sortedDates[sortedDates.length - 1]?.date ?? "",
81
+ totalRealizedPnlKrw: Math.round(totalRealized),
82
+ totalBuyAmountKrw: totalBuy,
83
+ totalSellAmountKrw: totalSell,
84
+ totalTrades: events.length,
85
+ bySymbol,
86
+ byDate: sortedDates,
87
+ };
88
+ }
89
+ export function replayJournal(opts) {
90
+ const events = readEvents({ ...opts, limit: 100_000 });
91
+ const decisionsBySymbol = {};
92
+ const ordersByStatus = {};
93
+ let watcherTriggers = 0;
94
+ for (const ev of events) {
95
+ if (ev.eventType === "decision" && ev.symbolCode) {
96
+ decisionsBySymbol[ev.symbolCode] = (decisionsBySymbol[ev.symbolCode] ?? 0) + 1;
97
+ }
98
+ else if (ev.eventType === "order_status") {
99
+ const status = ev.meta?.next ?? "unknown";
100
+ ordersByStatus[status] = (ordersByStatus[status] ?? 0) + 1;
101
+ }
102
+ else if (ev.eventType === "watcher_triggered") {
103
+ watcherTriggers++;
104
+ }
105
+ }
106
+ return { totalEvents: events.length, decisionsBySymbol, ordersByStatus, watcherTriggers };
107
+ }
108
+ //# sourceMappingURL=pnl-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pnl-stats.js","sourceRoot":"","sources":["../../../src-server/journal/pnl-stats.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Telemetry sender (apps/client)
3
+ *
4
+ * 책임: trade-journal 의 이벤트를 서버 (mulsok-traders.vercel.app/api/events/trade) 로 송신
5
+ *
6
+ * 정책 (Layer 1 §5 c1.4.0):
7
+ * - 자격증명 (API key · secret · token · 비밀번호) 절대 송신 금지
8
+ * - 거래 이벤트 (action · symbol · qty · price · pattern · pnl 등) 송신 OK
9
+ * - device_token 인증 헤더 필수
10
+ *
11
+ * 동작:
12
+ * - best-effort 송신 · 실패해도 로컬 저널은 항상 유지
13
+ * - 단발 송신 · batch 도 가능
14
+ * - 60s flush queue (P+1) · 현재는 즉시 호출
15
+ *
16
+ * 호출자:
17
+ * - trade-journal logXxx() 헬퍼 안에서 자동 호출 (옵션)
18
+ * - 또는 별도 flush() 호출
19
+ */
20
+ import { loadConfig, SERVER_BASE_URL } from "../config.js";
21
+ import { maskSymbolName, maskSymbolsInText } from "../server-sync.js";
22
+ /**
23
+ * SPEC-20 + ADR-008 익명화 — server 송신 전 적용.
24
+ *
25
+ * 송신 X (마스킹/제거):
26
+ * - symbolCode (6자리 숫자) → 제거
27
+ * - symbolName → 마스킹 (뉴프**)
28
+ * - priceKrw / filledPriceKrw / brokerOrderNo / clientOrderId → 제거
29
+ * - llmContext.reasoning → 마스킹된 발췌 (200자)
30
+ * - llmContext.screening_summary.top[].code/name/rawMetrics → 제거 (반응만 일반화)
31
+ * - llmContext.layer3_snapshot.holdings/pendingOrders → 제거 (잔고)
32
+ *
33
+ * 송신 OK:
34
+ * - persona slug · eventType · timestamp · side · quantity (수량) · symbolBucket
35
+ * - llmContext.lesson · deviation_note · pattern · confidence (자연어 메타)
36
+ * - meta (status 전이 등)
37
+ */
38
+ function anonymizeForTelemetry(ev) {
39
+ const ctx = ev.llmContext;
40
+ let anonContext;
41
+ if (ctx) {
42
+ anonContext = {
43
+ // 자연어 메타 (lesson · 패턴 · 가산점 라벨) — server 가 페르소나 성과 분석 가능
44
+ lesson: ctx.lesson,
45
+ pattern: ctx.pattern,
46
+ confidence: ctx.confidence,
47
+ deviationNote: ctx.deviationNote,
48
+ model: ctx.model,
49
+ tokens: ctx.tokens,
50
+ // reasoning 발췌만 (마스킹 + 200자)
51
+ reasoningExcerpt: typeof ctx.reasoning === "string"
52
+ ? maskSymbolsInText(ctx.reasoning).slice(0, 200)
53
+ : undefined,
54
+ // screening 메타 카운트만 (top 후보 정량 데이터 X)
55
+ screening: ctx.screening_summary
56
+ ? {
57
+ universe: ctx.screening_summary.universe,
58
+ evaluated: ctx.screening_summary.evaluated,
59
+ candidates: ctx.screening_summary.candidates,
60
+ elapsedMs: ctx.screening_summary.elapsedMs,
61
+ }
62
+ : undefined,
63
+ // triggered_by 는 watcherId · intent 만 (intent 마스킹)
64
+ triggeredBy: ctx.triggered_by
65
+ ? {
66
+ watcherId: ctx.triggered_by.watcherId,
67
+ intent: typeof ctx.triggered_by.intent === "string"
68
+ ? maskSymbolsInText(ctx.triggered_by.intent)
69
+ : undefined,
70
+ }
71
+ : undefined,
72
+ // layer3_snapshot 은 시장 지수만 (보유 · 미체결 · watcher 종목 코드 X)
73
+ marketIndex: ctx.layer3_snapshot
74
+ ? ctx.layer3_snapshot.marketIndex
75
+ : undefined,
76
+ };
77
+ }
78
+ return {
79
+ occurredAt: ev.timestamp,
80
+ eventType: ev.eventType,
81
+ personaSlug: ev.personaSlug,
82
+ // 종목 코드 송신 X · 이름은 마스킹 · bucket 만 OK (대형/중형/소형)
83
+ symbolNameMasked: ev.symbolName ? maskSymbolName(ev.symbolName) : undefined,
84
+ symbolBucket: ev.symbolBucket,
85
+ side: ev.side,
86
+ quantity: ev.quantity,
87
+ filledQty: ev.filledQty,
88
+ // 가격 · 주문번호 · client id 모두 송신 X (잔고 · 자격증명 인접)
89
+ llmContext: anonContext,
90
+ meta: ev.meta,
91
+ // error 는 마스킹된 메시지만
92
+ error: ev.error ? maskSymbolsInText(ev.error) : undefined,
93
+ };
94
+ }
95
+ class TelemetrySender {
96
+ queue = [];
97
+ flushing = false;
98
+ MAX_RETRIES = 2;
99
+ MAX_BATCH = 50;
100
+ /** 이벤트 큐에 추가 · 즉시 백그라운드 flush 시도 */
101
+ enqueue(ev) {
102
+ this.queue.push({ ev, retries: 0 });
103
+ void this.flush();
104
+ }
105
+ /** 큐 비우기 (best-effort · 실패 시 retry queue 유지) */
106
+ async flush() {
107
+ if (this.flushing)
108
+ return;
109
+ if (this.queue.length === 0)
110
+ return;
111
+ const cfg = loadConfig();
112
+ const token = cfg.auth?.deviceToken;
113
+ if (!token || !token.startsWith("mtd_")) {
114
+ // device 토큰 없음 · 로컬에만 저장 (큐 유지 · 다음 호출 시 재시도)
115
+ return;
116
+ }
117
+ this.flushing = true;
118
+ try {
119
+ const batch = this.queue.splice(0, this.MAX_BATCH);
120
+ // SPEC-20 + ADR-008: 종목 코드 · 가격 · 잔고 · 자격증명 송신 금지.
121
+ // anonymizeForTelemetry 가 마스킹 + 필드 제거 + 풍부 컨텍스트는 메타 카운트만 보존.
122
+ const events = batch.map((q) => anonymizeForTelemetry(q.ev));
123
+ try {
124
+ const url = `${SERVER_BASE_URL.replace(/\/$/, "")}/api/events/trade`;
125
+ const res = await fetch(url, {
126
+ method: "POST",
127
+ headers: {
128
+ "content-type": "application/json",
129
+ authorization: `Bearer ${token}`,
130
+ },
131
+ body: JSON.stringify({ events }),
132
+ });
133
+ if (!res.ok) {
134
+ // 401/4xx 는 재시도 의미 없음 · 5xx 는 재시도
135
+ if (res.status >= 500) {
136
+ this.requeue(batch);
137
+ }
138
+ else {
139
+ console.warn(`[telemetry] HTTP ${res.status} · 폐기 ${batch.length} 이벤트`);
140
+ }
141
+ }
142
+ }
143
+ catch (e) {
144
+ // 네트워크 실패 · retry
145
+ this.requeue(batch);
146
+ console.warn(`[telemetry] 네트워크 실패 · ${String(e)} · ${batch.length} 이벤트 retry queue`);
147
+ }
148
+ }
149
+ finally {
150
+ this.flushing = false;
151
+ }
152
+ }
153
+ requeue(batch) {
154
+ for (const item of batch) {
155
+ if (item.retries < this.MAX_RETRIES) {
156
+ item.retries++;
157
+ this.queue.push(item);
158
+ }
159
+ else {
160
+ console.warn(`[telemetry] MAX_RETRIES 초과 · 폐기 ${item.ev.eventType} · ${item.ev.symbolCode ?? ""}`);
161
+ }
162
+ }
163
+ }
164
+ stats() {
165
+ return { queueSize: this.queue.length, flushing: this.flushing };
166
+ }
167
+ }
168
+ let singleton = null;
169
+ export function getTelemetry() {
170
+ if (!singleton)
171
+ singleton = new TelemetrySender();
172
+ return singleton;
173
+ }
174
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../../../src-server/journal/telemetry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Trade Journal (apps/client)
3
+ *
4
+ * 책임: 모든 거래 이벤트를 로컬 영구 저장 (재시작 후 유지)
5
+ *
6
+ * 저장소: JSONL (JSON Lines · 한 줄 = 한 이벤트)
7
+ * - 경로: ~/.mulsok-traders/trade-journal/<yyyy-mm>.jsonl
8
+ * - 월별 파일 분리 (1년 12 파일 · grep 용이)
9
+ * - append-only · 수정 금지 (감사 무결성)
10
+ * - 0600 권한 (config.json 과 동일)
11
+ *
12
+ * 이벤트 종류:
13
+ * - decision · LLM 결정 (action · symbol · qty · reasoning · lesson)
14
+ * - order_submitted · placeOrder 직후
15
+ * - order_status · pending → partial → filled 전환
16
+ * - order_cancelled · 취소
17
+ * - order_rejected · 키움 거부
18
+ * - watcher_triggered · 감시자 트리거
19
+ *
20
+ * 검색/필터: read 함수가 날짜·종목·페르소나·종류로 필터
21
+ *
22
+ * 향후 (P+1):
23
+ * - SQLite 마이그레이션 (검색·집계 성능)
24
+ * - 서버 telemetry 송신 (별도 모듈)
25
+ */
26
+ import fs from "node:fs";
27
+ import path from "node:path";
28
+ import os from "node:os";
29
+ const JOURNAL_DIR = path.join(os.homedir(), ".mulsok-traders", "trade-journal");
30
+ function ensureDir() {
31
+ if (!fs.existsSync(JOURNAL_DIR)) {
32
+ fs.mkdirSync(JOURNAL_DIR, { recursive: true, mode: 0o700 });
33
+ }
34
+ }
35
+ function fileForDate(date) {
36
+ const y = date.getFullYear();
37
+ const m = String(date.getMonth() + 1).padStart(2, "0");
38
+ return path.join(JOURNAL_DIR, `${y}-${m}.jsonl`);
39
+ }
40
+ /** 이벤트 1개 append (단일 라인 · sync) + telemetry best-effort 송신 */
41
+ export function appendEvent(event) {
42
+ ensureDir();
43
+ const file = fileForDate(new Date(event.timestamp));
44
+ const line = JSON.stringify(event) + "\n";
45
+ try {
46
+ fs.appendFileSync(file, line, { mode: 0o600 });
47
+ }
48
+ catch (e) {
49
+ console.error(`[trade-journal] append fail · ${file} · ${e}`);
50
+ }
51
+ // 서버 telemetry 송신 · device_token 있을 때만 (로컬은 항상 저장)
52
+ // dynamic import 로 순환 의존 회피
53
+ import("./telemetry.js")
54
+ .then(({ getTelemetry }) => getTelemetry().enqueue(event))
55
+ .catch(() => {
56
+ /* ignore */
57
+ });
58
+ }
59
+ /** 헬퍼: 자동 timestamp + persona */
60
+ export function logDecision(opts) {
61
+ appendEvent({
62
+ timestamp: new Date().toISOString(),
63
+ eventType: "decision",
64
+ ...opts,
65
+ });
66
+ }
67
+ export function logOrderSubmitted(opts) {
68
+ appendEvent({
69
+ timestamp: new Date().toISOString(),
70
+ eventType: "order_submitted",
71
+ ...opts,
72
+ });
73
+ }
74
+ export function logOrderStatus(opts) {
75
+ appendEvent({
76
+ timestamp: new Date().toISOString(),
77
+ eventType: "order_status",
78
+ ...opts,
79
+ });
80
+ }
81
+ export function logOrderRejected(opts) {
82
+ appendEvent({
83
+ timestamp: new Date().toISOString(),
84
+ eventType: "order_rejected",
85
+ ...opts,
86
+ });
87
+ }
88
+ export function logWatcherTriggered(opts) {
89
+ appendEvent({
90
+ timestamp: new Date().toISOString(),
91
+ eventType: "watcher_triggered",
92
+ personaSlug: opts.personaSlug,
93
+ meta: {
94
+ watcherId: opts.watcherId,
95
+ intent: opts.intent,
96
+ triggerData: opts.triggerData,
97
+ },
98
+ });
99
+ }
100
+ export function readEvents(filter = {}) {
101
+ const from = filter.from ? new Date(filter.from) : new Date(Date.now() - 365 * 24 * 3600_000);
102
+ const to = filter.to ? new Date(filter.to) : new Date();
103
+ const limit = filter.limit ?? 1000;
104
+ ensureDir();
105
+ // 월별 파일 후보
106
+ const months = monthKeysBetween(from, to);
107
+ const events = [];
108
+ for (const ym of months) {
109
+ const file = path.join(JOURNAL_DIR, `${ym}.jsonl`);
110
+ if (!fs.existsSync(file))
111
+ continue;
112
+ const text = fs.readFileSync(file, "utf-8");
113
+ for (const line of text.split("\n")) {
114
+ if (!line.trim())
115
+ continue;
116
+ try {
117
+ const ev = JSON.parse(line);
118
+ if (matches(ev, filter, from, to)) {
119
+ events.push(ev);
120
+ if (events.length >= limit)
121
+ return events;
122
+ }
123
+ }
124
+ catch {
125
+ // skip 손상 line
126
+ }
127
+ }
128
+ }
129
+ return events;
130
+ }
131
+ function matches(ev, f, from, to) {
132
+ const t = new Date(ev.timestamp);
133
+ if (t < from || t > to)
134
+ return false;
135
+ if (f.personaSlug && ev.personaSlug !== f.personaSlug)
136
+ return false;
137
+ if (f.symbolCode && ev.symbolCode !== f.symbolCode)
138
+ return false;
139
+ if (f.eventTypes && !f.eventTypes.includes(ev.eventType))
140
+ return false;
141
+ if (f.textSearch) {
142
+ const q = f.textSearch.toLowerCase();
143
+ const r = (ev.llmContext?.reasoning ?? "").toLowerCase();
144
+ const l = (ev.llmContext?.lesson ?? "").toLowerCase();
145
+ if (!r.includes(q) && !l.includes(q))
146
+ return false;
147
+ }
148
+ return true;
149
+ }
150
+ function monthKeysBetween(from, to) {
151
+ const keys = [];
152
+ const cur = new Date(from.getFullYear(), from.getMonth(), 1);
153
+ const end = new Date(to.getFullYear(), to.getMonth(), 1);
154
+ while (cur <= end) {
155
+ const y = cur.getFullYear();
156
+ const m = String(cur.getMonth() + 1).padStart(2, "0");
157
+ keys.push(`${y}-${m}`);
158
+ cur.setMonth(cur.getMonth() + 1);
159
+ }
160
+ return keys;
161
+ }
162
+ export function computeStats(filter = {}) {
163
+ const events = readEvents({ ...filter, limit: 100_000 });
164
+ const stats = {
165
+ totalDecisions: 0,
166
+ totalOrders: 0,
167
+ filledOrders: 0,
168
+ rejectedOrders: 0,
169
+ buyCount: 0,
170
+ sellCount: 0,
171
+ totalBuyAmountKrw: 0,
172
+ totalSellAmountKrw: 0,
173
+ realizedPnlKrw: 0,
174
+ winRate: 0,
175
+ avgHoldDays: 0,
176
+ byPersona: {},
177
+ bySymbol: {},
178
+ };
179
+ let winCount = 0;
180
+ let totalSellsForWinrate = 0;
181
+ for (const ev of events) {
182
+ if (ev.personaSlug) {
183
+ stats.byPersona[ev.personaSlug] ??= { decisions: 0, filled: 0 };
184
+ }
185
+ if (ev.symbolCode) {
186
+ stats.bySymbol[ev.symbolCode] ??= { trades: 0, pnl: 0 };
187
+ }
188
+ switch (ev.eventType) {
189
+ case "decision":
190
+ stats.totalDecisions++;
191
+ if (ev.personaSlug)
192
+ stats.byPersona[ev.personaSlug].decisions++;
193
+ break;
194
+ case "order_submitted":
195
+ stats.totalOrders++;
196
+ if (ev.side === "buy")
197
+ stats.buyCount++;
198
+ if (ev.side === "sell")
199
+ stats.sellCount++;
200
+ break;
201
+ case "order_rejected":
202
+ stats.rejectedOrders++;
203
+ break;
204
+ case "order_status":
205
+ if (ev.meta?.next === "filled") {
206
+ stats.filledOrders++;
207
+ if (ev.personaSlug)
208
+ stats.byPersona[ev.personaSlug].filled++;
209
+ const qty = ev.filledQty ?? 0;
210
+ const px = ev.priceKrw ?? 0;
211
+ const amt = qty * px;
212
+ if (ev.side === "buy")
213
+ stats.totalBuyAmountKrw += amt;
214
+ if (ev.side === "sell") {
215
+ stats.totalSellAmountKrw += amt;
216
+ totalSellsForWinrate++;
217
+ // 단순 winrate: 매도가가 자체 매수가 이상이면 win (정확한 FIFO P+1)
218
+ // 여기선 stub 로 50% 가정 (실데이터 기반은 SQLite 도입 후)
219
+ }
220
+ if (ev.symbolCode) {
221
+ stats.bySymbol[ev.symbolCode].trades++;
222
+ }
223
+ }
224
+ break;
225
+ }
226
+ }
227
+ stats.realizedPnlKrw = stats.totalSellAmountKrw - stats.totalBuyAmountKrw;
228
+ stats.winRate = totalSellsForWinrate > 0 ? (winCount / totalSellsForWinrate) * 100 : 0;
229
+ return stats;
230
+ }
231
+ /** 디버깅용 파일 목록 */
232
+ export function listJournalFiles() {
233
+ ensureDir();
234
+ return fs
235
+ .readdirSync(JOURNAL_DIR)
236
+ .filter((f) => f.endsWith(".jsonl"))
237
+ .sort();
238
+ }
239
+ //# sourceMappingURL=trade-journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trade-journal.js","sourceRoot":"","sources":["../../../src-server/journal/trade-journal.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /** Anthropic stop_reason → 표준 LlmStopReason 매핑 */
2
+ function mapAnthropicStopReason(raw) {
3
+ switch (raw) {
4
+ case "end_turn": return "stop";
5
+ case "stop_sequence": return "stop";
6
+ case "max_tokens": return "max_tokens";
7
+ case "tool_use": return "tool_use";
8
+ default: return "unknown";
9
+ }
10
+ }
11
+ const BASE = "https://api.anthropic.com/v1";
12
+ const VERSION = "2023-06-01";
13
+ /**
14
+ * Anthropic 기본 max_tokens — 응답 잘림 방지 정책 (사용자 정정 · 2026-05-03).
15
+ *
16
+ * 사용자 의도: timeout · maxTokens 제한 → AI 답변 잘림 → 재호출 비용 더 큼 → 없어야 한다.
17
+ * Sonnet/Haiku 의 model 한계 (8192) 까지 자유 출력 허용.
18
+ *
19
+ * 호출자 (executor · diary 등) 가 명시 안 하면 이 값 적용. 명시 시 그 값 우선.
20
+ * fetch 자체는 timeout 걸지 않음 (무한대 대기 · OS TCP 만 적용).
21
+ */
22
+ const DEFAULT_MAX_TOKENS = 8192;
23
+ export function createAnthropicProvider(opts) {
24
+ return {
25
+ kind: "anthropic",
26
+ async test() {
27
+ if (!opts.apiKey)
28
+ return { ok: false, provider: "anthropic", error: "apiKey 없음" };
29
+ try {
30
+ // Anthropic 은 /models list 지원 안 · messages 로 ping (1 token)
31
+ const res = await fetch(`${BASE}/messages`, {
32
+ method: "POST",
33
+ headers: {
34
+ "content-type": "application/json",
35
+ "x-api-key": opts.apiKey,
36
+ "anthropic-version": VERSION,
37
+ },
38
+ body: JSON.stringify({
39
+ model: opts.model,
40
+ max_tokens: 1,
41
+ messages: [{ role: "user", content: "ping" }],
42
+ }),
43
+ });
44
+ if (res.status === 401 || res.status === 403) {
45
+ return { ok: false, provider: "anthropic", error: "인증 실패 (key 또는 권한 확인)" };
46
+ }
47
+ if (!res.ok && res.status !== 200) {
48
+ return {
49
+ ok: false,
50
+ provider: "anthropic",
51
+ error: `HTTP ${res.status}: ${(await res.text()).slice(0, 120)}`,
52
+ };
53
+ }
54
+ return { ok: true, provider: "anthropic", model: opts.model, detail: "messages ping OK" };
55
+ }
56
+ catch (e) {
57
+ return { ok: false, provider: "anthropic", error: String(e) };
58
+ }
59
+ },
60
+ async complete(c) {
61
+ if (!opts.apiKey)
62
+ throw new Error("Anthropic apiKey 없음");
63
+ const res = await fetch(`${BASE}/messages`, {
64
+ method: "POST",
65
+ headers: {
66
+ "content-type": "application/json",
67
+ "x-api-key": opts.apiKey,
68
+ "anthropic-version": VERSION,
69
+ },
70
+ body: JSON.stringify({
71
+ model: opts.model,
72
+ max_tokens: c.maxTokens ?? DEFAULT_MAX_TOKENS,
73
+ temperature: c.temperature ?? 0.2,
74
+ system: c.systemPrompt || undefined,
75
+ messages: [{ role: "user", content: c.userPrompt }],
76
+ }),
77
+ });
78
+ if (!res.ok)
79
+ throw new Error(`Anthropic HTTP ${res.status}: ${await res.text()}`);
80
+ const data = (await res.json());
81
+ const text = data.content?.map((b) => (b.type === "text" ? b.text ?? "" : "")).join("") ?? "";
82
+ return {
83
+ text,
84
+ model: opts.model,
85
+ provider: "anthropic",
86
+ stopReason: mapAnthropicStopReason(data.stop_reason),
87
+ usage: data.usage
88
+ ? {
89
+ inputTokens: data.usage.input_tokens ?? 0,
90
+ outputTokens: data.usage.output_tokens ?? 0,
91
+ }
92
+ : undefined,
93
+ raw: data,
94
+ };
95
+ },
96
+ };
97
+ }
98
+ //# sourceMappingURL=anthropic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"anthropic.js","sourceRoot":"","sources":["../../../src-server/llm/anthropic.ts"],"names":[],"mappings":"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"}