๐Ÿค– Cursor AI ์ „์šฉ: ํฌ์ง€์…˜ ์ข…์†์„ฑ ์„ ํƒ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์š”์ฒญ ํ”„๋กฌํ”„ํŠธ ๐ŸŽฏ ๊ตฌํ˜„ ๋ชฉํ‘œ (Goal) ๋งค๋งค ์ „๋žต ์‹คํ–‰ ๋ฐ ๋ฐฑํ…Œ์ŠคํŒ… ์—”์ง„ ๊ฐ€๋™ ์‹œ, "๋งค๋„ ์‹œ๊ทธ๋„์ด ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ด์ „ ๋งค์ˆ˜ ๊ธฐ๋ก(ํฌ์ง€์…˜)์ด ์žˆ์–ด์•ผ๋งŒ ์‹คํ–‰ํ•  ๊ฒƒ์ธ๊ฐ€"์— ๋Œ€ํ•œ ์ œ์•ฝ ์กฐ๊ฑด์„ ์œ ์ €๊ฐ€ ์ง์ ‘ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋„๋ก ์‹œ์Šคํ…œ์„ ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค. ์œ ์ €์˜ ์„ค์ •์— ๋”ฐ๋ผ Ta4j์˜ ์—„๊ฒฉํ•œ ํฌ์ง€์…˜ ๊ฒ€์ฆ ๋กœ์ง์„ ํƒ€๊ฑฐ๋‚˜, ํ˜น์€ ์ˆœ์ˆ˜ ์ง€ํ‘œ ์กฐ๊ฑด ๋งค์นญ ๋ถ„๊ธฐ๋กœ ์šฐํšŒ ๊ตฌ๋™๋˜๋„๋ก ๊ตฌํ˜„ํ•ด ์ฃผ์„ธ์š”. ๐Ÿ› ๏ธ 1. ์š”๊ตฌ์‚ฌํ•ญ ๋ช…์„ธ ๋ฐ ์ •์ฑ… (Requirements) โ‘  ์„ค์ • ํ™”๋ฉด UI ํ•ญ๋ชฉ ์ถ”๊ฐ€ (Frontend) ๋งค๋งค ์‹œ๊ทธ๋„ ๋ฐœ์ƒ ์กฐ๊ฑด ์ œ์–ด (๋ผ๋””์˜ค ๋ฒ„ํŠผ ๋˜๋Š” ์…€๋ ‰ํŠธ ๋ฐ•์Šค): 1. ๋ณด์œ  ์ž์‚ฐ ๊ธฐ์ค€ ๋งค๋งค (Long-Only ๋ชจ๋“œ): ๊ธฐ์กด์— ๋งค์ˆ˜ํ•œ ์ด๋ ฅ(Open Position)์ด ์™„๋ฒฝํžˆ ์กด์žฌํ•  ๋•Œ๋งŒ ๋งค๋„ ์‹œ๊ทธ๋„ ํ—ˆ์šฉ. 2. ์ˆœ์ˆ˜ ๊ธฐ์ˆ  ์ง€ํ‘œ ๊ธฐ์ค€ ๋งค๋งค (Signal-Only ๋ชจ๋“œ): ์ด์ „ ๋งค์ˆ˜ ์ด๋ ฅ ์กด์žฌ ์—ฌ๋ถ€์™€ ์™„์ „ํžˆ ๋ฌด๊ด€ํ•˜๊ฒŒ, ๊ธฐ์ˆ ์  ์ง€ํ‘œ ๊ทœ์น™๋งŒ ๋งŒ์กฑํ•˜๋ฉด ๋งค๋„ ์‹œ๊ทธ๋„์„ ์ฆ‰์‹œ ํ—ˆ์šฉ. โ‘ก ๋ฐฑ์—”๋“œ ์ „๋žต ํ‰๊ฐ€ ์ฝ”์–ด ์ œ์–ด ์ •์ฑ… (Backend Logic) ์œ ์ €๊ฐ€ ์„ ํƒํ•œ ์˜ต์…˜๊ฐ’์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค(UserStrategySettings)์— ์ €์žฅ๋˜๋ฉฐ, ์‹ค์‹œ๊ฐ„ ๊ฐ์‹œ ์—”์ง„ ๋ฐ ๊ณผ๊ฑฐ ๋ฐ์ดํ„ฐ ๋ฐฑํ…Œ์ŠคํŒ… ์ค‘๊ณ„๊ธฐ(HistoricalDataService) ์—ฐ์‚ฐ ์‹œ ๊ณตํ†ต ๋ถ„๊ธฐ ์กฐ๊ฑด์œผ๋กœ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. [Mode 1] LONG_ONLY (๋ณด์œ  ์ž์‚ฐ ๊ธฐ์ค€ ๋งค๋งค) ์ฒ˜๋ฆฌ ํ”„๋กœํ† ์ฝœ: Ta4j์˜ ํ‘œ์ค€ strategy.shouldExit(index, tradingRecord) ํ˜•์‹์„ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๋‚ด๋ถ€์ ์œผ๋กœ TradingRecord์— ํ˜„์žฌ ์—ด๋ ค ์žˆ๋Š” ํฌ์ง€์…˜์ด ์—†๋‹ค๋ฉด ๋งค๋„ ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜๋”๋ผ๋„ ์‹œ๊ทธ๋„์„ ๋‚ด๋ณด๋‚ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. [Mode 2] SIGNAL_ONLY (์ˆœ์ˆ˜ ๊ธฐ์ˆ  ์ง€ํ‘œ ๊ธฐ์ค€ ๋งค๋งค) ์ฒ˜๋ฆฌ ํ”„๋กœํ† ์ฝœ: Ta4j Strategy ๊ฐ์ฒด์˜ ํฌ์ง€์…˜ ๊ฒ€์ฆ์„ ์šฐํšŒํ•˜๊ธฐ ์œ„ํ•ด, ๋‚ด๋ถ€ Rule ์ธ์Šคํ„ด์Šค๋ฅผ ์ง์ ‘ ์ถ”์ถœํ•˜์—ฌ ํŒ๋‹จํ•ฉ๋‹ˆ๋‹ค. ๋งค์ˆ˜ ํŒ๋‹จ ์‹œ: strategy.getEntryRule().isSatisfied(index) ํ˜ธ์ถœ ๋งค๋„ ํŒ๋‹จ ์‹œ: strategy.getExitRule().isSatisfied(index) ํ˜ธ์ถœ โž” ์ด์ „ ๋งค์ˆ˜ ๊ธฐ๋ก ์œ ๋ฌด์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ ์ง€ํ‘œ ์ˆ˜์‹๋งŒ ๋งž์œผ๋ฉด ๋ฌด์กฐ๊ฑด ๋งค๋„("SELL") ์‹œ๊ทธ๋„ ํ™•์ • ๐Ÿ’พ 2. ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ๋ฐ ์ธํ„ฐํŽ˜์ด์Šค ์„ค๊ณ„ (Data Struct) โ‘  [PUT] /api/strategy/settings (๊ธฐ์กด ์„ค์ • API ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ํ™•์žฅ) JSON { "market": "KRW-BTC", "isLiveCheck": true, "executionType": "CANDLE_CLOSE", "positionMode": "SIGNAL_ONLY" // "LONG_ONLY" (๋ฐฉ์‹1) ๋˜๋Š” "SIGNAL_ONLY" (๋ฐฉ์‹2) ์ถ”๊ฐ€ } โ‘ก API ๋ฐ˜์˜์ฒ˜ ๋ช…์‹œ ์ด positionMode ์˜ต์…˜์€ ์•„๋ž˜ ๋‘ ๊ฐ€์ง€ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๋‚ด๋ถ€์˜ ์‹œ๊ทธ๋„ ํŒ์ • ๊ตฌ๊ฐ„์— ๋ฐ˜๋“œ์‹œ ๋…น์•„๋“ค์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. HistoricalDataService: ์ฐจํŠธ ์ดˆ๊ธฐ ๋กœ๋”ฉ ๋ฐ ๊ณผ๊ฑฐ ์Šคํฌ๋กค ์‹œ, ๊ณผ๊ฑฐ ์บ”๋“ค ๋ฐฐ์—ด ๊ฐ๊ฐ์— "BUY"/"SELL"์„ ํƒœ๊น…ํ•˜์—ฌ ๋„˜๊ฒจ์ฃผ๋Š” ๋ฐฑํ…Œ์ŠคํŒ… ๋กœ์ง ๋ถ€๋ฌธ. LiveStrategyEvaluator: 3์ดˆ ์Šค์ผ€์ค„๋Ÿฌ ๋˜๋Š” ๋ด‰ ๋งˆ๊ฐ ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ, ์‹ค์‹œ๊ฐ„ STOMP ํŒจํ‚ท์˜ signal ํ•„๋“œ์— ๊ฐ’์„ ์ฃผ์ž…ํ•˜๋Š” ์‹ค์‹œ๊ฐ„ ๊ฐ์‹œ ๋กœ์ง ๋ถ€๋ฌธ. ๐Ÿ’ป 3. ๋ฐฑ์—”๋“œ ์ฐธ๊ณ  ๊ฐ€์ด๋“œ ์ฝ”๋“œ (Reference Code for AI) Java @Component @RequiredArgsConstructor public class StrategySignalDeterminer { /** * ์œ ์ € ์„ค์ • ์ •๋ณด(positionMode)์— ๋”ฐ๋ผ ํฌ์ง€์…˜ ์ œ์•ฝ ์œ ๋ฌด๋ฅผ ๋ถ„๊ธฐํ•˜์—ฌ ์‹œ๊ทธ๋„์„ ๊ฒฐ์ •ํ•˜๋Š” ์ฝ”์–ด ๋ฉ”์„œ๋“œ */ public String determineSignal(Strategy strategy, BarSeries series, TradingRecord tradingRecord, int index, String positionMode) { // 1. ๋ณด์œ  ์ž์‚ฐ ๊ธฐ์ค€ ๋งค๋งค (Long-Only) -> Ta4j ํ‘œ์ค€ ์—„๊ฒฉ ๋ชจ๋“œ if ("LONG_ONLY".equals(positionMode)) { if (strategy.shouldEnter(index, tradingRecord)) { return "BUY"; } if (strategy.shouldExit(index, tradingRecord)) { return "SELL"; } return "NONE"; } // 2. ์ˆœ์ˆ˜ ๊ธฐ์ˆ  ์ง€ํ‘œ ๊ธฐ์ค€ ๋งค๋งค (Signal-Only) -> ํฌ์ง€์…˜ ๋ฝ ์šฐํšŒ ๋ชจ๋“œ if ("SIGNAL_ONLY".equals(positionMode)) { boolean isEntrySatisfied = strategy.getEntryRule().isSatisfied(index); boolean isExitSatisfied = strategy.getExitRule().isSatisfied(index); if (isEntrySatisfied) return "BUY"; if (isExitSatisfied) return "SELL"; return "NONE"; } return "NONE"; } }