From e6160fffc69229f4b2bb83b282febd5784c6a9eb Mon Sep 17 00:00:00 2001 From: zephyrdark Date: Thu, 19 Feb 2026 15:53:01 +0900 Subject: [PATCH] fix: add KJB strategy to frontend strategy list and backtest form - Add KJB card to strategy page with Zap icon - Create /strategy/kjb detail page with ranking table and rules reference - Add KJB option to backtest strategy dropdown - Add KJB-specific params UI (max positions, cash reserve, stop-loss, targets) - Hide rebalance period selector when KJB is selected (uses daily simulation) Co-Authored-By: Claude Opus 4.6 --- frontend/src/app/backtest/page.tsx | 94 +++++++++++- frontend/src/app/strategy/kjb/page.tsx | 198 +++++++++++++++++++++++++ frontend/src/app/strategy/page.tsx | 11 +- 3 files changed, 301 insertions(+), 2 deletions(-) create mode 100644 frontend/src/app/strategy/kjb/page.tsx diff --git a/frontend/src/app/backtest/page.tsx b/frontend/src/app/backtest/page.tsx index 28c8a99..134a490 100644 --- a/frontend/src/app/backtest/page.tsx +++ b/frontend/src/app/backtest/page.tsx @@ -60,6 +60,7 @@ const strategyOptions = [ { value: 'multi_factor', label: '멀티 팩터' }, { value: 'quality', label: '슈퍼 퀄리티' }, { value: 'value_momentum', label: '밸류 모멘텀' }, + { value: 'kjb', label: '김종봉 단기매매' }, ]; const periodOptions = [ @@ -98,6 +99,13 @@ export default function BacktestPage() { const [vmValueWeight, setVmValueWeight] = useState(0.5); const [vmMomentumWeight, setVmMomentumWeight] = useState(0.5); + // KJB params + const [kjbMaxPositions, setKjbMaxPositions] = useState(10); + const [kjbCashReserve, setKjbCashReserve] = useState(0.3); + const [kjbStopLoss, setKjbStopLoss] = useState(0.03); + const [kjbTarget1, setKjbTarget1] = useState(0.05); + const [kjbTarget2, setKjbTarget2] = useState(0.10); + useEffect(() => { const init = async () => { try { @@ -167,6 +175,14 @@ export default function BacktestPage() { value_weight: vmValueWeight, momentum_weight: vmMomentumWeight, }; + } else if (strategyType === 'kjb') { + strategyParams = { + max_positions: kjbMaxPositions, + cash_reserve_ratio: kjbCashReserve, + stop_loss_pct: kjbStopLoss, + target1_pct: kjbTarget1, + target2_pct: kjbTarget2, + }; } const response = await api.post<{ id: number }>('/api/backtest', { @@ -379,7 +395,8 @@ export default function BacktestPage() { - {/* Rebalancing Period */} + {/* Rebalancing Period (not for KJB) */} + {strategyType !== 'kjb' && (
+ )} {/* Initial Capital & Top N */}
@@ -514,6 +532,80 @@ export default function BacktestPage() {
)} + {strategyType === 'kjb' && ( +
+ +
+
+ + setKjbMaxPositions(parseInt(e.target.value))} + className="h-8" + /> +
+
+ + setKjbCashReserve(parseFloat(e.target.value))} + className="h-8" + /> +
+
+
+
+ + setKjbStopLoss(parseFloat(e.target.value))} + className="h-8" + /> +
+
+ + setKjbTarget1(parseFloat(e.target.value))} + className="h-8" + /> +
+
+ + setKjbTarget2(parseFloat(e.target.value))} + className="h-8" + /> +
+
+
+ )} + {/* Advanced options toggle */} + + + + {/* Rules Reference */} + + + 매매 규칙 + + +

매수: RS {'>'} 100 (KOSPI 대비) AND (20일 박스 돌파 OR 장대양봉)

+

손절: 진입가 -3%

+

1차 익절: +5% → 50% 매도, 손절선 본전 상향

+

2차 익절: +10% → 나머지 전량 매도

+

현금 비중: 30% 유지, 최대 10종목

+
+
+ + {/* Results */} + {result && ( + + + + 결과 ({result.result_count}/{result.universe_count} 종목) + +

기준일: {result.base_date}

+
+ +
+ + + + + + + + + + + + + + + + {result.stocks.map((stock) => ( + + + + + + + + + + + + ))} + +
순위종목섹터시가총액(억)현재가PERPBR모멘텀종합
{stock.rank} + {stock.name || stock.ticker} + {stock.sector_name || '-'}{formatCurrency(stock.market_cap)}{formatCurrency(stock.close_price)}{formatNumber(stock.per)}{formatNumber(stock.pbr)}{formatNumber(stock.momentum_score)}{formatNumber(stock.total_score)}
+
+
+
+ )} + + ); +} diff --git a/frontend/src/app/strategy/page.tsx b/frontend/src/app/strategy/page.tsx index 1cda2a6..c699d74 100644 --- a/frontend/src/app/strategy/page.tsx +++ b/frontend/src/app/strategy/page.tsx @@ -6,7 +6,7 @@ import { DashboardLayout } from '@/components/layout/dashboard-layout'; import { StrategyCard } from '@/components/strategy/strategy-card'; import { api } from '@/lib/api'; import { Skeleton } from '@/components/ui/skeleton'; -import { BarChart3, Star, TrendingUp } from 'lucide-react'; +import { BarChart3, Star, TrendingUp, Zap } from 'lucide-react'; const strategies = [ { @@ -36,6 +36,15 @@ const strategies = [ risk: 'high' as const, stockCount: 25, }, + { + id: 'kjb', + title: '김종봉 단기매매', + description: 'KOSPI 대비 상대강도 + 박스권 돌파/장대양봉 기반 단기 트레이딩 전략입니다. 손절 -3%, 익절 +5%/+10%.', + icon: Zap, + expectedCagr: '20-30%', + risk: 'high' as const, + stockCount: 30, + }, ]; export default function StrategyListPage() {