전략편집기 수정, 보조지표 설정 수정
This commit is contained in:
@@ -52,18 +52,25 @@ const ColorInput: React.FC<ColorInputProps> = ({ value, onChange, disabled = fal
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onDoc = (e: MouseEvent) => {
|
||||
const onDocPointer = (e: Event) => {
|
||||
const t = e.target as Node;
|
||||
if (anchorRef.current?.contains(t) || popoverRef.current?.contains(t)) return;
|
||||
setOpen(false);
|
||||
};
|
||||
document.addEventListener('mousedown', onDoc);
|
||||
return () => document.removeEventListener('mousedown', onDoc);
|
||||
// 설정 모달 등이 bubble 단계에서 stopPropagation 하므로 capture 사용
|
||||
document.addEventListener('mousedown', onDocPointer, true);
|
||||
document.addEventListener('pointerdown', onDocPointer, true);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', onDocPointer, true);
|
||||
document.removeEventListener('pointerdown', onDocPointer, true);
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
const patchHex = (h: string) => onChange(formatPlotColor(h, alpha));
|
||||
const patchAlpha = (a: number) => onChange(formatPlotColor(hex6, a));
|
||||
|
||||
const closePopover = useCallback(() => setOpen(false), []);
|
||||
|
||||
const popover = open && typeof document !== 'undefined' ? (
|
||||
<div
|
||||
ref={popoverRef}
|
||||
@@ -72,7 +79,19 @@ const ColorInput: React.FC<ColorInputProps> = ({ value, onChange, disabled = fal
|
||||
role="dialog"
|
||||
aria-label="색상 선택"
|
||||
onMouseDown={e => e.stopPropagation()}
|
||||
onPointerDown={e => e.stopPropagation()}
|
||||
>
|
||||
<div className="ism-color-popover-header">
|
||||
<span className="ism-color-popover-title">색상 선택</span>
|
||||
<button
|
||||
type="button"
|
||||
className="gc-popup-close ism-color-popover-close"
|
||||
onClick={closePopover}
|
||||
aria-label="색상 선택 닫기"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
<div className="ism-color-popover-native">
|
||||
<label className="ism-color-native-label">
|
||||
<span className="ism-style-label">직접 선택</span>
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
genId,
|
||||
type StrategyDto,
|
||||
} from '../utils/strategyEditorShared';
|
||||
import { syncLogicNodeWithGlobalDef } from '../utils/strategyDefSync';
|
||||
import { syncLogicNodeWithGlobalDef, syncLogicRootWithGlobalDef } from '../utils/strategyDefSync';
|
||||
import { useIndicatorSettings } from '../hooks/useIndicatorSettings';
|
||||
import { findLogicNode, getIndicatorPeriodLabel } from '../utils/strategyFlowLayout';
|
||||
import {
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
hasMultipleStartSections,
|
||||
normalizeStartCombineOp,
|
||||
updateStartCombineOp,
|
||||
updateStartCandleTypes,
|
||||
type EditorConditionState,
|
||||
} from '../utils/strategyConditionSerde';
|
||||
import {
|
||||
@@ -131,7 +132,7 @@ function normalizeTabLayoutState(snap: SignalFlowLayoutSnapshot) {
|
||||
}
|
||||
|
||||
export default function StrategyEditorPage({ theme }: Props) {
|
||||
const { getParams, getVisualConfig, settingsRevision } = useIndicatorSettings();
|
||||
const { getParams, getVisualConfig, settingsRevision, settingsLoaded } = useIndicatorSettings();
|
||||
const DEF = useMemo(
|
||||
() => buildStrategyEditorDefFromSettings(getParams, getVisualConfig),
|
||||
[getParams, getVisualConfig, settingsRevision],
|
||||
@@ -391,6 +392,11 @@ export default function StrategyEditorPage({ theme }: Props) {
|
||||
schedulePersistFlowLayout(layoutStrategyKey);
|
||||
}, [setCurrentRoot, setCurrentLayout, layoutStrategyKey, schedulePersistFlowLayout]);
|
||||
|
||||
const handleStartCandleTypesChange = useCallback((startId: string, candleTypes: string[]) => {
|
||||
const state = signalTab === 'buy' ? buyEditorState : sellEditorState;
|
||||
handleEditorStateChange(updateStartCandleTypes(state, startId, candleTypes));
|
||||
}, [signalTab, buyEditorState, sellEditorState, handleEditorStateChange]);
|
||||
|
||||
const handleStartCombineOpChange = useCallback((op: StartCombineOp) => {
|
||||
handleEditorStateChange(updateStartCombineOp(currentEditorState, op));
|
||||
}, [handleEditorStateChange, currentEditorState]);
|
||||
@@ -491,13 +497,26 @@ export default function StrategyEditorPage({ theme }: Props) {
|
||||
const sellDecoded = decodeConditionForEditor(s.sellCondition ?? null);
|
||||
const stored = loadStrategyFlowLayout(String(s.id));
|
||||
|
||||
const buySynced = syncLogicRootWithGlobalDef(
|
||||
buyDecoded.root,
|
||||
stored?.buy.orphans ?? [],
|
||||
DEF,
|
||||
'buy',
|
||||
);
|
||||
const sellSynced = syncLogicRootWithGlobalDef(
|
||||
sellDecoded.root,
|
||||
stored?.sell.orphans ?? [],
|
||||
DEF,
|
||||
'sell',
|
||||
);
|
||||
|
||||
setSelectedId(s.id);
|
||||
setStratName(s.name);
|
||||
setStratDesc(s.description ?? '');
|
||||
setBuyCondition(buyDecoded.root);
|
||||
setSellCondition(sellDecoded.root);
|
||||
setBuyOrphans(stored?.buy.orphans ?? []);
|
||||
setSellOrphans(stored?.sell.orphans ?? []);
|
||||
setBuyCondition(buySynced.root);
|
||||
setSellCondition(sellSynced.root);
|
||||
setBuyOrphans(buySynced.orphans);
|
||||
setSellOrphans(sellSynced.orphans);
|
||||
setBuyLayout({
|
||||
positions: stored?.buy.positions ?? {},
|
||||
edgeHandles: stored?.buy.edgeHandles ?? {},
|
||||
@@ -802,7 +821,12 @@ export default function StrategyEditorPage({ theme }: Props) {
|
||||
<header className="se-header">
|
||||
<div className="se-header-left">
|
||||
<h1 className="se-title">전략 빌더</h1>
|
||||
<span className="se-subtitle">Strategy Builder</span>
|
||||
<span className="se-subtitle">
|
||||
Strategy Builder
|
||||
{!settingsLoaded && (
|
||||
<span className="se-subtitle-hint"> · 보조지표 설정 로드 중…</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="se-header-actions">
|
||||
<div className="se-editor-mode" role="group" aria-label="편집 방식">
|
||||
@@ -993,6 +1017,7 @@ export default function StrategyEditorPage({ theme }: Props) {
|
||||
extraStartIds={currentLayout.extraStartIds}
|
||||
extraRoots={currentLayout.extraRoots}
|
||||
onStartMetaChange={handleStartMetaChange}
|
||||
onStartCandleTypesChange={handleStartCandleTypesChange}
|
||||
onExtraStartIdsChange={handleExtraStartIdsChange}
|
||||
onExtraRootsChange={handleExtraRootsChange}
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import type { ConditionDSL } from '../../utils/strategyTypes';
|
||||
import type { DefType } from '../../utils/strategyEditorShared';
|
||||
import { getDefaultConditionFields, type DefType } from '../../utils/strategyEditorShared';
|
||||
import {
|
||||
getCompositeLeftCandleType,
|
||||
getCompositeRightCandleType,
|
||||
@@ -27,13 +27,19 @@ import ComboNumberInput from './ComboNumberInput';
|
||||
interface Props {
|
||||
condition: ConditionDSL;
|
||||
def: DefType;
|
||||
signalType?: 'buy' | 'sell';
|
||||
onChange: (c: ConditionDSL) => void;
|
||||
onClose: () => void;
|
||||
popoverClassName?: string;
|
||||
}
|
||||
|
||||
export default function ConditionNodeSettings({
|
||||
condition, def, onChange, onClose, popoverClassName = 'se-flow-settings-pop',
|
||||
condition,
|
||||
def,
|
||||
signalType = 'buy',
|
||||
onChange,
|
||||
onClose,
|
||||
popoverClassName = 'se-flow-settings-pop',
|
||||
}: Props) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -45,7 +51,8 @@ export default function ConditionNodeSettings({
|
||||
return () => document.removeEventListener('mousedown', onDoc);
|
||||
}, [onClose]);
|
||||
|
||||
const rightThreshold = getConditionThreshold(condition, 'right');
|
||||
const inheritedThreshold = getDefaultConditionFields(condition.indicatorType, signalType, def).r;
|
||||
const rightThreshold = getConditionThreshold(condition, 'right', inheritedThreshold);
|
||||
const showThreshold = hasEditableThreshold(condition) && rightThreshold != null;
|
||||
const periodPresets = getPeriodPresetOptions(condition.indicatorType, def);
|
||||
const thresholdPresets = getThresholdPresetOptions(condition.indicatorType);
|
||||
|
||||
@@ -283,6 +283,7 @@ export const ConditionFlowNode = memo(function ConditionFlowNode({ id, data, sel
|
||||
<ConditionNodeSettings
|
||||
condition={cond}
|
||||
def={d.def}
|
||||
signalType={d.signalTab ?? 'buy'}
|
||||
onChange={handleSettingsChange}
|
||||
onClose={() => setSettingsOpen(false)}
|
||||
/>
|
||||
|
||||
@@ -105,6 +105,8 @@ interface Props {
|
||||
extraStartIds?: string[];
|
||||
extraRoots?: Record<string, LogicNode | null>;
|
||||
onStartMetaChange?: (meta: Record<string, StartNodeMeta>) => void;
|
||||
/** START 체크박스 — startMeta + 조건 트리 분봉 동기화 */
|
||||
onStartCandleTypesChange?: (startId: string, candleTypes: string[]) => void;
|
||||
onExtraStartIdsChange?: (ids: string[]) => void;
|
||||
onExtraRootsChange?: (roots: Record<string, LogicNode | null>) => void;
|
||||
}
|
||||
@@ -268,6 +270,7 @@ function StrategyEditorCanvasInner({
|
||||
extraStartIds = [],
|
||||
extraRoots = {},
|
||||
onStartMetaChange,
|
||||
onStartCandleTypesChange,
|
||||
onExtraStartIdsChange,
|
||||
onExtraRootsChange,
|
||||
}: Props) {
|
||||
@@ -372,13 +375,17 @@ function StrategyEditorCanvasInner({
|
||||
}, [emitLayoutChange]);
|
||||
|
||||
const handleStartCandleTypesChange = useCallback((startId: string, candleTypes: string[]) => {
|
||||
if (onStartCandleTypesChange) {
|
||||
onStartCandleTypesChange(startId, candleTypes);
|
||||
return;
|
||||
}
|
||||
const types = candleTypes.length > 0 ? candleTypes : [DEFAULT_START_CANDLE];
|
||||
const normalized = types.map(normalizeStartCandleType);
|
||||
onStartMetaChange?.({
|
||||
...startMeta,
|
||||
[startId]: { candleTypes: normalized, candleType: normalized[0] },
|
||||
});
|
||||
}, [startMeta, onStartMetaChange]);
|
||||
}, [startMeta, onStartMetaChange, onStartCandleTypesChange]);
|
||||
|
||||
const handleDeleteStart = useCallback((startId: string) => {
|
||||
if (startId === START_NODE_ID) return;
|
||||
|
||||
@@ -149,6 +149,7 @@ const TreeNodeComp: React.FC<TreeNodeProps> = ({
|
||||
<ConditionNodeSettings
|
||||
condition={node.condition}
|
||||
def={def}
|
||||
signalType={signalType}
|
||||
onChange={handleCondChange}
|
||||
onClose={() => setSettingsOpen(false)}
|
||||
popoverClassName="sp-node-settings-pop"
|
||||
|
||||
Reference in New Issue
Block a user