feat: Add strategy presets for quick configuration
This commit is contained in:
166
frontend/src/utils/strategyPresets.ts
Normal file
166
frontend/src/utils/strategyPresets.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 전략 프리셋 정의
|
||||
*
|
||||
* 사용자가 복잡한 파라미터를 직접 조정하지 않고
|
||||
* 미리 정의된 설정을 선택할 수 있도록 지원
|
||||
*/
|
||||
|
||||
export interface PresetConfig {
|
||||
name: string
|
||||
description: string
|
||||
emoji: string
|
||||
risk: 'low' | 'medium' | 'high'
|
||||
expectedReturn: string
|
||||
params: Record<string, string | number | boolean>
|
||||
}
|
||||
|
||||
export const STRATEGY_PRESETS: Record<string, PresetConfig> = {
|
||||
conservative: {
|
||||
name: '보수적 (안정 중시)',
|
||||
description: '낮은 위험으로 꾸준한 수익 추구. 손절 타이트, 진입 까다로움',
|
||||
emoji: '🛡️',
|
||||
risk: 'low',
|
||||
expectedReturn: '연 8-12%',
|
||||
params: {
|
||||
// 리스크 관리
|
||||
'MEAN_REVERSION_STOP_LOSS_PCT': -5.0, // 손절 -5%
|
||||
'MEAN_REVERSION_TAKE_PROFIT_PCT': 8.0, // 익절 +8%
|
||||
'MAX_TOTAL_POSITIONS': 3, // 최대 3종목
|
||||
'MDD_LIMIT_PCT': -15.0, // MDD 한도 -15%
|
||||
|
||||
// 진입 필터 (까다롭게)
|
||||
'MEAN_REVERSION_RSI_THRESHOLD': 25, // RSI ≤ 25만 진입
|
||||
'MIN_AVG_VOLUME_20D': 100000, // 최소 거래량 높게
|
||||
'MIN_MARKET_CAP': 3000, // 시가총액 3천억 이상
|
||||
|
||||
// 지표
|
||||
'RSI_PERIOD': 14,
|
||||
'BOLLINGER_PERIOD': 20,
|
||||
'ADX_THRESHOLD': 30, // 강추세 시 진입 차단
|
||||
|
||||
// 데이트레이딩 비활성화
|
||||
'DAY_TRADE_ENABLED': false,
|
||||
'DAY_TRADE_MAX_POSITIONS': 0,
|
||||
},
|
||||
},
|
||||
|
||||
balanced: {
|
||||
name: '균형형 (기본 설정)',
|
||||
description: '리스크와 수익의 균형. 대부분의 시장 환경에 적합',
|
||||
emoji: '⚖️',
|
||||
risk: 'medium',
|
||||
expectedReturn: '연 12-18%',
|
||||
params: {
|
||||
// 리스크 관리
|
||||
'MEAN_REVERSION_STOP_LOSS_PCT': -6.0,
|
||||
'MEAN_REVERSION_TAKE_PROFIT_PCT': 12.0,
|
||||
'MAX_TOTAL_POSITIONS': 5,
|
||||
'MDD_LIMIT_PCT': -20.0,
|
||||
|
||||
// 진입 필터 (표준)
|
||||
'MEAN_REVERSION_RSI_THRESHOLD': 30,
|
||||
'MIN_AVG_VOLUME_20D': 50000,
|
||||
'MIN_MARKET_CAP': 1000,
|
||||
|
||||
// 지표
|
||||
'RSI_PERIOD': 14,
|
||||
'BOLLINGER_PERIOD': 20,
|
||||
'ADX_THRESHOLD': 30,
|
||||
|
||||
// 데이트레이딩 제한적 활성화
|
||||
'DAY_TRADE_ENABLED': true,
|
||||
'DAY_TRADE_MAX_POSITIONS': 2,
|
||||
'DAY_TRADE_MAX_CAPITAL_RATIO': 0.15, // 전체 자본의 15%
|
||||
},
|
||||
},
|
||||
|
||||
aggressive: {
|
||||
name: '공격적 (수익 극대화)',
|
||||
description: '높은 수익 목표, 넓은 손절폭. 변동성 수용 필수',
|
||||
emoji: '🚀',
|
||||
risk: 'high',
|
||||
expectedReturn: '연 20-30%',
|
||||
params: {
|
||||
// 리스크 관리 (여유롭게)
|
||||
'MEAN_REVERSION_STOP_LOSS_PCT': -8.0, // 손절 -8%
|
||||
'MEAN_REVERSION_TAKE_PROFIT_PCT': 20.0, // 익절 +20%
|
||||
'MAX_TOTAL_POSITIONS': 8, // 최대 8종목
|
||||
'MDD_LIMIT_PCT': -25.0,
|
||||
|
||||
// 진입 필터 (완화)
|
||||
'MEAN_REVERSION_RSI_THRESHOLD': 35, // RSI ≤ 35
|
||||
'MIN_AVG_VOLUME_20D': 30000,
|
||||
'MIN_MARKET_CAP': 500, // 시가총액 500억
|
||||
|
||||
// 지표
|
||||
'RSI_PERIOD': 10, // 더 민감하게
|
||||
'BOLLINGER_PERIOD': 15,
|
||||
'ADX_THRESHOLD': 40, // 강추세도 일부 허용
|
||||
|
||||
// 데이트레이딩 적극 활용
|
||||
'DAY_TRADE_ENABLED': true,
|
||||
'DAY_TRADE_MAX_POSITIONS': 3,
|
||||
'DAY_TRADE_MAX_CAPITAL_RATIO': 0.25, // 전체 자본의 25%
|
||||
'DAY_TRADE_ORB_STOP_LOSS_PCT': 0.025, // ORB 손절 -2.5%
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const PRESET_ORDER = ['conservative', 'balanced', 'aggressive'] as const
|
||||
export type PresetKey = typeof PRESET_ORDER[number]
|
||||
|
||||
/**
|
||||
* 현재 설정이 어떤 프리셋에 가장 가까운지 판단
|
||||
*/
|
||||
export function detectCurrentPreset(
|
||||
configs: Array<{ key: string; value: string }>
|
||||
): PresetKey | 'custom' {
|
||||
const configMap = new Map(configs.map(c => [c.key, c.value]))
|
||||
|
||||
for (const presetKey of PRESET_ORDER) {
|
||||
const preset = STRATEGY_PRESETS[presetKey]
|
||||
let matchCount = 0
|
||||
let totalChecked = 0
|
||||
|
||||
for (const [key, expectedValue] of Object.entries(preset.params)) {
|
||||
const currentValue = configMap.get(key)
|
||||
if (currentValue !== undefined) {
|
||||
totalChecked++
|
||||
if (String(currentValue) === String(expectedValue)) {
|
||||
matchCount++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 80% 이상 일치하면 해당 프리셋으로 간주
|
||||
if (totalChecked > 0 && matchCount / totalChecked >= 0.8) {
|
||||
return presetKey as PresetKey
|
||||
}
|
||||
}
|
||||
|
||||
return 'custom'
|
||||
}
|
||||
|
||||
/**
|
||||
* 프리셋 적용 시 변경될 파라미터 목록 반환
|
||||
*/
|
||||
export function getPresetDiff(
|
||||
presetKey: PresetKey,
|
||||
currentConfigs: Array<{ key: string; value: string }>
|
||||
): Array<{ key: string; from: string; to: string }> {
|
||||
const preset = STRATEGY_PRESETS[presetKey]
|
||||
const diffs: Array<{ key: string; from: string; to: string }> = []
|
||||
|
||||
for (const [key, newValue] of Object.entries(preset.params)) {
|
||||
const current = currentConfigs.find(c => c.key === key)
|
||||
if (current && String(current.value) !== String(newValue)) {
|
||||
diffs.push({
|
||||
key,
|
||||
from: current.value,
|
||||
to: String(newValue),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return diffs
|
||||
}
|
||||
Reference in New Issue
Block a user