ai 전략 agent 수정
This commit is contained in:
@@ -1464,17 +1464,6 @@ export default function StrategyEditorPage({
|
||||
[templateRows, templateSearch],
|
||||
);
|
||||
|
||||
const buildAiContext = useCallback((): AiStrategyContext => {
|
||||
const buyEncoded = encodeConditionForSave(buyEditorState);
|
||||
const sellEncoded = encodeConditionForSave(sellEditorState);
|
||||
return {
|
||||
buyCondition: buyEncoded,
|
||||
sellCondition: sellEncoded,
|
||||
signalTab: buySellTab(signalTab),
|
||||
candleType: currentEditorState.startMeta[START_NODE_ID]?.candleType,
|
||||
};
|
||||
}, [buyEditorState, sellEditorState, signalTab, currentEditorState]);
|
||||
|
||||
const applyAiStrategy = useCallback((buyDsl: LogicNode | null, sellDsl: LogicNode | null) => {
|
||||
if (editorMode === 'graph') layoutFlushRef.current?.();
|
||||
|
||||
@@ -1799,6 +1788,52 @@ export default function StrategyEditorPage({
|
||||
evalCommissionRate,
|
||||
]);
|
||||
|
||||
const buildAiContext = useCallback((): AiStrategyContext => {
|
||||
const buyEncoded = encodeConditionForSave(buyEditorState);
|
||||
const sellEncoded = encodeConditionForSave(sellEditorState);
|
||||
|
||||
const evaluation = evalBacktestSignals.length > 0 || signalTab === 'eval'
|
||||
? {
|
||||
market: evalMarket,
|
||||
timeframe: qbStrategyTimeframe,
|
||||
barCount: evalBars.length,
|
||||
evaluationBarCount: evalWindowMeta.evaluationBarCount,
|
||||
scanRunning: evalSignalScanRunning,
|
||||
signalsTotal: evalBacktestSignals.length,
|
||||
buyCount: evalBacktestSignals.filter(s => s.type === 'BUY').length,
|
||||
sellCount: evalBacktestSignals.filter(s => s.type === 'SELL').length,
|
||||
recentSignals: evalBacktestSignals.slice(-12).map(s => ({
|
||||
type: s.type,
|
||||
time: s.time,
|
||||
timeLabel: new Date(s.time * 1000).toLocaleString('ko-KR'),
|
||||
price: s.price,
|
||||
barIndex: s.barIndex,
|
||||
})),
|
||||
summary: evalAnalysisSummary ?? null,
|
||||
}
|
||||
: null;
|
||||
|
||||
return {
|
||||
buyCondition: buyEncoded,
|
||||
sellCondition: sellEncoded,
|
||||
signalTab: buySellTab(signalTab),
|
||||
candleType: currentEditorState.startMeta[START_NODE_ID]?.candleType,
|
||||
evaluation,
|
||||
};
|
||||
}, [
|
||||
buyEditorState,
|
||||
sellEditorState,
|
||||
signalTab,
|
||||
currentEditorState,
|
||||
evalBacktestSignals,
|
||||
evalMarket,
|
||||
qbStrategyTimeframe,
|
||||
evalBars,
|
||||
evalWindowMeta.evaluationBarCount,
|
||||
evalSignalScanRunning,
|
||||
evalAnalysisSummary,
|
||||
]);
|
||||
|
||||
const handleEvalSignalsChange = useCallback((signals: BacktestSignal[]) => {
|
||||
setEvalBacktestSignals(signals);
|
||||
}, []);
|
||||
|
||||
@@ -10,6 +10,8 @@ export interface AiStrategyContext {
|
||||
sellCondition: LogicNode | null;
|
||||
signalTab: 'buy' | 'sell';
|
||||
candleType?: string;
|
||||
/** 평가탭 결과 (해석·문제 진단용) — 평가 미실행 시 null */
|
||||
evaluation?: unknown;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
@@ -33,12 +35,24 @@ type Turn = {
|
||||
};
|
||||
|
||||
const SUGGESTIONS = [
|
||||
'일목균형표 매수 전략 추천해줘',
|
||||
'전환선이 기준선을 상향돌파하면 매수',
|
||||
'26봉 전 후행스팬이 종가를 상향돌파하고 전환선이 기준선을 상향돌파하면 매수',
|
||||
'RSI 30 이하에서 상향돌파하면 매수, 70 이상이면 매도',
|
||||
'지금 전략을 더 엄격하게 만들어줘',
|
||||
'평가 결과를 해석하고 매수 시그널이 안 나오는 원인을 분석해줘',
|
||||
];
|
||||
|
||||
const CLEAR_REPLY =
|
||||
'전략을 초기화했습니다. 모든 조건을 비워 처음 상태로 되돌렸어요. 원하는 매매 전략을 자연어로 설명해 주세요.';
|
||||
|
||||
/** 초기화/리셋/새로 작성 의도 감지 — LLM 없이 즉시 처리 (서버 503에도 동작) */
|
||||
export function isResetRequest(text: string): boolean {
|
||||
const t = text.trim();
|
||||
if (!t) return false;
|
||||
// "조건 추가"처럼 추가 의도가 섞이면 초기화로 보지 않음
|
||||
if (/추가|더해|덧붙|넣어|보태/.test(t)) return false;
|
||||
return /(초기화|리셋|reset|clear|처음\s*부터|처음\s*으로|다시\s*처음|새\s*전략|새로운\s*전략|새\s*조건|새로운\s*조건|전부\s*삭제|전체\s*삭제|모두\s*삭제|다\s*지워|다\s*삭제|비우[기게]|싹\s*비워|새로\s*시작|처음\s*상태)/i.test(t);
|
||||
}
|
||||
|
||||
const AiStrategyPanel: React.FC<Props> = ({ getContext, onApplyStrategy }) => {
|
||||
const [turns, setTurns] = useState<Turn[]>([]);
|
||||
const [input, setInput] = useState('');
|
||||
@@ -61,11 +75,37 @@ const AiStrategyPanel: React.FC<Props> = ({ getContext, onApplyStrategy }) => {
|
||||
[turns],
|
||||
);
|
||||
|
||||
/** 모든 조건을 비우고(초기화) 되돌리기 가능한 어시스턴트 턴 생성 */
|
||||
const buildClearTurn = useCallback((ctx: AiStrategyContext, reply?: string): Turn => {
|
||||
const snapshot = { buy: ctx.buyCondition, sell: ctx.sellCondition };
|
||||
onApplyStrategy(null, null);
|
||||
return {
|
||||
id: nextId(),
|
||||
role: 'assistant',
|
||||
content: reply || CLEAR_REPLY,
|
||||
action: 'build',
|
||||
snapshot,
|
||||
produced: { buy: null, sell: null },
|
||||
};
|
||||
}, [onApplyStrategy]);
|
||||
|
||||
const send = useCallback(async (text: string) => {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed || busy) return;
|
||||
|
||||
setError(null);
|
||||
const ctx = getContext();
|
||||
|
||||
// 초기화/리셋 요청 — LLM 호출 없이 즉시 처리
|
||||
if (isResetRequest(trimmed)) {
|
||||
const userTurn: Turn = { id: nextId(), role: 'user', content: trimmed };
|
||||
const clearTurn = buildClearTurn(ctx);
|
||||
setInput('');
|
||||
setTurns(prev => [...prev, userTurn, clearTurn]);
|
||||
scrollToBottom();
|
||||
return;
|
||||
}
|
||||
|
||||
const userTurn: Turn = { id: nextId(), role: 'user', content: trimmed };
|
||||
const outgoing: StrategyAgentChatMessage[] = [...history, { role: 'user', content: trimmed }];
|
||||
setTurns(prev => [...prev, userTurn]);
|
||||
@@ -73,11 +113,12 @@ const AiStrategyPanel: React.FC<Props> = ({ getContext, onApplyStrategy }) => {
|
||||
setBusy(true);
|
||||
scrollToBottom();
|
||||
|
||||
const ctx = getContext();
|
||||
try {
|
||||
const res = await requestStrategyAgentChat(outgoing, ctx);
|
||||
|
||||
if (res.action === 'build' && res.hasStrategy) {
|
||||
if (res.action === 'clear') {
|
||||
setTurns(prev => [...prev, buildClearTurn(ctx, res.reply)]);
|
||||
} else if (res.action === 'build' && res.hasStrategy) {
|
||||
const snapshot = { buy: ctx.buyCondition, sell: ctx.sellCondition };
|
||||
// AI가 생략한 측은 기존 조건 유지
|
||||
const produced = {
|
||||
@@ -114,7 +155,7 @@ const AiStrategyPanel: React.FC<Props> = ({ getContext, onApplyStrategy }) => {
|
||||
setBusy(false);
|
||||
scrollToBottom();
|
||||
}
|
||||
}, [busy, history, getContext, onApplyStrategy, scrollToBottom]);
|
||||
}, [busy, history, getContext, onApplyStrategy, scrollToBottom, buildClearTurn]);
|
||||
|
||||
const handleRevert = useCallback((turnId: number) => {
|
||||
setTurns(prev => prev.map(t => {
|
||||
|
||||
@@ -1869,7 +1869,7 @@ export interface StrategyAgentChatMessage {
|
||||
|
||||
export interface StrategyAgentChatApiResponse {
|
||||
reply: string;
|
||||
action: 'ask' | 'build';
|
||||
action: 'ask' | 'build' | 'clear';
|
||||
buyCondition?: unknown | null;
|
||||
sellCondition?: unknown | null;
|
||||
model?: string;
|
||||
|
||||
@@ -12,7 +12,7 @@ export type { StrategyAgentChatMessage } from './backendApi';
|
||||
|
||||
export interface StrategyAgentResult {
|
||||
reply: string;
|
||||
action: 'ask' | 'build';
|
||||
action: 'ask' | 'build' | 'clear';
|
||||
/** 검증·정규화된 매수 조건 (build이고 유효할 때만) */
|
||||
buyCondition: LogicNode | null;
|
||||
/** 검증·정규화된 매도 조건 */
|
||||
@@ -93,7 +93,8 @@ export async function requestStrategyAgentChat(
|
||||
context: unknown,
|
||||
): Promise<StrategyAgentResult> {
|
||||
const res: StrategyAgentChatApiResponse = await postStrategyAgentChat(messages, context);
|
||||
const action = res.action === 'build' ? 'build' : 'ask';
|
||||
const action: 'ask' | 'build' | 'clear' =
|
||||
res.action === 'build' ? 'build' : res.action === 'clear' ? 'clear' : 'ask';
|
||||
const buyCondition = action === 'build' ? sanitizeAiLogicNode(res.buyCondition) : null;
|
||||
const sellCondition = action === 'build' ? sanitizeAiLogicNode(res.sellCondition) : null;
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user