@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,87 @@
|
|
|
1
|
+
/** OpenAI finish_reason → 표준 LlmStopReason 매핑 */
|
|
2
|
+
function mapOpenAIStopReason(raw) {
|
|
3
|
+
switch (raw) {
|
|
4
|
+
case "stop": return "stop";
|
|
5
|
+
case "length": return "max_tokens";
|
|
6
|
+
case "tool_calls": return "tool_use";
|
|
7
|
+
case "function_call": return "tool_use";
|
|
8
|
+
case "content_filter": return "safety";
|
|
9
|
+
default: return "unknown";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const BASE = "https://api.openai.com/v1";
|
|
13
|
+
/**
|
|
14
|
+
* OpenAI 기본 max_tokens — 응답 잘림 방지 (사용자 정정 · 2026-05-03).
|
|
15
|
+
* gpt-4o · gpt-4o-mini 의 model 한계 (16384) 까지 자유 출력 허용.
|
|
16
|
+
* fetch timeout 없음 (무한대 대기).
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_MAX_TOKENS = 16384;
|
|
19
|
+
export function createOpenAIProvider(opts) {
|
|
20
|
+
return {
|
|
21
|
+
kind: "openai",
|
|
22
|
+
async test() {
|
|
23
|
+
if (!opts.apiKey)
|
|
24
|
+
return { ok: false, provider: "openai", error: "apiKey 없음" };
|
|
25
|
+
try {
|
|
26
|
+
const res = await fetch(`${BASE}/models`, {
|
|
27
|
+
headers: { authorization: `Bearer ${opts.apiKey}` },
|
|
28
|
+
});
|
|
29
|
+
if (!res.ok) {
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
provider: "openai",
|
|
33
|
+
error: `HTTP ${res.status}: ${(await res.text()).slice(0, 120)}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const data = (await res.json());
|
|
37
|
+
const has = data.data?.some((m) => m.id === opts.model);
|
|
38
|
+
return {
|
|
39
|
+
ok: !!has,
|
|
40
|
+
provider: "openai",
|
|
41
|
+
model: opts.model,
|
|
42
|
+
detail: has ? "model 확인됨" : "model 목록에 없음",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
return { ok: false, provider: "openai", error: String(e) };
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
async complete(c) {
|
|
50
|
+
if (!opts.apiKey)
|
|
51
|
+
throw new Error("OpenAI apiKey 없음");
|
|
52
|
+
const res = await fetch(`${BASE}/chat/completions`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
"content-type": "application/json",
|
|
56
|
+
authorization: `Bearer ${opts.apiKey}`,
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
model: opts.model,
|
|
60
|
+
messages: [
|
|
61
|
+
...(c.systemPrompt ? [{ role: "system", content: c.systemPrompt }] : []),
|
|
62
|
+
{ role: "user", content: c.userPrompt },
|
|
63
|
+
],
|
|
64
|
+
max_tokens: c.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
65
|
+
temperature: c.temperature ?? 0.2,
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
if (!res.ok)
|
|
69
|
+
throw new Error(`OpenAI HTTP ${res.status}: ${await res.text()}`);
|
|
70
|
+
const data = (await res.json());
|
|
71
|
+
return {
|
|
72
|
+
text: data.choices?.[0]?.message?.content ?? "",
|
|
73
|
+
model: opts.model,
|
|
74
|
+
provider: "openai",
|
|
75
|
+
stopReason: mapOpenAIStopReason(data.choices?.[0]?.finish_reason),
|
|
76
|
+
usage: data.usage
|
|
77
|
+
? {
|
|
78
|
+
inputTokens: data.usage.prompt_tokens ?? 0,
|
|
79
|
+
outputTokens: data.usage.completion_tokens ?? 0,
|
|
80
|
+
}
|
|
81
|
+
: undefined,
|
|
82
|
+
raw: data,
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=openai.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../../../src-server/llm/openai.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Persona executor · 페르소나 단일 실행 사이클
|
|
3
|
+
*
|
|
4
|
+
* 흐름 (SPEC-10):
|
|
5
|
+
* 1. checkLlmCallSafety (emergency stop)
|
|
6
|
+
* 2. runPersonaScreening (frontmatter 기반)
|
|
7
|
+
* 3. Bundle (common + persona) + Layer 3 header + patches 조립
|
|
8
|
+
* 4. LLM 호출
|
|
9
|
+
* 5. logDecision (journal 기록)
|
|
10
|
+
* 6. extractAction → action="buy"/"sell" 시 safety 체크 + placeOrderWithTracking 호출
|
|
11
|
+
* 7. extractWakePlan → ConditionWatcher 자동 등록
|
|
12
|
+
* 8. 결과 반환
|
|
13
|
+
*
|
|
14
|
+
* 사용처:
|
|
15
|
+
* - HTTP API (/api/screen/:persona/decide) · routes.ts 에서 호출
|
|
16
|
+
* - ConditionWatcher 트리거 콜백 · index.ts 에서 호출
|
|
17
|
+
*
|
|
18
|
+
* 자율성 원칙 (SPEC-10): rate limit 폐기 (키움 broker rate limit 만 유효).
|
|
19
|
+
*/
|
|
20
|
+
import { fetchCommonBundle, fetchPersonaBundle, assembleFullPrompt } from "../bundle.js";
|
|
21
|
+
import { loadConfig, SERVER_BASE_URL } from "../config.js";
|
|
22
|
+
import { createLlmProvider } from "../llm/index.js";
|
|
23
|
+
import { collectLayer3Snapshot, formatLayer3Markdown, loadActivePatches } from "../llm/context-builder.js";
|
|
24
|
+
import { logDecision, logOrderRejected } from "../journal/trade-journal.js";
|
|
25
|
+
import { buildPersonaCandidatesContext, runPersonaScreening } from "./runner.js";
|
|
26
|
+
import { extractWakePlan, registerWakePlan } from "./wake-plan.js";
|
|
27
|
+
import { placeOrderWithTracking } from "../broker/kiwoom/index.js";
|
|
28
|
+
import { checkLlmCallSafety, checkOrderSafety } from "../safety.js";
|
|
29
|
+
export function extractDecidedAction(rawText) {
|
|
30
|
+
if (!rawText)
|
|
31
|
+
return null;
|
|
32
|
+
try {
|
|
33
|
+
const m = rawText.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
34
|
+
const t = m ? m[1] : rawText;
|
|
35
|
+
const obj = JSON.parse(t);
|
|
36
|
+
const action = obj.action;
|
|
37
|
+
if (!["buy", "sell", "skip", "hold"].includes(action))
|
|
38
|
+
return null;
|
|
39
|
+
return {
|
|
40
|
+
action,
|
|
41
|
+
symbolCode: typeof obj.symbol_code === "string" ? obj.symbol_code : undefined,
|
|
42
|
+
symbolName: typeof obj.symbol_name === "string" ? obj.symbol_name : undefined,
|
|
43
|
+
quantity: typeof obj.quantity === "number" ? obj.quantity : undefined,
|
|
44
|
+
targetPriceKrw: typeof obj.target_price_krw === "number" ? obj.target_price_krw : undefined,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/* ─────────── 메인 실행기 ─────────── */
|
|
52
|
+
//
|
|
53
|
+
// 자율성 원칙 (사용자 정정 · 2026-05-02 · SPEC-10):
|
|
54
|
+
// - executor 는 LLM 호출 횟수·간격에 어떤 제약도 강제하지 않음
|
|
55
|
+
// - 유일한 rate limit = 키움 REST API (broker 레이어 · SPEC-13 TokenBucket)
|
|
56
|
+
// - 무한 루프 방어는 wake-plan zod schema (SPEC-4) 의 minimum interval 로 처리
|
|
57
|
+
export async function executePersonaDecision(personaSlug, opts = {}) {
|
|
58
|
+
const triggeredBy = opts.triggeredBy;
|
|
59
|
+
// Emergency Stop 체크 (SPEC-26)
|
|
60
|
+
const llmGuard = checkLlmCallSafety();
|
|
61
|
+
if (!llmGuard.ok) {
|
|
62
|
+
return { ok: false, stage: "llm", error: `safety:${llmGuard.code} · ${llmGuard.reason}`, triggeredBy };
|
|
63
|
+
}
|
|
64
|
+
// LLM provider
|
|
65
|
+
const cfg = loadConfig();
|
|
66
|
+
const prov = createLlmProvider(cfg);
|
|
67
|
+
if (!prov) {
|
|
68
|
+
return { ok: false, stage: "llm", error: "LLM provider 미설정", triggeredBy };
|
|
69
|
+
}
|
|
70
|
+
// 스크리닝
|
|
71
|
+
const screening = await runPersonaScreening(personaSlug);
|
|
72
|
+
if (!screening) {
|
|
73
|
+
return { ok: false, stage: "persona", error: `persona '${personaSlug}' 미존재`, triggeredBy };
|
|
74
|
+
}
|
|
75
|
+
if (screening.skipped) {
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
stage: "persona",
|
|
79
|
+
error: `persona screener skipped · ${screening.skipReason ?? ""}`,
|
|
80
|
+
persona: screening.persona,
|
|
81
|
+
triggeredBy,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// Bundle 조립
|
|
85
|
+
// 페르소나 = server (Supabase) 단일 진실 소스 (ADR-009).
|
|
86
|
+
// local yaml 만 있는 페르소나는 정식 페르소나가 아니므로 server bundle fetch 실패 시 그대로 에러.
|
|
87
|
+
// Layer3 snapshot 은 prompt 와 journal 양쪽에 사용 (외부 API 1회만).
|
|
88
|
+
let systemPrompt;
|
|
89
|
+
let layer3Snapshot = null;
|
|
90
|
+
try {
|
|
91
|
+
const common = await fetchCommonBundle(SERVER_BASE_URL);
|
|
92
|
+
const personaB = await fetchPersonaBundle(SERVER_BASE_URL, personaSlug, cfg.auth?.deviceToken);
|
|
93
|
+
const layer2Body = personaB.bundle.body;
|
|
94
|
+
layer3Snapshot = await collectLayer3Snapshot({ personaSlug });
|
|
95
|
+
const baseLayer3 = formatLayer3Markdown(layer3Snapshot);
|
|
96
|
+
const candidatesSection = buildPersonaCandidatesContext(screening, opts.topN ?? 8);
|
|
97
|
+
const layer3 = triggeredBy
|
|
98
|
+
? `${baseLayer3}\n\n## 트리거 이벤트\n\n이번 실행은 감시자 트리거에 의해 발생했습니다.\n- Watcher ID: \`${triggeredBy.watcherId}\`\n- Intent: ${triggeredBy.intent}\n\n${candidatesSection}`
|
|
99
|
+
: `${baseLayer3}\n\n${candidatesSection}`;
|
|
100
|
+
const layer3Patches = loadActivePatches(personaSlug);
|
|
101
|
+
systemPrompt = assembleFullPrompt({
|
|
102
|
+
layer1: common.bundle.body,
|
|
103
|
+
layer2: layer2Body,
|
|
104
|
+
layer3Header: layer3,
|
|
105
|
+
layer3Patches: layer3Patches || undefined,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
return { ok: false, stage: "llm", error: `bundle 조립 실패: ${String(e)}`, persona: screening.persona, triggeredBy };
|
|
110
|
+
}
|
|
111
|
+
// LLM 호출
|
|
112
|
+
// - maxTokens 명시 X → provider 의 모델별 default 사용 (Anthropic 8192 / OpenAI 16384 / Gemini 8192)
|
|
113
|
+
// - 사용자 정정 (2026-05-03): timeout/maxTokens 제한 → 답변 잘림 → 재호출 비용 더 큼 → 없어야 한다.
|
|
114
|
+
// - 잘림 (max_tokens) 감지 시 journal 에 truncated:true + 경고 로그 (사후 추적).
|
|
115
|
+
let llmRes;
|
|
116
|
+
try {
|
|
117
|
+
llmRes = await prov.complete({
|
|
118
|
+
systemPrompt,
|
|
119
|
+
userPrompt: triggeredBy
|
|
120
|
+
? `감시자 트리거로 재호출되었습니다 (${triggeredBy.intent}). 현재 상황을 종합해 오늘 거래 의사결정을 하나의 JSON 으로 응답하세요. action / symbol_code (있을 때) / reasoning / wake_plan / neoul 객체 포함.`
|
|
121
|
+
: "위 스크리닝 결과를 종합해서 오늘 거래 의사결정을 하나의 JSON 으로 응답하세요. action / symbol_code (있을 때) / reasoning / wake_plan / neoul 객체 포함.",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
return { ok: false, stage: "llm", error: `LLM 호출 실패: ${String(e)}`, persona: screening.persona, triggeredBy };
|
|
126
|
+
}
|
|
127
|
+
// 잘림 감지 — max_tokens / safety / unknown 일 때 경고
|
|
128
|
+
const truncated = llmRes.stopReason === "max_tokens";
|
|
129
|
+
if (truncated) {
|
|
130
|
+
console.warn(`[executor] ⚠️ ${personaSlug} LLM 응답이 max_tokens cap 에 막혀 잘림 (model=${llmRes.model} · output=${llmRes.usage?.outputTokens ?? "?"} tokens)`);
|
|
131
|
+
}
|
|
132
|
+
else if (llmRes.stopReason === "safety") {
|
|
133
|
+
console.warn(`[executor] ⚠️ ${personaSlug} LLM safety filter 차단 (model=${llmRes.model})`);
|
|
134
|
+
}
|
|
135
|
+
// Journal 기록 (전략 리뷰 풍부 컨텍스트 · SPEC-9 line 54-62)
|
|
136
|
+
// - reasoning 전체 보존 (truncate 제거 · 긴 추론도 사후 분석 가능)
|
|
137
|
+
// - top 8 후보 마다 rawMetrics + matchedPatterns + humanSummary + reasons (정량 + 자연어 동시)
|
|
138
|
+
// - diagnostics (rejectedBy + patternReject) — "후보 적은 이유" 기록
|
|
139
|
+
// - layer3_snapshot — LLM 호출 시점의 시장·보유·미체결·watcher 상태 재현용
|
|
140
|
+
// - stop_reason + truncated — 잘림 감지 (사후 비정상 case 추적)
|
|
141
|
+
logDecision({
|
|
142
|
+
personaSlug,
|
|
143
|
+
llmContext: {
|
|
144
|
+
reasoning: llmRes.text,
|
|
145
|
+
model: llmRes.model,
|
|
146
|
+
tokens: llmRes.usage,
|
|
147
|
+
stopReason: llmRes.stopReason,
|
|
148
|
+
truncated,
|
|
149
|
+
triggered_by: triggeredBy ?? null,
|
|
150
|
+
screening_summary: {
|
|
151
|
+
universe: screening.screener.universeSize,
|
|
152
|
+
evaluated: screening.screener.evaluated,
|
|
153
|
+
candidates: screening.screener.candidates.length,
|
|
154
|
+
skippedNoCandles: screening.screener.skippedNoCandles,
|
|
155
|
+
elapsedMs: screening.screener.elapsedMs,
|
|
156
|
+
diagnostics: {
|
|
157
|
+
rejectedBy: screening.screener.diagnostics.rejectedBy,
|
|
158
|
+
patternReject: screening.screener.diagnostics.patternReject,
|
|
159
|
+
samples: screening.screener.diagnostics.samples,
|
|
160
|
+
},
|
|
161
|
+
top: screening.screener.candidates.slice(0, 8).map((c) => ({
|
|
162
|
+
code: c.code,
|
|
163
|
+
name: c.name,
|
|
164
|
+
score: c.score,
|
|
165
|
+
matchedPatterns: c.matchedPatterns,
|
|
166
|
+
rawMetrics: c.rawMetrics,
|
|
167
|
+
humanSummary: c.humanSummaries.join(" / "),
|
|
168
|
+
reasons: c.reasons,
|
|
169
|
+
ma: c.ma,
|
|
170
|
+
todayChangePct: c.todayChangePct,
|
|
171
|
+
lastPriceKrw: c.lastPriceKrw,
|
|
172
|
+
})),
|
|
173
|
+
},
|
|
174
|
+
layer3_snapshot: layer3Snapshot,
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
// 주문 실행 (action="buy"/"sell" 시) — SPEC-10 + SPEC-26 safety 게이트
|
|
178
|
+
let orderMeta;
|
|
179
|
+
const decided = extractDecidedAction(llmRes.text);
|
|
180
|
+
if (decided && (decided.action === "buy" || decided.action === "sell")) {
|
|
181
|
+
if (!decided.symbolCode || !decided.quantity) {
|
|
182
|
+
orderMeta = { sent: false, reason: `LLM action=${decided.action} 인데 symbol_code 또는 quantity 누락` };
|
|
183
|
+
console.warn(`[executor] ${personaSlug} 주문 skip · ${orderMeta.reason}`);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const safety = await checkOrderSafety({
|
|
187
|
+
personaSlug,
|
|
188
|
+
symbolCode: decided.symbolCode,
|
|
189
|
+
side: decided.action,
|
|
190
|
+
quantity: decided.quantity,
|
|
191
|
+
priceKrw: decided.targetPriceKrw,
|
|
192
|
+
});
|
|
193
|
+
if (!safety.ok) {
|
|
194
|
+
orderMeta = { sent: false, reason: `safety:${safety.code} · ${safety.reason}` };
|
|
195
|
+
console.warn(`[executor] ${personaSlug} 주문 차단 · ${orderMeta.reason}`);
|
|
196
|
+
logOrderRejected({
|
|
197
|
+
personaSlug,
|
|
198
|
+
symbolCode: decided.symbolCode,
|
|
199
|
+
side: decided.action,
|
|
200
|
+
quantity: decided.quantity,
|
|
201
|
+
priceKrw: decided.targetPriceKrw ?? 0,
|
|
202
|
+
clientOrderId: `safety_${Date.now()}`,
|
|
203
|
+
error: `safety:${safety.code} · ${safety.reason}`,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
try {
|
|
208
|
+
const placed = await placeOrderWithTracking({
|
|
209
|
+
symbolCode: decided.symbolCode,
|
|
210
|
+
side: decided.action,
|
|
211
|
+
quantity: decided.quantity,
|
|
212
|
+
priceKrw: decided.targetPriceKrw,
|
|
213
|
+
type: decided.targetPriceKrw ? "limit" : "market",
|
|
214
|
+
}, { personaSlug });
|
|
215
|
+
if (placed.ok) {
|
|
216
|
+
orderMeta = {
|
|
217
|
+
sent: true,
|
|
218
|
+
clientOrderId: placed.data.clientOrderId,
|
|
219
|
+
brokerOrderNo: placed.data.brokerOrderNo,
|
|
220
|
+
status: placed.data.status,
|
|
221
|
+
};
|
|
222
|
+
console.log(`[executor] ${personaSlug} 주문 접수 · ${decided.action} ${decided.symbolCode} ${decided.quantity}주 · ${placed.data.clientOrderId}`);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
orderMeta = { sent: false, reason: `broker reject · ${placed.error.message}` };
|
|
226
|
+
console.warn(`[executor] ${personaSlug} broker reject · ${placed.error.message}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
orderMeta = { sent: false, reason: `exception · ${String(e)}` };
|
|
231
|
+
console.error(`[executor] ${personaSlug} 주문 예외:`, e);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (decided) {
|
|
237
|
+
// skip / hold — 기록만
|
|
238
|
+
orderMeta = { sent: false, reason: `LLM action=${decided.action}` };
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
orderMeta = { sent: false, reason: "LLM 응답에서 action 파싱 실패" };
|
|
242
|
+
}
|
|
243
|
+
// wake_plan 파싱 + 자동 등록
|
|
244
|
+
let wakePlanMeta;
|
|
245
|
+
const extracted = extractWakePlan(llmRes.text);
|
|
246
|
+
if (!extracted.ok) {
|
|
247
|
+
wakePlanMeta = {
|
|
248
|
+
parsed: null,
|
|
249
|
+
registered: 0,
|
|
250
|
+
skipped: [],
|
|
251
|
+
nextScheduledRegistered: false,
|
|
252
|
+
parseError: extracted.error,
|
|
253
|
+
};
|
|
254
|
+
console.warn(`[executor] ${personaSlug} wake_plan 파싱 실패: ${extracted.error}`);
|
|
255
|
+
}
|
|
256
|
+
else if (extracted.wakePlan) {
|
|
257
|
+
const reg = registerWakePlan(personaSlug, extracted.wakePlan);
|
|
258
|
+
wakePlanMeta = {
|
|
259
|
+
parsed: extracted.wakePlan,
|
|
260
|
+
registered: reg.registered.length,
|
|
261
|
+
skipped: reg.skipped,
|
|
262
|
+
nextScheduledRegistered: reg.nextScheduledRegistered,
|
|
263
|
+
};
|
|
264
|
+
console.log(`[executor] ${personaSlug} wake_plan 등록 · time/price/event ${reg.registered.length} · skipped ${reg.skipped.length} · next_scheduled ${reg.nextScheduledRegistered ? "✓" : "-"}`);
|
|
265
|
+
}
|
|
266
|
+
// SPEC §6.1 + Layer 1 c1.8.0 §wake_plan 가드:
|
|
267
|
+
// wake_plan 누락 + tool call 로도 active watcher 등록 X 면 페르소나가 잠들 위험.
|
|
268
|
+
// 여기서는 비용 폭주 방지를 위해 자동 재호출은 하지 않고 강한 경고만 남긴다.
|
|
269
|
+
// 사용자/UI 가 cycle 결과의 wakePlan.silentDrift = true 를 보고 조치.
|
|
270
|
+
try {
|
|
271
|
+
const { getConditionWatcher } = await import("../watchers/condition-watcher.js");
|
|
272
|
+
const activeAfter = getConditionWatcher().list({ personaSlug, activeOnly: true }).length;
|
|
273
|
+
if ((!wakePlanMeta || wakePlanMeta.registered === 0) && activeAfter === 0) {
|
|
274
|
+
console.error(`[executor] ⚠ ${personaSlug} wake_plan 미등록 + active watcher 0 — 페르소나 잠듦 위험. ` +
|
|
275
|
+
`Layer 1 c1.8.0 §wake_plan 매 응답 필수.`);
|
|
276
|
+
wakePlanMeta = {
|
|
277
|
+
parsed: wakePlanMeta?.parsed ?? null,
|
|
278
|
+
registered: wakePlanMeta?.registered ?? 0,
|
|
279
|
+
skipped: wakePlanMeta?.skipped ?? [],
|
|
280
|
+
nextScheduledRegistered: wakePlanMeta?.nextScheduledRegistered ?? false,
|
|
281
|
+
parseError: wakePlanMeta?.parseError,
|
|
282
|
+
silentDrift: true,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
catch {
|
|
287
|
+
// 가드 실패는 cycle 자체를 막지 않음.
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
ok: true,
|
|
291
|
+
persona: screening.persona,
|
|
292
|
+
screening: {
|
|
293
|
+
universe: screening.screener.universeSize,
|
|
294
|
+
evaluated: screening.screener.evaluated,
|
|
295
|
+
candidates: screening.screener.candidates.length,
|
|
296
|
+
topCandidates: screening.screener.candidates.slice(0, opts.topN ?? 8),
|
|
297
|
+
},
|
|
298
|
+
llm: {
|
|
299
|
+
model: llmRes.model,
|
|
300
|
+
tokens: llmRes.usage,
|
|
301
|
+
response: llmRes.text,
|
|
302
|
+
assembled_length: systemPrompt.length,
|
|
303
|
+
},
|
|
304
|
+
wakePlan: wakePlanMeta,
|
|
305
|
+
order: orderMeta,
|
|
306
|
+
triggeredBy,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/* ─────────── 진단 ─────────── */
|
|
310
|
+
//
|
|
311
|
+
// 진단 메타는 trade-journal 의 readEvents() 에서 구할 수 있음.
|
|
312
|
+
// 별도 module-level Map 추적은 폐기 (SPEC-10 정정 · rate limit 폐기와 함께).
|
|
313
|
+
//
|
|
314
|
+
// e.g. 페르소나의 마지막 실행 시각 / 시간당 호출 수 조회:
|
|
315
|
+
// const events = readEvents({ eventTypes: ["decision"], personaSlug, from: oneHourAgo });
|
|
316
|
+
// const lastRunAt = events.at(-1)?.timestamp;
|
|
317
|
+
// const recentHourCount = events.length;
|
|
318
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../src-server/personas/executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 페르소나 로더 · parser
|
|
3
|
+
*
|
|
4
|
+
* 입력 소스 (우선순위):
|
|
5
|
+
* 1. 서버 Bundle API 의 persona body (Supabase · 실 배포)
|
|
6
|
+
* └ body markdown 상단의 `---` frontmatter 를 추출
|
|
7
|
+
* 2. 로컬 override 파일 `~/.mulsok-traders/personas/<slug>.yaml`
|
|
8
|
+
* └ Supabase 배포 전 개발용 · frontmatter 만 저장 (본문 없음)
|
|
9
|
+
*
|
|
10
|
+
* 출력:
|
|
11
|
+
* PersonaDef = { slug, frontmatter, promptBody, source }
|
|
12
|
+
*
|
|
13
|
+
* 캐시:
|
|
14
|
+
* 60초 (빈번한 스크리닝 호출 고려 · Supabase 부담 경감)
|
|
15
|
+
*/
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import os from "node:os";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import matter from "gray-matter";
|
|
20
|
+
import { fetchPersonaBundle } from "../bundle.js";
|
|
21
|
+
import { SERVER_BASE_URL, loadConfig } from "../config.js";
|
|
22
|
+
import { PersonaFrontmatterSchema, } from "./schema.js";
|
|
23
|
+
const CACHE = new Map();
|
|
24
|
+
const CACHE_TTL_MS = 60_000;
|
|
25
|
+
const OVERRIDE_DIR = path.join(os.homedir(), ".mulsok-traders", "personas");
|
|
26
|
+
/**
|
|
27
|
+
* 페르소나 slug 로 정의 확보.
|
|
28
|
+
*
|
|
29
|
+
* @param slug 페르소나 슬러그 (예: "neoul")
|
|
30
|
+
* @returns PersonaDef · Supabase 가 있으면 그 frontmatter + 본문 · 없으면 로컬 override · 둘 다 없으면 null
|
|
31
|
+
*/
|
|
32
|
+
export async function loadPersona(slug) {
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
const cached = CACHE.get(slug);
|
|
35
|
+
if (cached && cached.expiresAt > now)
|
|
36
|
+
return cached.def;
|
|
37
|
+
const def = await loadPersonaFresh(slug);
|
|
38
|
+
if (def)
|
|
39
|
+
CACHE.set(slug, { def, expiresAt: now + CACHE_TTL_MS });
|
|
40
|
+
return def;
|
|
41
|
+
}
|
|
42
|
+
/** 캐시 무시 · 강제 재로드 */
|
|
43
|
+
export async function reloadPersona(slug) {
|
|
44
|
+
CACHE.delete(slug);
|
|
45
|
+
return loadPersona(slug);
|
|
46
|
+
}
|
|
47
|
+
async function loadPersonaFresh(slug) {
|
|
48
|
+
let supabaseFm = null;
|
|
49
|
+
let supabaseBody = "";
|
|
50
|
+
let rawBody;
|
|
51
|
+
let hashOk;
|
|
52
|
+
let version;
|
|
53
|
+
/* 1. Supabase 시도 (device_token 인증 — bundle endpoint 가 401 요구) */
|
|
54
|
+
try {
|
|
55
|
+
const cfg = loadConfig();
|
|
56
|
+
const bundleRes = await fetchPersonaBundle(SERVER_BASE_URL, slug, cfg.auth?.deviceToken);
|
|
57
|
+
rawBody = bundleRes.bundle.body;
|
|
58
|
+
hashOk = bundleRes.hash_ok;
|
|
59
|
+
version = bundleRes.bundle.version;
|
|
60
|
+
const parsed = parseMarkdownWithFrontmatter(rawBody);
|
|
61
|
+
if (parsed) {
|
|
62
|
+
supabaseFm = parsed.frontmatter;
|
|
63
|
+
supabaseBody = parsed.body;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
// frontmatter 없는 레거시 body · frontmatter 없이 그대로
|
|
67
|
+
supabaseBody = rawBody;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Supabase 실패는 조용히 override 로 fallback
|
|
72
|
+
}
|
|
73
|
+
/* 2. 로컬 override */
|
|
74
|
+
const overrideFm = readLocalOverride(slug);
|
|
75
|
+
if (!supabaseFm && !overrideFm) {
|
|
76
|
+
// Supabase body 는 있지만 frontmatter 없고 override 도 없음 → 페르소나 스크리너 쓸 수 없음
|
|
77
|
+
if (supabaseBody) {
|
|
78
|
+
return {
|
|
79
|
+
slug,
|
|
80
|
+
frontmatter: {
|
|
81
|
+
persona: slug,
|
|
82
|
+
version: version ?? "unknown",
|
|
83
|
+
},
|
|
84
|
+
promptBody: supabaseBody,
|
|
85
|
+
source: "supabase",
|
|
86
|
+
rawBody,
|
|
87
|
+
hashOk,
|
|
88
|
+
version,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
// override 가 있으면 supabase fm 위에 얹기 (override 가 우선)
|
|
94
|
+
const merged = overrideFm
|
|
95
|
+
? { ...(supabaseFm ?? { persona: slug, version: version ?? "override" }), ...overrideFm }
|
|
96
|
+
: supabaseFm;
|
|
97
|
+
const source = overrideFm && supabaseFm
|
|
98
|
+
? "mixed"
|
|
99
|
+
: overrideFm
|
|
100
|
+
? "local-override"
|
|
101
|
+
: "supabase";
|
|
102
|
+
return {
|
|
103
|
+
slug,
|
|
104
|
+
frontmatter: merged,
|
|
105
|
+
promptBody: supabaseBody,
|
|
106
|
+
source,
|
|
107
|
+
rawBody,
|
|
108
|
+
hashOk,
|
|
109
|
+
version,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function readLocalOverride(slug) {
|
|
113
|
+
const yamlPath = path.join(OVERRIDE_DIR, `${slug}.yaml`);
|
|
114
|
+
const ymlPath = path.join(OVERRIDE_DIR, `${slug}.yml`);
|
|
115
|
+
const mdPath = path.join(OVERRIDE_DIR, `${slug}.md`);
|
|
116
|
+
const candidates = [yamlPath, ymlPath, mdPath];
|
|
117
|
+
for (const p of candidates) {
|
|
118
|
+
if (!fs.existsSync(p))
|
|
119
|
+
continue;
|
|
120
|
+
try {
|
|
121
|
+
const raw = fs.readFileSync(p, "utf8");
|
|
122
|
+
const frontmatter = p.endsWith(".md")
|
|
123
|
+
? parseMarkdownWithFrontmatter(raw)?.frontmatter ?? null
|
|
124
|
+
: parseYamlAsFrontmatter(raw);
|
|
125
|
+
if (!frontmatter)
|
|
126
|
+
continue;
|
|
127
|
+
const res = PersonaFrontmatterSchema.safeParse({ persona: slug, version: "override", ...frontmatter });
|
|
128
|
+
if (!res.success) {
|
|
129
|
+
throw new Error(`Override frontmatter validation 실패 · ${slug}: ${formatZodError(res.error)}`);
|
|
130
|
+
}
|
|
131
|
+
return res.data;
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
console.warn(`[personas] override 로드 실패 (${p}): ${String(e)}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
export function parseMarkdownWithFrontmatter(body) {
|
|
140
|
+
const trimmed = body.trimStart();
|
|
141
|
+
if (!trimmed.startsWith("---"))
|
|
142
|
+
return null;
|
|
143
|
+
const parsed = matter(body);
|
|
144
|
+
const fmRaw = parsed.data;
|
|
145
|
+
if (!fmRaw || Object.keys(fmRaw).length === 0)
|
|
146
|
+
return null;
|
|
147
|
+
const res = PersonaFrontmatterSchema.safeParse(fmRaw);
|
|
148
|
+
if (!res.success) {
|
|
149
|
+
throw new Error(`Persona frontmatter validation 실패: ${formatZodError(res.error)}`);
|
|
150
|
+
}
|
|
151
|
+
return { frontmatter: res.data, body: parsed.content };
|
|
152
|
+
}
|
|
153
|
+
function parseYamlAsFrontmatter(raw) {
|
|
154
|
+
// gray-matter 내부의 js-yaml 재사용 · 단독 yaml 을 "---\n...\n---" 로 감싸서 파싱
|
|
155
|
+
const wrapped = `---\n${raw.trim()}\n---\n`;
|
|
156
|
+
const parsed = matter(wrapped);
|
|
157
|
+
return parsed.data;
|
|
158
|
+
}
|
|
159
|
+
function formatZodError(err) {
|
|
160
|
+
return err.issues
|
|
161
|
+
.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`)
|
|
162
|
+
.join(" · ");
|
|
163
|
+
}
|
|
164
|
+
export { OVERRIDE_DIR as PERSONA_OVERRIDE_DIR };
|
|
165
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../src-server/personas/loader.ts"],"names":[],"mappings":"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"}
|