@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.
- package/README.md +103 -0
- package/bin/cli.js +160 -0
- package/bin/postinstall.js +57 -0
- package/bin/preuninstall.js +36 -0
- package/dist/server/broker/kiwoom/cache.js +86 -0
- package/dist/server/broker/kiwoom/cache.js.map +1 -0
- package/dist/server/broker/kiwoom/client.js +256 -0
- package/dist/server/broker/kiwoom/client.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/_helpers.js +61 -0
- package/dist/server/broker/kiwoom/endpoints/_helpers.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/account.js +448 -0
- package/dist/server/broker/kiwoom/endpoints/account.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/detail.js +118 -0
- package/dist/server/broker/kiwoom/endpoints/detail.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/investor.js +139 -0
- package/dist/server/broker/kiwoom/endpoints/investor.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/order.js +134 -0
- package/dist/server/broker/kiwoom/endpoints/order.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/quote.js +165 -0
- package/dist/server/broker/kiwoom/endpoints/quote.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/ranking.js +180 -0
- package/dist/server/broker/kiwoom/endpoints/ranking.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/sector.js +135 -0
- package/dist/server/broker/kiwoom/endpoints/sector.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/theme.js +104 -0
- package/dist/server/broker/kiwoom/endpoints/theme.js.map +1 -0
- package/dist/server/broker/kiwoom/endpoints/universe.js +119 -0
- package/dist/server/broker/kiwoom/endpoints/universe.js.map +1 -0
- package/dist/server/broker/kiwoom/index.js +59 -0
- package/dist/server/broker/kiwoom/index.js.map +1 -0
- package/dist/server/broker/kiwoom/order-tracker.js +353 -0
- package/dist/server/broker/kiwoom/order-tracker.js.map +1 -0
- package/dist/server/broker/kiwoom/price-feed.js +119 -0
- package/dist/server/broker/kiwoom/price-feed.js.map +1 -0
- package/dist/server/broker/kiwoom/rate-limiter.js +97 -0
- package/dist/server/broker/kiwoom/rate-limiter.js.map +1 -0
- package/dist/server/broker/kiwoom/types.js +13 -0
- package/dist/server/broker/kiwoom/types.js.map +1 -0
- package/dist/server/broker/kiwoom/ws/client.js +370 -0
- package/dist/server/broker/kiwoom/ws/client.js.map +1 -0
- package/dist/server/broker/kiwoom/ws/endpoints/condition.js +146 -0
- package/dist/server/broker/kiwoom/ws/endpoints/condition.js.map +1 -0
- package/dist/server/broker/kiwoom/ws/realtime-bus.js +42 -0
- package/dist/server/broker/kiwoom/ws/realtime-bus.js.map +1 -0
- package/dist/server/broker/kiwoom/ws/types.js +19 -0
- package/dist/server/broker/kiwoom/ws/types.js.map +1 -0
- package/dist/server/broker/news.js +34 -0
- package/dist/server/broker/news.js.map +1 -0
- package/dist/server/bundle.js +43 -0
- package/dist/server/bundle.js.map +1 -0
- package/dist/server/calendar/krx-holidays.js +162 -0
- package/dist/server/calendar/krx-holidays.js.map +1 -0
- package/dist/server/config.js +263 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/db/sqlite.js +252 -0
- package/dist/server/db/sqlite.js.map +1 -0
- package/dist/server/diary/writer.js +266 -0
- package/dist/server/diary/writer.js.map +1 -0
- package/dist/server/index.js +316 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/jobs/universe-sync.js +132 -0
- package/dist/server/jobs/universe-sync.js.map +1 -0
- package/dist/server/jobs/watchdog.js +87 -0
- package/dist/server/jobs/watchdog.js.map +1 -0
- package/dist/server/journal/pnl-stats.js +108 -0
- package/dist/server/journal/pnl-stats.js.map +1 -0
- package/dist/server/journal/telemetry.js +174 -0
- package/dist/server/journal/telemetry.js.map +1 -0
- package/dist/server/journal/trade-journal.js +239 -0
- package/dist/server/journal/trade-journal.js.map +1 -0
- package/dist/server/llm/anthropic.js +98 -0
- package/dist/server/llm/anthropic.js.map +1 -0
- package/dist/server/llm/claude-cli.js +204 -0
- package/dist/server/llm/claude-cli.js.map +1 -0
- package/dist/server/llm/context-builder.js +229 -0
- package/dist/server/llm/context-builder.js.map +1 -0
- package/dist/server/llm/gemini.js +86 -0
- package/dist/server/llm/gemini.js.map +1 -0
- package/dist/server/llm/index.js +36 -0
- package/dist/server/llm/index.js.map +1 -0
- package/dist/server/llm/openai.js +87 -0
- package/dist/server/llm/openai.js.map +1 -0
- package/dist/server/personas/executor.js +318 -0
- package/dist/server/personas/executor.js.map +1 -0
- package/dist/server/personas/loader.js +165 -0
- package/dist/server/personas/loader.js.map +1 -0
- package/dist/server/personas/persona-agent.js +386 -0
- package/dist/server/personas/persona-agent.js.map +1 -0
- package/dist/server/personas/persona-state.js +170 -0
- package/dist/server/personas/persona-state.js.map +1 -0
- package/dist/server/personas/runner.js +162 -0
- package/dist/server/personas/runner.js.map +1 -0
- package/dist/server/personas/schema.js +123 -0
- package/dist/server/personas/schema.js.map +1 -0
- package/dist/server/personas/wake-plan.js +313 -0
- package/dist/server/personas/wake-plan.js.map +1 -0
- package/dist/server/readiness.js +414 -0
- package/dist/server/readiness.js.map +1 -0
- package/dist/server/routes.js +1216 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/safety.js +153 -0
- package/dist/server/safety.js.map +1 -0
- package/dist/server/screener/engine.js +856 -0
- package/dist/server/screener/engine.js.map +1 -0
- package/dist/server/server-sync.js +427 -0
- package/dist/server/server-sync.js.map +1 -0
- package/dist/server/signing.js +39 -0
- package/dist/server/signing.js.map +1 -0
- package/dist/server/watchers/condition-watcher.js +519 -0
- package/dist/server/watchers/condition-watcher.js.map +1 -0
- package/dist/server/watchers/types.js +16 -0
- package/dist/server/watchers/types.js.map +1 -0
- package/dist/web/assets/index-62SMpbaf.js +79 -0
- package/dist/web/assets/index-BPLQR0wt.css +1 -0
- package/dist/web/index.html +14 -0
- package/package.json +93 -0
- package/scripts/com.mulsok.traders.client.plist.template +58 -0
- package/scripts/install-daemon.sh +156 -0
- package/scripts/uninstall-daemon.sh +62 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bundle 클라이언트 · ADR-009.
|
|
3
|
+
* mulsok-traders 웹 서버의 Bundle API 호출 + 해시 검증 + Layer 조립.
|
|
4
|
+
*/
|
|
5
|
+
import { hashBody } from "./signing.js";
|
|
6
|
+
export async function fetchCommonBundle(serverBaseUrl) {
|
|
7
|
+
const url = `${serverBaseUrl.replace(/\/$/, "")}/api/bundle/common?version=latest`;
|
|
8
|
+
const res = await fetch(url, { headers: { accept: "application/json" } });
|
|
9
|
+
if (!res.ok) {
|
|
10
|
+
throw new Error(`Bundle common fetch 실패 · HTTP ${res.status} · ${await res.text()}`);
|
|
11
|
+
}
|
|
12
|
+
const bundle = (await res.json());
|
|
13
|
+
return { bundle, hash_ok: hashBody(bundle.body) === bundle.hash };
|
|
14
|
+
}
|
|
15
|
+
export async function fetchPersonaBundle(serverBaseUrl, slug, authBearer) {
|
|
16
|
+
const url = `${serverBaseUrl.replace(/\/$/, "")}/api/bundle/persona/${encodeURIComponent(slug)}?version=latest`;
|
|
17
|
+
const headers = { accept: "application/json" };
|
|
18
|
+
if (authBearer)
|
|
19
|
+
headers.authorization = `Bearer ${authBearer}`;
|
|
20
|
+
const res = await fetch(url, { headers });
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
throw new Error(`Bundle persona/${slug} fetch 실패 · HTTP ${res.status} · ${await res.text()}`);
|
|
23
|
+
}
|
|
24
|
+
const bundle = (await res.json());
|
|
25
|
+
return { bundle, hash_ok: hashBody(bundle.body) === bundle.hash };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Full prompt 조립 (ADR-009 canonical 순서):
|
|
29
|
+
* L3 header → L2 body → L3 patches → L1 body (마지막 = 성역)
|
|
30
|
+
*/
|
|
31
|
+
export function assembleFullPrompt(opts) {
|
|
32
|
+
const parts = [];
|
|
33
|
+
if (opts.layer3Header?.trim()) {
|
|
34
|
+
parts.push("# === LAYER 3 · LOCAL JOURNAL (header) ===", opts.layer3Header.trim(), "");
|
|
35
|
+
}
|
|
36
|
+
parts.push("# === LAYER 2 · PERSONA BODY ===", opts.layer2.trim(), "");
|
|
37
|
+
if (opts.layer3Patches?.trim()) {
|
|
38
|
+
parts.push("# === LAYER 3 · PATCHES (accumulated) ===", opts.layer3Patches.trim(), "");
|
|
39
|
+
}
|
|
40
|
+
parts.push("# === LAYER 1 · COMMON (SACRED · overrides all) ===", opts.layer1.trim());
|
|
41
|
+
return parts.join("\n");
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../src-server/bundle.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAkBxC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,aAAqB;IAErB,MAAM,GAAG,GAAG,GAAG,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,mCAAmC,CAAC;IACnF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC;IAC1E,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,CAAC,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACvF,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAe,CAAC;IAChD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,aAAqB,EACrB,IAAY,EACZ,UAAmB;IAEnB,MAAM,GAAG,GAAG,GAAG,aAAa,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,uBAAuB,kBAAkB,CAAC,IAAI,CAAC,iBAAiB,CAAC;IAChH,MAAM,OAAO,GAA2B,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IACvE,IAAI,UAAU;QAAE,OAAO,CAAC,aAAa,GAAG,UAAU,UAAU,EAAE,CAAC;IAC/D,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1C,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,oBAAoB,GAAG,CAAC,MAAM,MAAM,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAChG,CAAC;IACD,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAe,CAAC;IAChD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAKlC;IACC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,4CAA4C,EAAE,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,kCAAkC,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACvE,IAAI,IAAI,CAAC,aAAa,EAAE,IAAI,EAAE,EAAE,CAAC;QAC/B,KAAK,CAAC,IAAI,CAAC,2CAA2C,EAAE,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,qDAAqD,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACtF,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KRX 휴장일 캘린더 (한국거래소).
|
|
3
|
+
*
|
|
4
|
+
* 데이터 소스 (우선순위):
|
|
5
|
+
* 1. ~/.mulsok-traders/krx-holidays.json (사용자 override · 정확한 발표 반영)
|
|
6
|
+
* 2. 정적 fallback 리스트 (이 파일 · 2026·2027 추정치 · 발표 변경 가능)
|
|
7
|
+
*
|
|
8
|
+
* 사용:
|
|
9
|
+
* - normalizeKstDateMarker("다음거래일T...") · 휴장일 자동 skip
|
|
10
|
+
* - isKrxHoliday(date), nextTradingDay(date), isTradingDay(date)
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ 정적 리스트는 추정이며 한국거래소 공식 발표와 어긋날 수 있음.
|
|
13
|
+
* 실 거래 운영 시 사용자가 ~/.mulsok-traders/krx-holidays.json 으로 갱신 권장.
|
|
14
|
+
* 공식 발표: https://open.krx.co.kr (시장 운영 공지)
|
|
15
|
+
*/
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import os from "node:os";
|
|
19
|
+
const USER_OVERRIDE_PATH = path.join(os.homedir(), ".mulsok-traders", "krx-holidays.json");
|
|
20
|
+
/**
|
|
21
|
+
* 정적 KRX 휴장일 (YYYY-MM-DD KST · 2026·2027 추정).
|
|
22
|
+
*
|
|
23
|
+
* 포함:
|
|
24
|
+
* - 일요일·토요일 자동 skip (별도 계산 · 이 리스트에 없음)
|
|
25
|
+
* - 신정 · 삼일절 · 어린이날 · 부처님오신날 · 현충일 · 광복절 · 개천절 · 한글날 · 성탄절
|
|
26
|
+
* - 설날·추석 연휴 (음력 기준)
|
|
27
|
+
* - 12월 31일 (KRX 연말 폐장)
|
|
28
|
+
* - 대체공휴일 (토/일과 겹치는 경우)
|
|
29
|
+
*
|
|
30
|
+
* 갱신 책임: 사용자 (또는 매년 12월 KRX 공식 발표 후 SPEC 갱신).
|
|
31
|
+
*/
|
|
32
|
+
const STATIC_KRX_HOLIDAYS = new Set([
|
|
33
|
+
// ── 2026 ──
|
|
34
|
+
"2026-01-01", // 신정
|
|
35
|
+
"2026-02-16", // 설날 연휴 (월)
|
|
36
|
+
"2026-02-17", // 설날 (화 · 음력 1/1)
|
|
37
|
+
"2026-02-18", // 설날 연휴 (수)
|
|
38
|
+
"2026-03-02", // 삼일절 대체 (3/1 일요일)
|
|
39
|
+
"2026-05-05", // 어린이날
|
|
40
|
+
"2026-05-25", // 부처님오신날 (음력 4/8)
|
|
41
|
+
"2026-08-17", // 광복절 대체 (8/15 토요일)
|
|
42
|
+
"2026-09-24", // 추석 연휴 (목)
|
|
43
|
+
"2026-09-25", // 추석 (금 · 음력 8/15)
|
|
44
|
+
"2026-09-28", // 추석 대체 (9/26 토요일)
|
|
45
|
+
"2026-10-05", // 개천절 대체 (10/3 토요일)
|
|
46
|
+
"2026-10-09", // 한글날
|
|
47
|
+
"2026-12-25", // 성탄절
|
|
48
|
+
"2026-12-31", // 연말 폐장
|
|
49
|
+
// ── 2027 ──
|
|
50
|
+
"2027-01-01", // 신정
|
|
51
|
+
"2027-02-08", // 설날 연휴
|
|
52
|
+
"2027-02-09", // 설날 (음력 1/1)
|
|
53
|
+
"2027-02-10", // 설날 연휴
|
|
54
|
+
"2027-03-01", // 삼일절
|
|
55
|
+
"2027-05-05", // 어린이날
|
|
56
|
+
"2027-05-13", // 부처님오신날 (음력 4/8)
|
|
57
|
+
"2027-08-16", // 광복절 대체 (8/15 일요일)
|
|
58
|
+
"2027-09-14", // 추석 연휴
|
|
59
|
+
"2027-09-15", // 추석 (음력 8/15)
|
|
60
|
+
"2027-09-16", // 추석 연휴
|
|
61
|
+
"2027-10-04", // 개천절 대체 (10/3 일요일)
|
|
62
|
+
"2027-10-11", // 한글날 대체 (10/9 토요일)
|
|
63
|
+
"2027-12-31", // 연말 폐장
|
|
64
|
+
]);
|
|
65
|
+
let userOverrideCache = null;
|
|
66
|
+
/** 사용자 override 로드 (변경 감지 + 캐시) */
|
|
67
|
+
function loadUserOverride() {
|
|
68
|
+
try {
|
|
69
|
+
if (!fs.existsSync(USER_OVERRIDE_PATH))
|
|
70
|
+
return null;
|
|
71
|
+
const stat = fs.statSync(USER_OVERRIDE_PATH);
|
|
72
|
+
const mtime = stat.mtimeMs;
|
|
73
|
+
if (userOverrideCache && userOverrideCache.mtime === mtime) {
|
|
74
|
+
return userOverrideCache.set;
|
|
75
|
+
}
|
|
76
|
+
const raw = fs.readFileSync(USER_OVERRIDE_PATH, "utf-8");
|
|
77
|
+
const parsed = JSON.parse(raw);
|
|
78
|
+
if (!Array.isArray(parsed)) {
|
|
79
|
+
console.warn(`[krx-holidays] ${USER_OVERRIDE_PATH} 형식 오류 (배열 아님) · static fallback 사용`);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
const set = new Set();
|
|
83
|
+
for (const item of parsed) {
|
|
84
|
+
if (typeof item === "string" && /^\d{4}-\d{2}-\d{2}$/.test(item))
|
|
85
|
+
set.add(item);
|
|
86
|
+
}
|
|
87
|
+
userOverrideCache = { mtime, set };
|
|
88
|
+
return set;
|
|
89
|
+
}
|
|
90
|
+
catch (e) {
|
|
91
|
+
console.warn(`[krx-holidays] override 로드 실패 · ${e}`);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** YYYY-MM-DD (KST) 문자열 → KRX 휴장일 여부 (공휴일만 · 토/일 별도) */
|
|
96
|
+
export function isKrxHolidayKey(yyyyMmDd) {
|
|
97
|
+
const override = loadUserOverride();
|
|
98
|
+
if (override)
|
|
99
|
+
return override.has(yyyyMmDd);
|
|
100
|
+
return STATIC_KRX_HOLIDAYS.has(yyyyMmDd);
|
|
101
|
+
}
|
|
102
|
+
/** Date (UTC) → KST 기준 YYYY-MM-DD */
|
|
103
|
+
function toKstYmd(d) {
|
|
104
|
+
const kst = new Date(d.getTime() + 9 * 3600_000);
|
|
105
|
+
return kst.toISOString().slice(0, 10);
|
|
106
|
+
}
|
|
107
|
+
/** Date → KST 기준 요일 (0=일, 6=토) */
|
|
108
|
+
function kstDay(d) {
|
|
109
|
+
const kst = new Date(d.getTime() + 9 * 3600_000);
|
|
110
|
+
return kst.getUTCDay();
|
|
111
|
+
}
|
|
112
|
+
/** Date → KRX 거래일 여부 (평일 + 휴장일 아님) */
|
|
113
|
+
export function isTradingDay(d) {
|
|
114
|
+
const day = kstDay(d);
|
|
115
|
+
if (day === 0 || day === 6)
|
|
116
|
+
return false; // 일·토
|
|
117
|
+
return !isKrxHolidayKey(toKstYmd(d));
|
|
118
|
+
}
|
|
119
|
+
/** Date → KRX 휴장일 (휴장일 또는 주말) */
|
|
120
|
+
export function isKrxHoliday(d) {
|
|
121
|
+
return !isTradingDay(d);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* 주어진 시각 이후 (또는 같은 날) 다음 거래일.
|
|
125
|
+
* - input 이 거래일 · 09:00 이전 → input 그대로 (같은 날 장 시작 가능)
|
|
126
|
+
* - input 이 거래일 · 09:00 이후 → 다음 거래일
|
|
127
|
+
* - input 이 휴장일 → 다음 거래일
|
|
128
|
+
*
|
|
129
|
+
* KST 기준. 시각 부분은 input 그대로 유지 (날짜만 변경).
|
|
130
|
+
*
|
|
131
|
+
* @param d 기준 시점 (UTC Date)
|
|
132
|
+
* @param afterMarketHours true 면 input 이 거래일이라도 다음 거래일 반환 (장 마감 후 호출 시)
|
|
133
|
+
*/
|
|
134
|
+
export function nextTradingDay(d, afterMarketHours = false) {
|
|
135
|
+
let cursor = new Date(d.getTime());
|
|
136
|
+
// input 이 거래일이고 afterMarketHours=true 면 +1일
|
|
137
|
+
if (afterMarketHours && isTradingDay(cursor)) {
|
|
138
|
+
cursor = new Date(cursor.getTime() + 86400_000);
|
|
139
|
+
}
|
|
140
|
+
// 거래일 찾을 때까지 +1일
|
|
141
|
+
let safety = 0;
|
|
142
|
+
while (!isTradingDay(cursor)) {
|
|
143
|
+
cursor = new Date(cursor.getTime() + 86400_000);
|
|
144
|
+
safety++;
|
|
145
|
+
if (safety > 30) {
|
|
146
|
+
// 30일 연속 휴장은 비정상 — fallback 으로 다음 평일 반환
|
|
147
|
+
console.warn(`[krx-holidays] nextTradingDay safety break · ${cursor.toISOString()}`);
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return cursor;
|
|
152
|
+
}
|
|
153
|
+
/** 통계용 — 디버깅 시 정적 리스트 size + override 활성 여부 */
|
|
154
|
+
export function holidayStats() {
|
|
155
|
+
const override = loadUserOverride();
|
|
156
|
+
return {
|
|
157
|
+
staticCount: STATIC_KRX_HOLIDAYS.size,
|
|
158
|
+
overrideActive: override !== null,
|
|
159
|
+
overrideCount: override?.size ?? 0,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=krx-holidays.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"krx-holidays.js","sourceRoot":"","sources":["../../../src-server/calendar/krx-holidays.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client Config · 로컬 JSON 파일 저장.
|
|
3
|
+
* MVP · OS Keychain 은 향후 Tauri 포장 시 교체 (현재는 ~/.mulsok-traders/config.json).
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import os from "node:os";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
const CONFIG_DIR = path.join(os.homedir(), ".mulsok-traders");
|
|
10
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
11
|
+
/**
|
|
12
|
+
* Server Base URL · 내장 상수.
|
|
13
|
+
* 프로덕션: mulsok-traders.vercel.app (기본)
|
|
14
|
+
* 개발: `MULSOK_SERVER_URL=http://localhost:3000 npm run dev`
|
|
15
|
+
*/
|
|
16
|
+
export const SERVER_BASE_URL = process.env.MULSOK_SERVER_URL ?? "https://mulsok-traders.vercel.app";
|
|
17
|
+
export const LlmProviderKind = z.enum([
|
|
18
|
+
"none",
|
|
19
|
+
"gemini",
|
|
20
|
+
"openai",
|
|
21
|
+
"anthropic",
|
|
22
|
+
"claude-cli",
|
|
23
|
+
]);
|
|
24
|
+
export const ClientConfigSchema = z.object({
|
|
25
|
+
version: z.literal(1).default(1),
|
|
26
|
+
llm: z.object({
|
|
27
|
+
provider: LlmProviderKind.default("none"),
|
|
28
|
+
apiKey: z.string().optional(), // gemini / openai / anthropic 용
|
|
29
|
+
model: z.string().optional(), // 예: gemini-2.5-flash, gpt-4o-mini, claude-sonnet-4-5, sonnet
|
|
30
|
+
claudeCli: z.object({
|
|
31
|
+
binaryPath: z.string().optional(), // 기본: `claude` (PATH 에서 찾음)
|
|
32
|
+
model: z.string().default("sonnet"), // sonnet / opus / haiku
|
|
33
|
+
}).optional(),
|
|
34
|
+
}).default({ provider: "none" }),
|
|
35
|
+
broker: z.object({
|
|
36
|
+
kind: z.enum(["none", "kiwoom", "kis"]).default("none"),
|
|
37
|
+
appKey: z.string().optional(), // 키움 AppKey · KIS APP_KEY
|
|
38
|
+
appSecret: z.string().optional(), // 키움 SecretKey · KIS APP_SECRET
|
|
39
|
+
isDemo: z.boolean().default(true), // 모의투자 · 실전 토글 · 기본 안전
|
|
40
|
+
// 계좌번호: 주문 실행 단계에서 키움 API 가 자동 감지 예정 (사용자 입력 불필요)
|
|
41
|
+
}).default({ kind: "none", isDemo: true }),
|
|
42
|
+
auth: z.object({
|
|
43
|
+
deviceToken: z.string().optional(), // mtd_... · 웹 대시보드에서 발급
|
|
44
|
+
}).default({}),
|
|
45
|
+
// 구독한 페르소나 슬러그 목록 (SaaS 구독 모델 · ADR-001).
|
|
46
|
+
// server SSoT (`/api/client/ping` 응답) 의 캐시 (사용자 임의 추가 X).
|
|
47
|
+
subscriptions: z.array(z.string()).default([]),
|
|
48
|
+
/**
|
|
49
|
+
* 페르소나별 배정 자본 (KRW · taeeun-life allocatedCapital 컨셉)
|
|
50
|
+
* 페르소나는 자기 받은 자본 안에서만 운용. 초과 주문 차단 (SPEC-26).
|
|
51
|
+
* 향후 server `/api/me/instances.allocated_capital_krw` 동기화 예정.
|
|
52
|
+
* 사용자 설정 탭에서 manual 입력 가능.
|
|
53
|
+
*/
|
|
54
|
+
allocations: z.record(z.string(), z.number().nonnegative()).default({}),
|
|
55
|
+
/**
|
|
56
|
+
* 응급 정지 상태 (SPEC-26 Emergency Stop).
|
|
57
|
+
* true 시 모든 주문·LLM 호출 차단. 사용자가 UI 토글로 해제.
|
|
58
|
+
*/
|
|
59
|
+
halted: z.boolean().default(false),
|
|
60
|
+
});
|
|
61
|
+
export const DEFAULT_CONFIG = ClientConfigSchema.parse({});
|
|
62
|
+
function ensureDir() {
|
|
63
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
64
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** 로컬 config 로드 · file → env override → default 우선순위 */
|
|
68
|
+
export function loadConfig() {
|
|
69
|
+
let fromFile = DEFAULT_CONFIG;
|
|
70
|
+
try {
|
|
71
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
72
|
+
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
73
|
+
const parsed = ClientConfigSchema.safeParse(JSON.parse(raw));
|
|
74
|
+
if (parsed.success) {
|
|
75
|
+
fromFile = parsed.data;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
console.warn("[config] invalid format · default 사용:", parsed.error.issues);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
console.warn("[config] load 실패 · default:", e);
|
|
84
|
+
}
|
|
85
|
+
// env override — webui 안 쓰는 사용자 (CI/CD · 자동 배포 · 비밀 관리자) 시나리오 지원
|
|
86
|
+
return mergeFromEnv(fromFile);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 환경 변수로 config override.
|
|
90
|
+
*
|
|
91
|
+
* 우선순위: env > file > default
|
|
92
|
+
* 지원 키:
|
|
93
|
+
* MULSOK_BROKER_KIND · APPKEY · APPSECRET · DEMO
|
|
94
|
+
* MULSOK_LLM_PROVIDER · APIKEY · MODEL
|
|
95
|
+
* MULSOK_DEVICE_TOKEN
|
|
96
|
+
* MULSOK_HALTED
|
|
97
|
+
* MULSOK_ALLOCATION_<SLUG>=<KRW> (예: MULSOK_ALLOCATION_NEOUL=1000000)
|
|
98
|
+
*
|
|
99
|
+
* 빈 문자열 / undefined / 잘못된 enum 은 무시 (file 값 보존).
|
|
100
|
+
*/
|
|
101
|
+
export function mergeFromEnv(cfg) {
|
|
102
|
+
const env = process.env;
|
|
103
|
+
const broker = { ...cfg.broker };
|
|
104
|
+
const brokerKind = env.MULSOK_BROKER_KIND;
|
|
105
|
+
if (brokerKind === "none" || brokerKind === "kiwoom" || brokerKind === "kis") {
|
|
106
|
+
broker.kind = brokerKind;
|
|
107
|
+
}
|
|
108
|
+
if (env.MULSOK_BROKER_APPKEY)
|
|
109
|
+
broker.appKey = env.MULSOK_BROKER_APPKEY;
|
|
110
|
+
if (env.MULSOK_BROKER_APPSECRET)
|
|
111
|
+
broker.appSecret = env.MULSOK_BROKER_APPSECRET;
|
|
112
|
+
if (env.MULSOK_BROKER_DEMO != null && env.MULSOK_BROKER_DEMO !== "") {
|
|
113
|
+
broker.isDemo = env.MULSOK_BROKER_DEMO === "true";
|
|
114
|
+
}
|
|
115
|
+
const llm = { ...cfg.llm };
|
|
116
|
+
const provider = env.MULSOK_LLM_PROVIDER;
|
|
117
|
+
if (provider && LlmProviderKind.options.includes(provider)) {
|
|
118
|
+
llm.provider = provider;
|
|
119
|
+
}
|
|
120
|
+
if (env.MULSOK_LLM_APIKEY)
|
|
121
|
+
llm.apiKey = env.MULSOK_LLM_APIKEY;
|
|
122
|
+
if (env.MULSOK_LLM_MODEL)
|
|
123
|
+
llm.model = env.MULSOK_LLM_MODEL;
|
|
124
|
+
const auth = { ...cfg.auth };
|
|
125
|
+
if (env.MULSOK_DEVICE_TOKEN)
|
|
126
|
+
auth.deviceToken = env.MULSOK_DEVICE_TOKEN;
|
|
127
|
+
let halted = cfg.halted;
|
|
128
|
+
if (env.MULSOK_HALTED != null && env.MULSOK_HALTED !== "") {
|
|
129
|
+
halted = env.MULSOK_HALTED === "true";
|
|
130
|
+
}
|
|
131
|
+
// allocations: MULSOK_ALLOCATION_<SLUG>=<KRW>
|
|
132
|
+
const allocations = { ...cfg.allocations };
|
|
133
|
+
for (const [k, v] of Object.entries(env)) {
|
|
134
|
+
if (!k.startsWith("MULSOK_ALLOCATION_") || !v)
|
|
135
|
+
continue;
|
|
136
|
+
const slug = k.replace("MULSOK_ALLOCATION_", "").toLowerCase();
|
|
137
|
+
const n = Number(v);
|
|
138
|
+
if (Number.isFinite(n) && n >= 0)
|
|
139
|
+
allocations[slug] = n;
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
...cfg,
|
|
143
|
+
broker,
|
|
144
|
+
llm,
|
|
145
|
+
auth,
|
|
146
|
+
halted,
|
|
147
|
+
allocations,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/** config 저장 · 0600 권한 */
|
|
151
|
+
export function saveConfig(cfg) {
|
|
152
|
+
ensureDir();
|
|
153
|
+
const validated = ClientConfigSchema.parse(cfg);
|
|
154
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(validated, null, 2), { mode: 0o600 });
|
|
155
|
+
return validated;
|
|
156
|
+
}
|
|
157
|
+
/** API key 마스킹 · GET /api/config 응답 시 사용 */
|
|
158
|
+
export function maskedConfig(cfg) {
|
|
159
|
+
const mask = (s) => {
|
|
160
|
+
if (!s)
|
|
161
|
+
return undefined;
|
|
162
|
+
if (s.length <= 10)
|
|
163
|
+
return "***";
|
|
164
|
+
return s.slice(0, 6) + "..." + s.slice(-4);
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
...cfg,
|
|
168
|
+
llm: {
|
|
169
|
+
...cfg.llm,
|
|
170
|
+
apiKey: mask(cfg.llm.apiKey),
|
|
171
|
+
},
|
|
172
|
+
broker: {
|
|
173
|
+
...cfg.broker,
|
|
174
|
+
appKey: mask(cfg.broker.appKey),
|
|
175
|
+
appSecret: mask(cfg.broker.appSecret),
|
|
176
|
+
},
|
|
177
|
+
auth: {
|
|
178
|
+
...cfg.auth,
|
|
179
|
+
deviceToken: mask(cfg.auth.deviceToken),
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* maskedConfig 가 만든 마스킹 형태 (`***` 또는 `xxxxxx...yyyy`) 인지 감지.
|
|
185
|
+
* UI 가 GET 응답을 그대로 POST 로 다시 보내는 실수를 백엔드에서 차단하기 위함.
|
|
186
|
+
* 정상 비밀값(키움 AppKey, deviceToken 등)은 `...` 를 포함하지 않거나, 길이/형태가 다름.
|
|
187
|
+
*/
|
|
188
|
+
export function isMaskedSecret(s) {
|
|
189
|
+
if (typeof s !== "string")
|
|
190
|
+
return false;
|
|
191
|
+
if (s === "***")
|
|
192
|
+
return true;
|
|
193
|
+
// maskedConfig: head(6) + "..." + tail(4) = 정확히 13자
|
|
194
|
+
return s.length === 13 && s.slice(6, 9) === "...";
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* POST /api/config partial merge.
|
|
198
|
+
*
|
|
199
|
+
* 규칙:
|
|
200
|
+
* - 비밀값(llm.apiKey · auth.deviceToken · broker.appKey/appSecret): 빈문자열·미포함·**마스킹 sentinel** → 기존 유지
|
|
201
|
+
* - 공개값(provider · model · broker.kind · isDemo): 명시 시 교체
|
|
202
|
+
* - patch 에 없는 최상위 섹션(subscriptions/allocations/halted/llm/broker/auth): **기존 유지** (default 로 리셋 X)
|
|
203
|
+
*
|
|
204
|
+
* 결함 회귀 차단: 한 섹션 저장이 다른 섹션을 덮어쓰지 않도록 모든 최상위 필드를 명시적 보존.
|
|
205
|
+
*/
|
|
206
|
+
export function mergeConfigPatch(existing, patch) {
|
|
207
|
+
const preserveSecret = (incoming, current) => {
|
|
208
|
+
if (typeof incoming === "string" && incoming.length > 0 && !isMaskedSecret(incoming))
|
|
209
|
+
return incoming;
|
|
210
|
+
return current;
|
|
211
|
+
};
|
|
212
|
+
// patch 에 섹션이 명시 안 되면 기존 그대로 — default 로 리셋 X
|
|
213
|
+
const has = (k) => Object.prototype.hasOwnProperty.call(patch, k);
|
|
214
|
+
const mergedLlm = (() => {
|
|
215
|
+
if (!has("llm"))
|
|
216
|
+
return existing.llm;
|
|
217
|
+
const p = (patch.llm ?? {});
|
|
218
|
+
return {
|
|
219
|
+
provider: p.provider ?? existing.llm.provider,
|
|
220
|
+
apiKey: preserveSecret(p.apiKey, existing.llm.apiKey),
|
|
221
|
+
model: p.model === undefined ? existing.llm.model : p.model,
|
|
222
|
+
claudeCli: p.claudeCli === undefined
|
|
223
|
+
? existing.llm.claudeCli
|
|
224
|
+
: p.claudeCli,
|
|
225
|
+
};
|
|
226
|
+
})();
|
|
227
|
+
const mergedBroker = (() => {
|
|
228
|
+
if (!has("broker"))
|
|
229
|
+
return existing.broker;
|
|
230
|
+
const p = (patch.broker ?? {});
|
|
231
|
+
return {
|
|
232
|
+
kind: p.kind ?? existing.broker.kind,
|
|
233
|
+
appKey: preserveSecret(p.appKey, existing.broker.appKey),
|
|
234
|
+
appSecret: preserveSecret(p.appSecret, existing.broker.appSecret),
|
|
235
|
+
isDemo: typeof p.isDemo === "boolean" ? p.isDemo : existing.broker.isDemo,
|
|
236
|
+
};
|
|
237
|
+
})();
|
|
238
|
+
const mergedAuth = (() => {
|
|
239
|
+
if (!has("auth"))
|
|
240
|
+
return existing.auth;
|
|
241
|
+
const p = (patch.auth ?? {});
|
|
242
|
+
return {
|
|
243
|
+
deviceToken: preserveSecret(p.deviceToken, existing.auth?.deviceToken),
|
|
244
|
+
};
|
|
245
|
+
})();
|
|
246
|
+
const merged = {
|
|
247
|
+
version: 1,
|
|
248
|
+
llm: mergedLlm,
|
|
249
|
+
broker: mergedBroker,
|
|
250
|
+
auth: mergedAuth,
|
|
251
|
+
// 누락 시 기존 유지 (default 리셋 차단)
|
|
252
|
+
subscriptions: has("subscriptions")
|
|
253
|
+
? patch.subscriptions
|
|
254
|
+
: existing.subscriptions,
|
|
255
|
+
allocations: has("allocations")
|
|
256
|
+
? patch.allocations
|
|
257
|
+
: existing.allocations,
|
|
258
|
+
halted: typeof patch.halted === "boolean" ? patch.halted : existing.halted,
|
|
259
|
+
};
|
|
260
|
+
return ClientConfigSchema.parse(merged);
|
|
261
|
+
}
|
|
262
|
+
export const CONFIG_PATHS = { dir: CONFIG_DIR, file: CONFIG_FILE };
|
|
263
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src-server/config.ts"],"names":[],"mappings":"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"}
|