전략편집기 수정
This commit is contained in:
@@ -32,10 +32,11 @@ import {
|
||||
defaultStartMeta,
|
||||
type StartCombineOp,
|
||||
} from '../utils/strategyStartNodes';
|
||||
import IndicatorPaletteTab from './strategyEditor/IndicatorPaletteTab';
|
||||
import {
|
||||
COMPOSITE_INDICATOR_ITEMS,
|
||||
compositePeriodLabel,
|
||||
} from '../utils/compositeIndicators';
|
||||
loadPaletteItems,
|
||||
type PaletteItem,
|
||||
} from '../utils/strategyPaletteStorage';
|
||||
import {
|
||||
emptySignalFlowLayout,
|
||||
loadStrategyFlowLayout,
|
||||
@@ -135,6 +136,9 @@ export default function StrategyEditorPage({ theme }: Props) {
|
||||
const [sellCondition, setSellCondition] = useState<LogicNode | null>(null);
|
||||
const [signalTab, setSignalTab] = useState<'buy' | 'sell'>('buy');
|
||||
const [rightTab, setRightTab] = useState<'indicators' | 'templates'>('indicators');
|
||||
const [indicatorSubTab, setIndicatorSubTab] = useState<'auxiliary' | 'composite'>('auxiliary');
|
||||
const [auxiliaryPalette, setAuxiliaryPalette] = useState<PaletteItem[]>(() => loadPaletteItems('auxiliary'));
|
||||
const [compositePalette, setCompositePalette] = useState<PaletteItem[]>(() => loadPaletteItems('composite'));
|
||||
const [paletteSearch, setPaletteSearch] = useState('');
|
||||
const [selectedPaletteKey, setSelectedPaletteKey] = useState<string | null>(null);
|
||||
const [stratName, setStratName] = useState('');
|
||||
@@ -689,6 +693,19 @@ export default function StrategyEditorPage({ theme }: Props) {
|
||||
else setCurrentRoot(mergeAtRoot(root, newNode, false));
|
||||
}, [signalTab, DEF, currentRoot, setCurrentRoot, handleAddStartSection]);
|
||||
|
||||
const applyPaletteItem = useCallback((item: PaletteItem) => {
|
||||
const composite = item.kind === 'composite';
|
||||
const newNode = makeNode('indicator', item.value, signalTab, DEF, {
|
||||
composite,
|
||||
period: item.period,
|
||||
leftPeriod: item.shortPeriod,
|
||||
rightPeriod: item.longPeriod,
|
||||
});
|
||||
const root = currentRoot;
|
||||
if (!root) setCurrentRoot(newNode);
|
||||
else setCurrentRoot(mergeAtRoot(root, newNode, false));
|
||||
}, [signalTab, DEF, currentRoot, setCurrentRoot]);
|
||||
|
||||
const templates = useMemo(() => getStrategyTemplates(DEF), [DEF]);
|
||||
|
||||
const handleTemplate = useCallback((tmpl: StrategyTemplateDef) => {
|
||||
@@ -724,34 +741,16 @@ export default function StrategyEditorPage({ theme }: Props) {
|
||||
{ type: 'indicator' as const, value: 'ICHIMOKU', label: 'Ichimoku', desc: '일목균형표', color: 'band' },
|
||||
];
|
||||
|
||||
const indicatorItems = [
|
||||
{ value: 'RSI', label: 'RSI', desc: '상대강도지수', color: 'ind' as const },
|
||||
{ value: 'MACD', label: 'MACD', desc: '이동평균 수렴확산', color: 'ind' as const },
|
||||
{ value: 'STOCHASTIC', label: 'Stochastic', desc: '스토캐스틱', color: 'ind' as const },
|
||||
{ value: 'CCI', label: 'CCI', desc: '상품채널지수', color: 'ind' as const },
|
||||
{ value: 'ADX', label: 'ADX', desc: '평균방향지수', color: 'ind' as const },
|
||||
{ value: 'DMI', label: 'DMI', desc: '방향성 지표', color: 'ind' as const },
|
||||
{ value: 'OBV', label: 'OBV', desc: '거래량 균형', color: 'ind' as const },
|
||||
{ value: 'WILLIAMS_R', label: 'Williams %R', desc: '윌리엄스 %R', color: 'ind' as const },
|
||||
{ value: 'TRIX', label: 'TRIX', desc: '삼중지수이동평균', color: 'ind' as const },
|
||||
{ value: 'VOLUME_OSC', label: 'Volume OSC', desc: '거래량 오실레이터', color: 'ind' as const },
|
||||
{ value: 'VR', label: 'VR', desc: '거래량 비율', color: 'ind' as const },
|
||||
{ value: 'DISPARITY', label: '이격도', desc: 'Disparity', color: 'ind' as const },
|
||||
{ value: 'PSYCHOLOGICAL', label: '심리도', desc: 'Psychological', color: 'ind' as const },
|
||||
{ value: 'INVEST_PSYCHOLOGICAL', label: '투자심리도', desc: 'Invest PSY', color: 'ind' as const },
|
||||
{ value: 'VOLUME', label: '거래량', desc: 'Volume', color: 'ind' as const },
|
||||
];
|
||||
|
||||
const q = paletteSearch.trim().toLowerCase();
|
||||
const match = (label: string, desc?: string) =>
|
||||
!q || label.toLowerCase().includes(q) || (desc?.toLowerCase().includes(q) ?? false);
|
||||
|
||||
const paletteKey = (type: 'operator' | 'indicator' | 'composite', value: string) => `${type}:${value}`;
|
||||
const selectPalette = (type: 'operator' | 'indicator' | 'composite', value: string) => {
|
||||
setSelectedPaletteKey(paletteKey(type, value));
|
||||
const paletteKey = (type: string, id: string) => `${type}:${id}`;
|
||||
const selectPalette = (type: string, id: string) => {
|
||||
setSelectedPaletteKey(paletteKey(type, id));
|
||||
};
|
||||
const isPaletteSelected = (type: 'operator' | 'indicator' | 'composite', value: string) =>
|
||||
selectedPaletteKey === paletteKey(type, value);
|
||||
const isPaletteSelected = (type: string, id: string) =>
|
||||
selectedPaletteKey === paletteKey(type, id);
|
||||
|
||||
return (
|
||||
<div className={`se-page se-page--${theme}`}>
|
||||
@@ -1085,45 +1084,65 @@ export default function StrategyEditorPage({ theme }: Props) {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="se-palette-section se-palette-section--aux">
|
||||
<h3>보조지표</h3>
|
||||
<div className="se-palette-grid se-palette-grid--3">
|
||||
{indicatorItems.filter(i => match(i.label, i.desc)).map(item => (
|
||||
<PaletteChip
|
||||
key={item.value}
|
||||
type="indicator"
|
||||
value={item.value}
|
||||
label={item.label}
|
||||
desc={item.desc}
|
||||
color={item.color}
|
||||
period={getIndicatorPeriodLabel(item.value, DEF)}
|
||||
selected={isPaletteSelected('indicator', item.value)}
|
||||
onSelect={() => selectPalette('indicator', item.value)}
|
||||
onAdd={() => applyPalette('indicator', item.value, item.label)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="se-palette-section se-palette-section--composite se-palette-section--scroll">
|
||||
<h3>복합지표</h3>
|
||||
<div className="se-palette-grid se-palette-grid--3">
|
||||
{COMPOSITE_INDICATOR_ITEMS.filter(i => match(i.label, i.desc)).map(item => (
|
||||
<PaletteChip
|
||||
key={`composite-${item.value}`}
|
||||
type="indicator"
|
||||
value={item.value}
|
||||
label={item.label}
|
||||
desc={item.desc}
|
||||
color="composite"
|
||||
composite
|
||||
period={compositePeriodLabel(item.value, DEF)}
|
||||
selected={isPaletteSelected('composite', item.value)}
|
||||
onSelect={() => selectPalette('composite', item.value)}
|
||||
onAdd={() => applyPalette('indicator', item.value, item.label, true)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="se-palette-subtabs">
|
||||
<button
|
||||
type="button"
|
||||
className={`se-palette-subtab${indicatorSubTab === 'auxiliary' ? ' se-palette-subtab--on' : ''}`}
|
||||
onClick={() => {
|
||||
setIndicatorSubTab('auxiliary');
|
||||
setSelectedPaletteKey(null);
|
||||
}}
|
||||
>
|
||||
보조지표
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`se-palette-subtab${indicatorSubTab === 'composite' ? ' se-palette-subtab--on' : ''}`}
|
||||
onClick={() => {
|
||||
setIndicatorSubTab('composite');
|
||||
setSelectedPaletteKey(null);
|
||||
}}
|
||||
>
|
||||
복합지표
|
||||
</button>
|
||||
</div>
|
||||
{indicatorSubTab === 'auxiliary' ? (
|
||||
<IndicatorPaletteTab
|
||||
kind="auxiliary"
|
||||
items={auxiliaryPalette}
|
||||
onItemsChange={setAuxiliaryPalette}
|
||||
def={DEF}
|
||||
searchQuery={paletteSearch}
|
||||
selectedItemId={
|
||||
selectedPaletteKey?.startsWith('auxiliary:')
|
||||
? selectedPaletteKey.slice('auxiliary:'.length)
|
||||
: null
|
||||
}
|
||||
onSelectItem={id => setSelectedPaletteKey(id ? paletteKey('auxiliary', id) : null)}
|
||||
onAddToCanvas={item => {
|
||||
selectPalette('auxiliary', item.id);
|
||||
applyPaletteItem(item);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<IndicatorPaletteTab
|
||||
kind="composite"
|
||||
items={compositePalette}
|
||||
onItemsChange={setCompositePalette}
|
||||
def={DEF}
|
||||
searchQuery={paletteSearch}
|
||||
selectedItemId={
|
||||
selectedPaletteKey?.startsWith('composite:')
|
||||
? selectedPaletteKey.slice('composite:'.length)
|
||||
: null
|
||||
}
|
||||
onSelectItem={id => setSelectedPaletteKey(id ? paletteKey('composite', id) : null)}
|
||||
onAddToCanvas={item => {
|
||||
selectPalette('composite', item.id);
|
||||
applyPaletteItem(item);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{rightTab === 'templates' && (
|
||||
|
||||
Reference in New Issue
Block a user