전략편집기 평가탭 추가
This commit is contained in:
@@ -121,6 +121,7 @@ import LogicExpressionPreview from './strategyEditor/LogicExpressionPreview';
|
||||
import LogicExpressionNarrative from './strategyEditor/LogicExpressionNarrative';
|
||||
import StrategyEditorCanvas, { type FlowLayoutSeed, type StrategyEditorCanvasHandle } from './strategyEditor/StrategyEditorCanvas';
|
||||
import StrategyListEditor from './strategyEditor/StrategyListEditor';
|
||||
import StrategyEditorEvaluationPanel from './strategyEditor/StrategyEditorEvaluationPanel';
|
||||
import StartCombineOpControl from './strategyEditor/StartCombineOpControl';
|
||||
import { layoutFlushRef } from './strategyEditor/strategyEditorCallbacks';
|
||||
import {
|
||||
@@ -178,6 +179,13 @@ import {
|
||||
import '../styles/strategyEditor.css';
|
||||
import '../styles/strategyEditorTheme.css';
|
||||
|
||||
type SignalTab = 'buy' | 'sell' | 'eval';
|
||||
type BuySellTab = 'buy' | 'sell';
|
||||
|
||||
function buySellTab(tab: SignalTab): BuySellTab {
|
||||
return tab === 'sell' ? 'sell' : 'buy';
|
||||
}
|
||||
|
||||
const BACKTEST_FOCUS_KEY = 'backtest_focus_id';
|
||||
|
||||
const LEFT_PANEL_MIN = 220;
|
||||
@@ -301,7 +309,7 @@ export default function StrategyEditorPage({
|
||||
const [selectedId, setSelectedId] = useState<number | null>(null);
|
||||
const [buyCondition, setBuyCondition] = useState<LogicNode | null>(null);
|
||||
const [sellCondition, setSellCondition] = useState<LogicNode | null>(null);
|
||||
const [signalTab, setSignalTab] = useState<'buy' | 'sell'>('buy');
|
||||
const [signalTab, setSignalTab] = useState<SignalTab>('buy');
|
||||
const [rightTab, setRightTab] = useState<'indicators' | 'templates'>('indicators');
|
||||
const [indicatorSubTab, setIndicatorSubTab] = useState<'auxiliary' | 'composite' | 'range'>('auxiliary');
|
||||
const [auxiliaryPalette, setAuxiliaryPalette] = useState<PaletteItem[]>(() => loadPaletteItems('auxiliary'));
|
||||
@@ -610,7 +618,7 @@ export default function StrategyEditorPage({
|
||||
...prev,
|
||||
extraRoots: syncExtraRoots(prev.extraRoots ?? {}, 'sell'),
|
||||
}));
|
||||
bumpLayoutSeed(layoutStrategyKey, signalTab);
|
||||
if (signalTab !== 'eval') bumpLayoutSeed(layoutStrategyKey, buySellTab(signalTab));
|
||||
if (selectedId != null && stratName.trim()) scheduleStrategyPersist();
|
||||
}, [settingsLoaded, settingsRevision, DEF, layoutStrategyKey, signalTab, bumpLayoutSeed, selectedId, stratName, scheduleStrategyPersist]);
|
||||
|
||||
@@ -647,7 +655,7 @@ export default function StrategyEditorPage({
|
||||
const handleExtraStartIdsChange = useCallback((ids: string[]) => {
|
||||
setCurrentLayout(prev => ({ ...prev, extraStartIds: ids }));
|
||||
scheduleStrategyPersist();
|
||||
bumpLayoutSeed(layoutStrategyKey, signalTab);
|
||||
if (signalTab !== 'eval') bumpLayoutSeed(layoutStrategyKey, buySellTab(signalTab));
|
||||
}, [setCurrentLayout, layoutStrategyKey, scheduleStrategyPersist, bumpLayoutSeed, signalTab]);
|
||||
|
||||
const handleExtraRootsChange = useCallback((roots: Record<string, LogicNode | null>) => {
|
||||
@@ -814,14 +822,17 @@ export default function StrategyEditorPage({
|
||||
handleEditorStateChange(prev => updateStartCombineOp(prev, op));
|
||||
}, [handleEditorStateChange]);
|
||||
|
||||
const switchSignalTab = useCallback((tab: 'buy' | 'sell') => {
|
||||
if (editorMode === 'graph') layoutFlushRef.current?.();
|
||||
flushStrategyPersist();
|
||||
layoutRevisionRef.current += 1;
|
||||
setLayoutSeedKey(`${layoutStrategyKey}:${tab}:${layoutRevisionRef.current}`);
|
||||
const switchSignalTab = useCallback((tab: SignalTab) => {
|
||||
if (tab === signalTab) return;
|
||||
if (editorMode === 'graph' && signalTab !== 'eval') layoutFlushRef.current?.();
|
||||
if (signalTab !== 'eval') flushStrategyPersist();
|
||||
if (tab !== 'eval') {
|
||||
layoutRevisionRef.current += 1;
|
||||
setLayoutSeedKey(`${layoutStrategyKey}:${tab}:${layoutRevisionRef.current}`);
|
||||
}
|
||||
setSignalTab(tab);
|
||||
setSelectedNodeId(null);
|
||||
}, [layoutStrategyKey, flushStrategyPersist, editorMode]);
|
||||
if (tab !== 'eval') setSelectedNodeId(null);
|
||||
}, [layoutStrategyKey, flushStrategyPersist, editorMode, signalTab]);
|
||||
|
||||
const handleEditorModeChange = useCallback((mode: StrategyEditorMode) => {
|
||||
if (mode === editorMode) return;
|
||||
@@ -991,7 +1002,7 @@ export default function StrategyEditorPage({
|
||||
startCombineOp: normalizeStartCombineOp(stored?.sell.startCombineOp ?? sellDecoded.startCombineOp),
|
||||
});
|
||||
setSelectedNodeId(null);
|
||||
bumpLayoutSeed(String(s.id), signalTab);
|
||||
bumpLayoutSeed(String(s.id), buySellTab(signalTab));
|
||||
|
||||
if (!s.flowLayout && stored) {
|
||||
void (async () => {
|
||||
@@ -1144,7 +1155,7 @@ export default function StrategyEditorPage({
|
||||
}
|
||||
setStratName(name);
|
||||
setStratDesc(saveDraftDesc);
|
||||
bumpLayoutSeed(String(dbId), signalTab);
|
||||
bumpLayoutSeed(String(dbId), buySellTab(signalTab));
|
||||
setSaveOpen(false);
|
||||
setSaveToast(true);
|
||||
setTimeout(() => setSaveToast(false), 2500);
|
||||
@@ -1175,7 +1186,7 @@ export default function StrategyEditorPage({
|
||||
notifyStrategiesChanged();
|
||||
};
|
||||
|
||||
const applyImportedFlowLayout = useCallback((layout: StrategyFlowLayoutStore | undefined, tab: 'buy' | 'sell' = signalTab) => {
|
||||
const applyImportedFlowLayout = useCallback((layout: StrategyFlowLayoutStore | undefined, tab: BuySellTab = buySellTab(signalTab)) => {
|
||||
if (layout) {
|
||||
setBuyLayout({
|
||||
positions: layout.buy.positions ?? {},
|
||||
@@ -1346,7 +1357,7 @@ export default function StrategyEditorPage({
|
||||
|
||||
const handleAddStartSection = useCallback(() => {
|
||||
handleEditorStateChange(prev => addExtraStartSection(prev));
|
||||
bumpLayoutSeed(layoutStrategyKey, signalTab);
|
||||
if (signalTab !== 'eval') bumpLayoutSeed(layoutStrategyKey, buySellTab(signalTab));
|
||||
}, [handleEditorStateChange, bumpLayoutSeed, layoutStrategyKey, signalTab]);
|
||||
|
||||
const applyPalette = useCallback((type: string, value: string, _label: string, composite = false) => {
|
||||
@@ -1354,7 +1365,7 @@ export default function StrategyEditorPage({
|
||||
handleAddStartSection();
|
||||
return;
|
||||
}
|
||||
const newNode = makeNode(type, value, signalTab, DEF, composite ? { composite: true } : undefined);
|
||||
const newNode = makeNode(type, value, buySellTab(signalTab), DEF, composite ? { composite: true } : undefined);
|
||||
handleEditorStateChange(prev => {
|
||||
const root = prev.root;
|
||||
if (!root) return updateBranchRoot(prev, START_NODE_ID, newNode);
|
||||
@@ -1385,7 +1396,7 @@ export default function StrategyEditorPage({
|
||||
? buildStableStrategyTree(item.value, DEF)
|
||||
: ichimokuSituation
|
||||
? buildIchimokuSituationTree(item.value, DEF)
|
||||
: makeNode('indicator', item.value, signalTab, DEF, composite ? { composite: true } : undefined);
|
||||
: makeNode('indicator', item.value, buySellTab(signalTab), DEF, composite ? { composite: true } : undefined);
|
||||
if (!newNode) return;
|
||||
handleEditorStateChange(prev => {
|
||||
const root = prev.root;
|
||||
@@ -1583,7 +1594,7 @@ export default function StrategyEditorPage({
|
||||
showSnack('갱신할 조건이 없습니다', false);
|
||||
return;
|
||||
}
|
||||
patch.signal = signalTab;
|
||||
patch.signal = buySellTab(signalTab);
|
||||
patch.editorState = toEditorState(currentRoot, currentLayout);
|
||||
patch.orphans = currentOrphans;
|
||||
}
|
||||
@@ -1595,7 +1606,7 @@ export default function StrategyEditorPage({
|
||||
const saved = addUserStrategyTemplate({
|
||||
label,
|
||||
description: templateSaveDesc.trim() || undefined,
|
||||
signal: signalTab,
|
||||
signal: buySellTab(signalTab),
|
||||
editorState: toEditorState(currentRoot, currentLayout),
|
||||
orphans: currentOrphans,
|
||||
});
|
||||
@@ -1946,7 +1957,7 @@ export default function StrategyEditorPage({
|
||||
<div className="se-main-row">
|
||||
<main className="se-center">
|
||||
<div className="se-center-panel">
|
||||
<div className={`se-center-work${editorMode === 'list' ? ' se-center-work--list' : ''}`}>
|
||||
<div className={`se-center-work${editorMode === 'list' && signalTab !== 'eval' ? ' se-center-work--list' : ''}${signalTab === 'eval' ? ' se-center-work--eval' : ''}`}>
|
||||
<div className="se-center-head">
|
||||
<div className="se-signal-tabs">
|
||||
<button
|
||||
@@ -1963,9 +1974,16 @@ export default function StrategyEditorPage({
|
||||
>
|
||||
매도 조건 (Exit)
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`se-signal-tab${signalTab === 'eval' ? ' se-signal-tab--eval-on' : ''}`}
|
||||
onClick={() => switchSignalTab('eval')}
|
||||
>
|
||||
평가
|
||||
</button>
|
||||
</div>
|
||||
{stratName && <span className="se-editing-name">{stratName}</span>}
|
||||
{hasMultipleStartSections(currentEditorState) && (
|
||||
{signalTab !== 'eval' && hasMultipleStartSections(currentEditorState) && (
|
||||
<StartCombineOpControl
|
||||
value={normalizeStartCombineOp(currentEditorState.startCombineOp)}
|
||||
onChange={handleStartCombineOpChange}
|
||||
@@ -1973,6 +1991,7 @@ export default function StrategyEditorPage({
|
||||
)}
|
||||
<div className="se-center-head-actions">
|
||||
<button type="button" className="se-btn se-btn--ghost se-btn--sm" onClick={handleNew}>+ 새 전략</button>
|
||||
{signalTab !== 'eval' && (
|
||||
<div className="se-editor-mode se-editor-mode--compact" role="group" aria-label="편집 방식">
|
||||
<button
|
||||
type="button"
|
||||
@@ -1989,6 +2008,8 @@ export default function StrategyEditorPage({
|
||||
목록 방식
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{signalTab !== 'eval' && (
|
||||
<button
|
||||
type="button"
|
||||
className="se-btn se-btn--ghost se-btn--sm"
|
||||
@@ -1997,6 +2018,7 @@ export default function StrategyEditorPage({
|
||||
>
|
||||
템플릿 저장
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="se-btn se-btn--gold se-btn--sm"
|
||||
@@ -2015,7 +2037,15 @@ export default function StrategyEditorPage({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{editorMode === 'graph' ? (
|
||||
{signalTab === 'eval' ? (
|
||||
<StrategyEditorEvaluationPanel
|
||||
theme={theme}
|
||||
stratName={stratName}
|
||||
stratDesc={stratDesc}
|
||||
buyEditorState={buyEditorState}
|
||||
sellEditorState={sellEditorState}
|
||||
/>
|
||||
) : editorMode === 'graph' ? (
|
||||
<>
|
||||
<ReactFlowProvider>
|
||||
<StrategyEditorCanvas
|
||||
@@ -2025,7 +2055,7 @@ export default function StrategyEditorPage({
|
||||
orphans={currentOrphans}
|
||||
onOrphansChange={handleOrphansChange}
|
||||
def={DEF}
|
||||
signalTab={signalTab}
|
||||
signalTab={buySellTab(signalTab)}
|
||||
onChange={handleRootChange}
|
||||
selectedNodeId={selectedNodeId}
|
||||
onSelectNode={setSelectedNodeId}
|
||||
@@ -2061,7 +2091,7 @@ export default function StrategyEditorPage({
|
||||
</div>
|
||||
<CondEditor
|
||||
cond={selectedLogicNode.condition}
|
||||
signalType={signalTab}
|
||||
signalType={buySellTab(signalTab)}
|
||||
def={DEF}
|
||||
stochPairEdit={stochPairEditForSelected}
|
||||
onChange={c => {
|
||||
@@ -2195,7 +2225,7 @@ export default function StrategyEditorPage({
|
||||
) : (
|
||||
<StrategyListEditor
|
||||
editorState={currentEditorState}
|
||||
signalTab={signalTab}
|
||||
signalTab={buySellTab(signalTab)}
|
||||
def={DEF}
|
||||
onEditorStateChange={handleEditorStateChange}
|
||||
onAddStart={handleAddStartSection}
|
||||
@@ -2206,6 +2236,8 @@ export default function StrategyEditorPage({
|
||||
|
||||
</div>
|
||||
|
||||
{signalTab !== 'eval' && (
|
||||
<>
|
||||
<div
|
||||
className="se-splitter se-splitter--h"
|
||||
role="separator"
|
||||
@@ -2243,6 +2275,8 @@ export default function StrategyEditorPage({
|
||||
</section>
|
||||
</div>
|
||||
</footer>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user